diff --git a/Cargo.lock b/Cargo.lock index 97653e124a..a35dfd20cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4198,6 +4198,7 @@ dependencies = [ "anyhow", "gpui2", "log", + "serde", "smol", "util", ] @@ -11402,7 +11403,7 @@ dependencies = [ "ignore", "image", "indexmap 1.9.3", - "install_cli", + "install_cli2", "isahc", "journal2", "language2", diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 4b6b9bea73..b732be7455 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -2112,6 +2112,10 @@ impl AppContext { AsyncAppContext(self.weak_self.as_ref().unwrap().upgrade().unwrap()) } + pub fn open_url(&self, url: &str) { + self.platform.open_url(url) + } + pub fn write_to_clipboard(&self, item: ClipboardItem) { self.platform.write_to_clipboard(item); } diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index b6cb3f6307..5463550587 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -431,6 +431,18 @@ impl AppContext { self.platform.activate(ignoring_other_apps); } + pub fn hide(&self) { + self.platform.hide(); + } + + pub fn hide_other_apps(&self) { + self.platform.hide_other_apps(); + } + + pub fn unhide_other_apps(&self) { + self.platform.unhide_other_apps(); + } + /// Returns the list of currently active displays. pub fn displays(&self) -> Vec> { self.platform.displays() diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index eb69b451b3..4ad807b357 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1365,6 +1365,14 @@ impl<'a> WindowContext<'a> { self.window.platform_window.activate(); } + pub fn minimize_window(&self) { + self.window.platform_window.minimize(); + } + + pub fn toggle_full_screen(&self) { + self.window.platform_window.toggle_full_screen(); + } + pub fn prompt( &self, level: PromptLevel, diff --git a/crates/install_cli2/Cargo.toml b/crates/install_cli2/Cargo.toml index 3310e7fbc8..26fe212fe3 100644 --- a/crates/install_cli2/Cargo.toml +++ b/crates/install_cli2/Cargo.toml @@ -14,5 +14,6 @@ test-support = [] smol.workspace = true anyhow.workspace = true log.workspace = true +serde.workspace = true gpui = { package = "gpui2", path = "../gpui2" } util = { path = "../util" } diff --git a/crates/install_cli2/src/install_cli2.rs b/crates/install_cli2/src/install_cli2.rs index 7938d60210..6fd1019c3f 100644 --- a/crates/install_cli2/src/install_cli2.rs +++ b/crates/install_cli2/src/install_cli2.rs @@ -1,10 +1,9 @@ use anyhow::{anyhow, Result}; -use gpui::AsyncAppContext; +use gpui::{actions, AsyncAppContext}; use std::path::Path; use util::ResultExt; -// todo!() -// actions!(cli, [Install]); +actions!(Install); pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??; diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index f5cbc6e787..1a5f3329b8 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -45,7 +45,7 @@ use gpui::{ }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; -use language2::LanguageRegistry; +use language2::{LanguageRegistry, Rope}; use lazy_static::lazy_static; pub use modal_layer::*; use node_runtime::NodeRuntime; @@ -1241,29 +1241,29 @@ impl Workspace { // self.titlebar_item.clone() // } - // /// Call the given callback with a workspace whose project is local. - // /// - // /// If the given workspace has a local project, then it will be passed - // /// to the callback. Otherwise, a new empty window will be created. - // pub fn with_local_workspace( - // &mut self, - // cx: &mut ViewContext, - // callback: F, - // ) -> Task> - // where - // T: 'static, - // F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> T, - // { - // if self.project.read(cx).is_local() { - // Task::Ready(Some(Ok(callback(self, cx)))) - // } else { - // let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); - // cx.spawn(|_vh, mut cx| async move { - // let (workspace, _) = task.await; - // workspace.update(&mut cx, callback) - // }) - // } - // } + /// Call the given callback with a workspace whose project is local. + /// + /// If the given workspace has a local project, then it will be passed + /// to the callback. Otherwise, a new empty window will be created. + pub fn with_local_workspace( + &mut self, + cx: &mut ViewContext, + callback: F, + ) -> Task> + where + T: 'static, + F: 'static + FnOnce(&mut Workspace, &mut ViewContext) -> T, + { + if self.project.read(cx).is_local() { + Task::Ready(Some(Ok(callback(self, cx)))) + } else { + let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx); + cx.spawn(|_vh, mut cx| async move { + let (workspace, _) = task.await?; + workspace.update(&mut cx, callback) + }) + } + } pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator> { self.project.read(cx).worktrees() @@ -4507,32 +4507,32 @@ pub fn open_new( }) } -// pub fn create_and_open_local_file( -// path: &'static Path, -// cx: &mut ViewContext, -// default_content: impl 'static + Send + FnOnce() -> Rope, -// ) -> Task>> { -// cx.spawn(|workspace, mut cx| async move { -// let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?; -// if !fs.is_file(path).await { -// fs.create_file(path, Default::default()).await?; -// fs.save(path, &default_content(), Default::default()) -// .await?; -// } +pub fn create_and_open_local_file( + path: &'static Path, + cx: &mut ViewContext, + default_content: impl 'static + Send + FnOnce() -> Rope, +) -> Task>> { + cx.spawn(|workspace, mut cx| async move { + let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?; + if !fs.is_file(path).await { + fs.create_file(path, Default::default()).await?; + fs.save(path, &default_content(), Default::default()) + .await?; + } -// let mut items = workspace -// .update(&mut cx, |workspace, cx| { -// workspace.with_local_workspace(cx, |workspace, cx| { -// workspace.open_paths(vec![path.to_path_buf()], false, cx) -// }) -// })? -// .await? -// .await; + let mut items = workspace + .update(&mut cx, |workspace, cx| { + workspace.with_local_workspace(cx, |workspace, cx| { + workspace.open_paths(vec![path.to_path_buf()], false, cx) + }) + })? + .await? + .await; -// let item = items.pop().flatten(); -// item.ok_or_else(|| anyhow!("path {path:?} is not a file"))? -// }) -// } + let item = items.pop().flatten(); + item.ok_or_else(|| anyhow!("path {path:?} is not a file"))? + }) +} // pub fn join_remote_project( // project_id: u64, diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index 570912abc5..610bd90f69 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -43,7 +43,7 @@ fsevent = { path = "../fsevent" } fuzzy = { path = "../fuzzy" } go_to_line = { package = "go_to_line2", path = "../go_to_line2" } gpui = { package = "gpui2", path = "../gpui2" } -install_cli = { path = "../install_cli" } +install_cli = { package = "install_cli2", path = "../install_cli2" } journal = { package = "journal2", path = "../journal2" } language = { package = "language2", path = "../language2" } # language_selector = { path = "../language_selector" } diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 9a4ad81806..730dd255f9 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -98,7 +98,7 @@ fn main() { let (listener, mut open_rx) = OpenListener::new(); let listener = Arc::new(listener); let open_listener = listener.clone(); - app.on_open_urls(move |urls, _| open_listener.open_urls(urls)); + app.on_open_urls(move |urls, _| open_listener.open_urls(&urls)); app.on_reopen(move |_cx| { // todo!("workspace") // if cx.has_global::>() { @@ -211,13 +211,13 @@ fn main() { // zed::init(&app_state, cx); // cx.set_menus(menus::menus()); - init_zed_actions(cx); + init_zed_actions(app_state.clone(), cx); if stdout_is_a_pty() { cx.activate(true); let urls = collect_url_args(); if !urls.is_empty() { - listener.open_urls(urls) + listener.open_urls(&urls) } } else { upload_previous_panics(http.clone(), cx); @@ -227,7 +227,7 @@ fn main() { if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some() && !listener.triggered.load(Ordering::Acquire) { - listener.open_urls(collect_url_args()) + listener.open_urls(&collect_url_args()) } } diff --git a/crates/zed2/src/open_listener.rs b/crates/zed2/src/open_listener.rs index f4219f199d..4c961a2b31 100644 --- a/crates/zed2/src/open_listener.rs +++ b/crates/zed2/src/open_listener.rs @@ -54,7 +54,7 @@ impl OpenListener { ) } - pub fn open_urls(&self, urls: Vec) { + pub fn open_urls(&self, urls: &[String]) { self.triggered.store(true, Ordering::Release); let request = if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) @@ -101,7 +101,7 @@ impl OpenListener { None } - fn handle_file_urls(&self, urls: Vec) -> Option { + fn handle_file_urls(&self, urls: &[String]) -> Option { let paths: Vec<_> = urls .iter() .flat_map(|url| url.strip_prefix("file://")) diff --git a/crates/zed2/src/zed2.rs b/crates/zed2/src/zed2.rs index 54723ee8d8..37c317fb61 100644 --- a/crates/zed2/src/zed2.rs +++ b/crates/zed2/src/zed2.rs @@ -1,5 +1,5 @@ -#![allow(unused_variables, dead_code, unused_mut)] -// todo!() this is to make transition easier. +#![allow(unused_variables, unused_mut)] +//todo!() mod assets; pub mod languages; @@ -7,18 +7,54 @@ mod only_instance; mod open_listener; pub use assets::*; +use collections::VecDeque; +use editor::{Editor, MultiBuffer}; use gpui::{ - point, px, AppContext, AsyncWindowContext, Task, TitlebarOptions, WeakView, WindowBounds, - WindowKind, WindowOptions, + actions, point, px, AppContext, AsyncWindowContext, Context, PromptLevel, Task, + TitlebarOptions, ViewContext, VisualContext, WeakView, WindowBounds, WindowKind, WindowOptions, }; pub use only_instance::*; pub use open_listener::*; -use anyhow::Result; -use settings::Settings; -use std::sync::Arc; +use anyhow::{anyhow, Context as _, Result}; +use settings::{initial_local_settings_content, Settings}; +use std::{borrow::Cow, ops::Deref, sync::Arc}; +use util::{ + asset_str, + channel::ReleaseChannel, + paths::{self, LOCAL_SETTINGS_RELATIVE_PATH}, + ResultExt, +}; use uuid::Uuid; -use workspace::{AppState, Workspace}; +use workspace::{ + create_and_open_local_file, notifications::simple_message_notification::MessageNotification, + open_new, AppState, NewFile, NewWindow, Workspace, WorkspaceSettings, +}; +use zed_actions::{OpenBrowser, OpenZedURL}; + +actions!( + About, + DebugElements, + DecreaseBufferFontSize, + Hide, + HideOthers, + IncreaseBufferFontSize, + Minimize, + OpenDefaultKeymap, + OpenDefaultSettings, + OpenKeymap, + OpenLicenses, + OpenLocalSettings, + OpenLog, + OpenSettings, + OpenTelemetryLog, + Quit, + ResetBufferFontSize, + ResetDatabase, + ShowAll, + ToggleFullScreen, + Zoom, +); pub fn build_window_options( bounds: Option, @@ -48,209 +84,206 @@ pub fn build_window_options( } } -pub fn init_zed_actions(cx: &mut AppContext) { - cx.observe_new_views(|workspace: &mut Workspace, cx| { +pub fn init_zed_actions(app_state: Arc, cx: &mut AppContext) { + cx.observe_new_views(move |workspace: &mut Workspace, _cx| { workspace - // cx.add_action(about); - // cx.add_global_action(|_: &Hide, cx: &mut gpui::AppContext| { - // cx.platform().hide(); + .register_action(about) + .register_action(|_, _: &Hide, cx| { + cx.hide(); + }) + .register_action(|_, _: &HideOthers, cx| { + cx.hide_other_apps(); + }) + .register_action(|_, _: &ShowAll, cx| { + cx.unhide_other_apps(); + }) + .register_action(|_, _: &Minimize, cx| { + cx.minimize_window(); + }) + .register_action(|_, _: &Zoom, cx| { + cx.zoom_window(); + }) + .register_action(|_, _: &ToggleFullScreen, cx| { + cx.toggle_full_screen(); + }) + .register_action(quit) + .register_action(|_, action: &OpenZedURL, cx| { + cx.global::>() + .open_urls(&[action.url.clone()]) + }) + .register_action(|_, action: &OpenBrowser, cx| cx.open_url(&action.url)) + //todo!(buffer font size) + // cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { + // theme::adjust_font_size(cx, |size| *size += 1.0) // }); - // cx.add_global_action(|_: &HideOthers, cx: &mut gpui::AppContext| { - // cx.platform().hide_other_apps(); + // cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| { + // theme::adjust_font_size(cx, |size| *size -= 1.0) // }); - // cx.add_global_action(|_: &ShowAll, cx: &mut gpui::AppContext| { - // cx.platform().unhide_other_apps(); + // cx.add_global_action(move |_: &ResetBufferFontSize, cx| theme::reset_font_size(cx)); + .register_action(|_, _: &install_cli::Install, cx| { + cx.spawn(|_, cx| async move { + install_cli::install_cli(cx.deref()) + .await + .context("error creating CLI symlink") + }) + .detach_and_log_err(cx); + }) + .register_action(|workspace, _: &OpenLog, cx| { + open_log_file(workspace, cx); + }) + .register_action(|workspace, _: &OpenLicenses, cx| { + open_bundled_file( + workspace, + asset_str::("licenses.md"), + "Open Source License Attribution", + "Markdown", + cx, + ); + }) + .register_action( + move |workspace: &mut Workspace, + _: &OpenTelemetryLog, + cx: &mut ViewContext| { + open_telemetry_log_file(workspace, cx); + }, + ) + .register_action( + move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext| { + create_and_open_local_file(&paths::KEYMAP, cx, Default::default) + .detach_and_log_err(cx); + }, + ) + .register_action( + move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext| { + create_and_open_local_file(&paths::SETTINGS, cx, || { + settings::initial_user_settings_content().as_ref().into() + }) + .detach_and_log_err(cx); + }, + ) + .register_action(open_local_settings_file) + .register_action( + move |workspace: &mut Workspace, + _: &OpenDefaultKeymap, + cx: &mut ViewContext| { + open_bundled_file( + workspace, + settings::default_keymap(), + "Default Key Bindings", + "JSON", + cx, + ); + }, + ) + .register_action( + move |workspace: &mut Workspace, + _: &OpenDefaultSettings, + cx: &mut ViewContext| { + open_bundled_file( + workspace, + settings::default_settings(), + "Default Settings", + "JSON", + cx, + ); + }, + ) + //todo!() + // cx.add_action({ + // move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext| { + // let app_state = workspace.app_state().clone(); + // let markdown = app_state.languages.language_for_name("JSON"); + // let window = cx.window(); + // cx.spawn(|workspace, mut cx| async move { + // let markdown = markdown.await.log_err(); + // let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| { + // anyhow!("could not debug elements for window {}", window.id()) + // })?) + // .unwrap(); + // workspace + // .update(&mut cx, |workspace, cx| { + // workspace.with_local_workspace(cx, move |workspace, cx| { + // let project = workspace.project().clone(); + // let buffer = project + // .update(cx, |project, cx| { + // project.create_buffer(&content, markdown, cx) + // }) + // .expect("creating buffers on a local workspace always succeeds"); + // let buffer = cx.add_model(|cx| { + // MultiBuffer::singleton(buffer, cx) + // .with_title("Debug Elements".into()) + // }); + // workspace.add_item( + // Box::new(cx.add_view(|cx| { + // Editor::for_multibuffer(buffer, Some(project.clone()), cx) + // })), + // cx, + // ); + // }) + // })? + // .await + // }) + // .detach_and_log_err(cx); + // } // }); - // cx.add_action( - // |_: &mut Workspace, _: &Minimize, cx: &mut ViewContext| { - // cx.minimize_window(); + // .register_action( + // |workspace: &mut Workspace, + // _: &project_panel::ToggleFocus, + // cx: &mut ViewContext| { + // workspace.toggle_panel_focus::(cx); // }, // ); // cx.add_action( - // |_: &mut Workspace, _: &Zoom, cx: &mut ViewContext| { - // cx.zoom_window(); + // |workspace: &mut Workspace, + // _: &collab_ui::collab_panel::ToggleFocus, + // cx: &mut ViewContext| { + // workspace.toggle_panel_focus::(cx); // }, // ); // cx.add_action( - // |_: &mut Workspace, _: &ToggleFullScreen, cx: &mut ViewContext| { - // cx.toggle_full_screen(); + // |workspace: &mut Workspace, + // _: &collab_ui::chat_panel::ToggleFocus, + // cx: &mut ViewContext| { + // workspace.toggle_panel_focus::(cx); // }, // ); - .register_action(|workspace, _: &zed_actions::Quit, cx| quit(cx)); - // cx.add_global_action(move |action: &OpenZedURL, cx| { - // cx.global::>() - // .open_urls(vec![action.url.clone()]) - // }); - // cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url)); - // cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| { - // theme::adjust_font_size(cx, |size| *size += 1.0) - // }); - // cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| { - // theme::adjust_font_size(cx, |size| *size -= 1.0) - // }); - // cx.add_global_action(move |_: &ResetBufferFontSize, cx| theme::reset_font_size(cx)); - // cx.add_global_action(move |_: &install_cli::Install, cx| { - // cx.spawn(|cx| async move { - // install_cli::install_cli(&cx) - // .await - // .context("error creating CLI symlink") - // }) - // .detach_and_log_err(cx); - // }); - // cx.add_action( - // move |workspace: &mut Workspace, _: &OpenLog, cx: &mut ViewContext| { - // open_log_file(workspace, cx); - // }, - // ); - // cx.add_action( - // move |workspace: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext| { - // open_bundled_file( - // workspace, - // asset_str::("licenses.md"), - // "Open Source License Attribution", - // "Markdown", - // cx, - // ); - // }, - // ); - // cx.add_action( - // move |workspace: &mut Workspace, _: &OpenTelemetryLog, cx: &mut ViewContext| { - // open_telemetry_log_file(workspace, cx); - // }, - // ); - // cx.add_action( - // move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext| { - // create_and_open_local_file(&paths::KEYMAP, cx, Default::default).detach_and_log_err(cx); - // }, - // ); - // cx.add_action( - // move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext| { - // create_and_open_local_file(&paths::SETTINGS, cx, || { - // settings::initial_user_settings_content().as_ref().into() - // }) - // .detach_and_log_err(cx); - // }, - // ); - // cx.add_action(open_local_settings_file); - // cx.add_action( - // move |workspace: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext| { - // open_bundled_file( - // workspace, - // settings::default_keymap(), - // "Default Key Bindings", - // "JSON", - // cx, - // ); - // }, - // ); - // cx.add_action( - // move |workspace: &mut Workspace, - // _: &OpenDefaultSettings, - // cx: &mut ViewContext| { - // open_bundled_file( - // workspace, - // settings::default_settings(), - // "Default Settings", - // "JSON", - // cx, - // ); - // }, - // ); - // cx.add_action({ - // move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext| { - // let app_state = workspace.app_state().clone(); - // let markdown = app_state.languages.language_for_name("JSON"); - // let window = cx.window(); - // cx.spawn(|workspace, mut cx| async move { - // let markdown = markdown.await.log_err(); - // let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| { - // anyhow!("could not debug elements for window {}", window.id()) - // })?) - // .unwrap(); - // workspace - // .update(&mut cx, |workspace, cx| { - // workspace.with_local_workspace(cx, move |workspace, cx| { - // let project = workspace.project().clone(); - - // let buffer = project - // .update(cx, |project, cx| { - // project.create_buffer(&content, markdown, cx) - // }) - // .expect("creating buffers on a local workspace always succeeds"); - // let buffer = cx.add_model(|cx| { - // MultiBuffer::singleton(buffer, cx) - // .with_title("Debug Elements".into()) - // }); - // workspace.add_item( - // Box::new(cx.add_view(|cx| { - // Editor::for_multibuffer(buffer, Some(project.clone()), cx) - // })), - // cx, - // ); - // }) - // })? - // .await - // }) - // .detach_and_log_err(cx); - // } - // }); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &project_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &collab_ui::collab_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &collab_ui::chat_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &collab_ui::notification_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_action( - // |workspace: &mut Workspace, - // _: &terminal_panel::ToggleFocus, - // cx: &mut ViewContext| { - // workspace.toggle_panel_focus::(cx); - // }, - // ); - // cx.add_global_action({ - // let app_state = Arc::downgrade(&app_state); - // move |_: &NewWindow, cx: &mut AppContext| { - // if let Some(app_state) = app_state.upgrade() { - // open_new(&app_state, cx, |workspace, cx| { - // Editor::new_file(workspace, &Default::default(), cx) - // }) - // .detach(); - // } - // } - // }); - // cx.add_global_action({ - // let app_state = Arc::downgrade(&app_state); - // move |_: &NewFile, cx: &mut AppContext| { - // if let Some(app_state) = app_state.upgrade() { - // open_new(&app_state, cx, |workspace, cx| { - // Editor::new_file(workspace, &Default::default(), cx) - // }) - // .detach(); - // } - // } - // }); + // cx.add_action( + // |workspace: &mut Workspace, + // _: &collab_ui::notification_panel::ToggleFocus, + // cx: &mut ViewContext| { + // workspace.toggle_panel_focus::(cx); + // }, + // ); + // cx.add_action( + // |workspace: &mut Workspace, + // _: &terminal_panel::ToggleFocus, + // cx: &mut ViewContext| { + // workspace.toggle_panel_focus::(cx); + // }, + // ); + .register_action({ + let app_state = Arc::downgrade(&app_state); + move |_, _: &NewWindow, cx| { + if let Some(app_state) = app_state.upgrade() { + open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + } + } + }) + .register_action({ + let app_state = Arc::downgrade(&app_state); + move |_, _: &NewFile, cx| { + if let Some(app_state) = app_state.upgrade() { + open_new(&app_state, cx, |workspace, cx| { + Editor::new_file(workspace, &Default::default(), cx) + }) + .detach(); + } + } + }); + //todo!() // load_default_keymap(cx); }) .detach(); @@ -415,46 +448,58 @@ pub fn initialize_workspace( }) } -fn quit(cx: &mut gpui::AppContext) { - let should_confirm = workspace::WorkspaceSettings::get_global(cx).confirm_quit; - cx.spawn(|mut cx| async move { - // let mut workspace_windows = cx - // .windows() - // .into_iter() - // .filter_map(|window| window.downcast::()) - // .collect::>(); +fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext) { + let app_name = cx.global::().display_name(); + let version = env!("CARGO_PKG_VERSION"); + let prompt = cx.prompt(PromptLevel::Info, &format!("{app_name} {version}"), &["OK"]); + cx.foreground_executor() + .spawn(async { + prompt.await.ok(); + }) + .detach(); +} - // // If multiple windows have unsaved changes, and need a save prompt, - // // prompt in the active window before switching to a different window. - // workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false)); +fn quit(_: &mut Workspace, _: &Quit, cx: &mut gpui::ViewContext) { + let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit; + cx.spawn(|_, mut cx| async move { + let mut workspace_windows = cx.update(|_, cx| { + cx.windows() + .into_iter() + .filter_map(|window| window.downcast::()) + .collect::>() + })?; - // if let (true, Some(window)) = (should_confirm, workspace_windows.first().copied()) { - // let answer = window.prompt( - // PromptLevel::Info, - // "Are you sure you want to quit?", - // &["Quit", "Cancel"], - // &mut cx, - // ); + // // If multiple windows have unsaved changes, and need a save prompt, + // // prompt in the active window before switching to a different window. + // workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false)); - // if let Some(mut answer) = answer { - // let answer = answer.next().await; - // if answer != Some(0) { - // return Ok(()); - // } + // if let (true, Some(window)) = (should_confirm, workspace_windows.first().copied()) { + // let answer = window.prompt( + // PromptLevel::Info, + // "Are you sure you want to quit?", + // &["Quit", "Cancel"], + // &mut cx, + // ); + + // if let Some(mut answer) = answer { + // let answer = answer.next().await; + // if answer != Some(0) { + // return Ok(()); // } // } + // } - // // If the user cancels any save prompt, then keep the app open. - // for window in workspace_windows { - // if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| { - // workspace.prepare_to_close(true, cx) - // }) { - // if !should_close.await? { - // return Ok(()); - // } + // // If the user cancels any save prompt, then keep the app open. + // for window in workspace_windows { + // if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| { + // workspace.prepare_to_close(true, cx) + // }) { + // if !should_close.await? { + // return Ok(()); // } // } - cx.update(|cx| { + // } + cx.update(|_, cx| { cx.quit(); })?; @@ -462,3 +507,211 @@ fn quit(cx: &mut gpui::AppContext) { }) .detach_and_log_err(cx); } + +fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { + const MAX_LINES: usize = 1000; + workspace + .with_local_workspace(cx, move |workspace, cx| { + let fs = workspace.app_state().fs.clone(); + cx.spawn(|workspace, mut cx| async move { + let (old_log, new_log) = + futures::join!(fs.load(&paths::OLD_LOG), fs.load(&paths::LOG)); + + let mut lines = VecDeque::with_capacity(MAX_LINES); + for line in old_log + .iter() + .flat_map(|log| log.lines()) + .chain(new_log.iter().flat_map(|log| log.lines())) + { + if lines.len() == MAX_LINES { + lines.pop_front(); + } + lines.push_back(line); + } + let log = lines + .into_iter() + .flat_map(|line| [line, "\n"]) + .collect::(); + + workspace + .update(&mut cx, |workspace, cx| { + let project = workspace.project().clone(); + let buffer = project + .update(cx, |project, cx| project.create_buffer("", None, cx)) + .expect("creating buffers on a local workspace always succeeds"); + buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx)); + + let buffer = cx.build_model(|cx| { + MultiBuffer::singleton(buffer, cx).with_title("Log".into()) + }); + workspace.add_item( + Box::new(cx.build_view(|cx| { + Editor::for_multibuffer(buffer, Some(project), cx) + })), + cx, + ); + }) + .log_err(); + }) + .detach(); + }) + .detach(); +} + +fn open_local_settings_file( + workspace: &mut Workspace, + _: &OpenLocalSettings, + cx: &mut ViewContext, +) { + let project = workspace.project().clone(); + let worktree = project + .read(cx) + .visible_worktrees(cx) + .find_map(|tree| tree.read(cx).root_entry()?.is_dir().then_some(tree)); + if let Some(worktree) = worktree { + let tree_id = worktree.read(cx).id(); + cx.spawn(|workspace, mut cx| async move { + let file_path = &*LOCAL_SETTINGS_RELATIVE_PATH; + + if let Some(dir_path) = file_path.parent() { + if worktree.update(&mut cx, |tree, _| tree.entry_for_path(dir_path).is_none())? { + project + .update(&mut cx, |project, cx| { + project.create_entry((tree_id, dir_path), true, cx) + })? + .ok_or_else(|| anyhow!("worktree was removed"))? + .await?; + } + } + + if worktree.update(&mut cx, |tree, _| tree.entry_for_path(file_path).is_none())? { + project + .update(&mut cx, |project, cx| { + project.create_entry((tree_id, file_path), false, cx) + })? + .ok_or_else(|| anyhow!("worktree was removed"))? + .await?; + } + + let editor = workspace + .update(&mut cx, |workspace, cx| { + workspace.open_path((tree_id, file_path), None, true, cx) + })? + .await? + .downcast::() + .ok_or_else(|| anyhow!("unexpected item type"))?; + + editor + .downgrade() + .update(&mut cx, |editor, cx| { + if let Some(buffer) = editor.buffer().read(cx).as_singleton() { + if buffer.read(cx).is_empty() { + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, initial_local_settings_content())], None, cx) + }); + } + } + }) + .ok(); + + anyhow::Ok(()) + }) + .detach(); + } else { + workspace.show_notification(0, cx, |cx| { + cx.build_view(|_| MessageNotification::new("This project has no folders open.")) + }) + } +} + +fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext) { + workspace.with_local_workspace(cx, move |workspace, cx| { + let app_state = workspace.app_state().clone(); + cx.spawn(|workspace, mut cx| async move { + async fn fetch_log_string(app_state: &Arc) -> Option { + let path = app_state.client.telemetry().log_file_path()?; + app_state.fs.load(&path).await.log_err() + } + + let log = fetch_log_string(&app_state).await.unwrap_or_else(|| "// No data has been collected yet".to_string()); + + const MAX_TELEMETRY_LOG_LEN: usize = 5 * 1024 * 1024; + let mut start_offset = log.len().saturating_sub(MAX_TELEMETRY_LOG_LEN); + if let Some(newline_offset) = log[start_offset..].find('\n') { + start_offset += newline_offset + 1; + } + let log_suffix = &log[start_offset..]; + let json = app_state.languages.language_for_name("JSON").await.log_err(); + + workspace.update(&mut cx, |workspace, cx| { + let project = workspace.project().clone(); + let buffer = project + .update(cx, |project, cx| project.create_buffer("", None, cx)) + .expect("creating buffers on a local workspace always succeeds"); + buffer.update(cx, |buffer, cx| { + buffer.set_language(json, cx); + buffer.edit( + [( + 0..0, + concat!( + "// Zed collects anonymous usage data to help us understand how people are using the app.\n", + "// Telemetry can be disabled via the `settings.json` file.\n", + "// Here is the data that has been reported for the current session:\n", + "\n" + ), + )], + None, + cx, + ); + buffer.edit([(buffer.len()..buffer.len(), log_suffix)], None, cx); + }); + + let buffer = cx.build_model(|cx| { + MultiBuffer::singleton(buffer, cx).with_title("Telemetry Log".into()) + }); + workspace.add_item( + Box::new(cx.build_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))), + cx, + ); + }).log_err()?; + + Some(()) + }) + .detach(); + }).detach(); +} + +fn open_bundled_file( + workspace: &mut Workspace, + text: Cow<'static, str>, + title: &'static str, + language: &'static str, + cx: &mut ViewContext, +) { + let language = workspace.app_state().languages.language_for_name(language); + cx.spawn(|workspace, mut cx| async move { + let language = language.await.log_err(); + workspace + .update(&mut cx, |workspace, cx| { + workspace.with_local_workspace(cx, |workspace, cx| { + let project = workspace.project(); + let buffer = project.update(cx, move |project, cx| { + project + .create_buffer(text.as_ref(), language, cx) + .expect("creating buffers on a local workspace always succeeds") + }); + let buffer = cx.build_model(|cx| { + MultiBuffer::singleton(buffer, cx).with_title(title.into()) + }); + workspace.add_item( + Box::new(cx.build_view(|cx| { + Editor::for_multibuffer(buffer, Some(project.clone()), cx) + })), + cx, + ); + }) + })? + .await + }) + .detach_and_log_err(cx); +} diff --git a/crates/zed_actions2/src/lib.rs b/crates/zed_actions2/src/lib.rs index 097766492f..7f0c19853e 100644 --- a/crates/zed_actions2/src/lib.rs +++ b/crates/zed_actions2/src/lib.rs @@ -1,4 +1,4 @@ -use gpui::{action, actions}; +use gpui::action; // If the zed binary doesn't use anything in this crate, it will be optimized away // and the actions won't initialize. So we just provide an empty initialization function @@ -9,34 +9,11 @@ use gpui::{action, actions}; // https://github.com/mmastrac/rust-ctor/issues/280 pub fn init() {} -actions!( - About, - DebugElements, - DecreaseBufferFontSize, - Hide, - HideOthers, - IncreaseBufferFontSize, - Minimize, - OpenDefaultKeymap, - OpenDefaultSettings, - OpenKeymap, - OpenLicenses, - OpenLocalSettings, - OpenLog, - OpenSettings, - OpenTelemetryLog, - Quit, - ResetBufferFontSize, - ResetDatabase, - ShowAll, - ToggleFullScreen, - Zoom, -); - #[action] pub struct OpenBrowser { pub url: String, } + #[action] pub struct OpenZedURL { pub url: String,