diff --git a/Cargo.lock b/Cargo.lock index 403dffadfc..ec3399f791 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5074,6 +5074,7 @@ dependencies = [ "settings", "smol", "text", + "util", "workspace", ] diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 727b9d2d4f..5b3b066ea8 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -225,8 +225,14 @@ impl Copilot { let server_path = get_copilot_lsp(http).await?; let node_path = node_runtime.binary_path().await?; let arguments: &[OsString] = &[server_path.into(), "--stdio".into()]; - let server = - LanguageServer::new(0, &node_path, arguments, Path::new("/"), cx.clone())?; + let server = LanguageServer::new( + 0, + &node_path, + arguments, + Path::new("/"), + None, + cx.clone(), + )?; let server = server.initialize(Default::default()).await?; let status = server diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 52290b773b..2530186bce 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -48,7 +48,6 @@ impl TabMap { new_snapshot.version += 1; } - let old_max_offset = old_snapshot.suggestion_snapshot.len(); let mut tab_edits = Vec::with_capacity(suggestion_edits.len()); if old_snapshot.tab_size == new_snapshot.tab_size { @@ -56,50 +55,47 @@ impl TabMap { // and any subsequent tabs on that line that moved across the tab expansion // boundary. for suggestion_edit in &mut suggestion_edits { - let old_end_column = old_snapshot + let old_end = old_snapshot .suggestion_snapshot - .to_point(suggestion_edit.old.end) - .column(); - let new_end_column = new_snapshot + .to_point(suggestion_edit.old.end); + let old_end_row_successor_offset = + old_snapshot.suggestion_snapshot.to_offset(cmp::min( + SuggestionPoint::new(old_end.row() + 1, 0), + old_snapshot.suggestion_snapshot.max_point(), + )); + let new_end = new_snapshot .suggestion_snapshot - .to_point(suggestion_edit.new.end) - .column(); + .to_point(suggestion_edit.new.end); let mut offset_from_edit = 0; let mut first_tab_offset = None; let mut last_tab_with_changed_expansion_offset = None; 'outer: for chunk in old_snapshot.suggestion_snapshot.chunks( - suggestion_edit.old.end..old_max_offset, + suggestion_edit.old.end..old_end_row_successor_offset, false, None, None, ) { - for (ix, mat) in chunk.text.match_indices(&['\t', '\n']) { + for (ix, _) in chunk.text.match_indices('\t') { let offset_from_edit = offset_from_edit + (ix as u32); - match mat { - "\t" => { - if first_tab_offset.is_none() { - first_tab_offset = Some(offset_from_edit); - } + if first_tab_offset.is_none() { + first_tab_offset = Some(offset_from_edit); + } - let old_column = old_end_column + offset_from_edit; - let new_column = new_end_column + offset_from_edit; - let was_expanded = old_column < old_snapshot.max_expansion_column; - let is_expanded = new_column < new_snapshot.max_expansion_column; - if was_expanded != is_expanded { - last_tab_with_changed_expansion_offset = Some(offset_from_edit); - } else if !was_expanded && !is_expanded { - break 'outer; - } - } - "\n" => break 'outer, - _ => unreachable!(), + let old_column = old_end.column() + offset_from_edit; + let new_column = new_end.column() + offset_from_edit; + let was_expanded = old_column < old_snapshot.max_expansion_column; + let is_expanded = new_column < new_snapshot.max_expansion_column; + if was_expanded != is_expanded { + last_tab_with_changed_expansion_offset = Some(offset_from_edit); + } else if !was_expanded && !is_expanded { + break 'outer; } } offset_from_edit += chunk.text.len() as u32; - if old_end_column + offset_from_edit >= old_snapshot.max_expansion_column - && new_end_column | offset_from_edit >= new_snapshot.max_expansion_column + if old_end.column() + offset_from_edit >= old_snapshot.max_expansion_column + && new_end.column() + offset_from_edit >= new_snapshot.max_expansion_column { break; } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index a744018e1f..f48e45073d 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -254,6 +254,19 @@ impl App { self } + /// Handle the application being re-activated when no windows are open. + pub fn on_reopen(&mut self, mut callback: F) -> &mut Self + where + F: 'static + FnMut(&mut MutableAppContext), + { + let cx = self.0.clone(); + self.0 + .borrow_mut() + .foreground_platform + .on_reopen(Box::new(move || callback(&mut *cx.borrow_mut()))); + self + } + pub fn on_event(&mut self, mut callback: F) -> &mut Self where F: 'static + FnMut(Event, &mut MutableAppContext) -> bool, @@ -276,9 +289,7 @@ impl App { self.0 .borrow_mut() .foreground_platform - .on_open_urls(Box::new(move |paths| { - callback(paths, &mut *cx.borrow_mut()) - })); + .on_open_urls(Box::new(move |urls| callback(urls, &mut *cx.borrow_mut()))); self } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 538b46ee77..4c0c319745 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -90,6 +90,10 @@ pub(crate) trait ForegroundPlatform { fn on_become_active(&self, callback: Box); fn on_resign_active(&self, callback: Box); fn on_quit(&self, callback: Box); + + /// Handle the application being re-activated with no windows open. + fn on_reopen(&self, callback: Box); + fn on_event(&self, callback: Box bool>); fn on_open_urls(&self, callback: Box)>); fn run(&self, on_finish_launching: Box); diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index ab4fd873c6..7e901e8a5e 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -82,6 +82,10 @@ unsafe fn build_classes() { sel!(applicationDidFinishLaunching:), did_finish_launching as extern "C" fn(&mut Object, Sel, id), ); + decl.add_method( + sel!(applicationShouldHandleReopen:hasVisibleWindows:), + should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool), + ); decl.add_method( sel!(applicationDidBecomeActive:), did_become_active as extern "C" fn(&mut Object, Sel, id), @@ -144,6 +148,7 @@ pub struct MacForegroundPlatform(RefCell); pub struct MacForegroundPlatformState { become_active: Option>, resign_active: Option>, + reopen: Option>, quit: Option>, event: Option bool>>, menu_command: Option>, @@ -158,15 +163,16 @@ pub struct MacForegroundPlatformState { impl MacForegroundPlatform { pub fn new(foreground: Rc) -> Self { Self(RefCell::new(MacForegroundPlatformState { - become_active: Default::default(), - resign_active: Default::default(), - quit: Default::default(), - event: Default::default(), - menu_command: Default::default(), - validate_menu_command: Default::default(), - will_open_menu: Default::default(), - open_urls: Default::default(), - finish_launching: Default::default(), + become_active: None, + resign_active: None, + reopen: None, + quit: None, + event: None, + menu_command: None, + validate_menu_command: None, + will_open_menu: None, + open_urls: None, + finish_launching: None, menu_actions: Default::default(), foreground, })) @@ -332,6 +338,10 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { self.0.borrow_mut().quit = Some(callback); } + fn on_reopen(&self, callback: Box) { + self.0.borrow_mut().reopen = Some(callback); + } + fn on_event(&self, callback: Box bool>) { self.0.borrow_mut().event = Some(callback); } @@ -943,6 +953,15 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { } } +extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) { + if !has_open_windows { + let platform = unsafe { get_foreground_platform(this) }; + if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() { + callback(); + } + } +} + extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) { let platform = unsafe { get_foreground_platform(this) }; if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() { diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index a3532dd96e..b6b2fe5217 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -61,13 +61,10 @@ impl ForegroundPlatform { impl super::ForegroundPlatform for ForegroundPlatform { fn on_become_active(&self, _: Box) {} - fn on_resign_active(&self, _: Box) {} - fn on_quit(&self, _: Box) {} - + fn on_reopen(&self, _: Box) {} fn on_event(&self, _: Box bool>) {} - fn on_open_urls(&self, _: Box)>) {} fn run(&self, _on_finish_launching: Box) { diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 2246bf44f0..beda6aa557 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -19,6 +19,7 @@ use futures::{ use gpui::{executor::Background, MutableAppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; +use lsp::CodeActionKind; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; @@ -140,6 +141,10 @@ impl CachedLspAdapter { self.adapter.cached_server_binary(container_dir).await } + pub fn code_action_kinds(&self) -> Option> { + self.adapter.code_action_kinds() + } + pub fn workspace_configuration( &self, cx: &mut MutableAppContext, @@ -225,6 +230,16 @@ pub trait LspAdapter: 'static + Send + Sync { None } + fn code_action_kinds(&self) -> Option> { + Some(vec![ + CodeActionKind::EMPTY, + CodeActionKind::QUICKFIX, + CodeActionKind::REFACTOR, + CodeActionKind::REFACTOR_EXTRACT, + CodeActionKind::SOURCE, + ]) + } + async fn disk_based_diagnostic_sources(&self) -> Vec { Default::default() } @@ -825,6 +840,7 @@ impl LanguageRegistry { &binary.path, &binary.arguments, &root_path, + adapter.code_action_kinds(), cx, )?; diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 80bf0a70f6..514648cfa3 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -40,6 +40,7 @@ pub struct LanguageServer { outbound_tx: channel::Sender>, name: String, capabilities: ServerCapabilities, + code_action_kinds: Option>, notification_handlers: Arc>>, response_handlers: Arc>>>, executor: Arc, @@ -110,6 +111,7 @@ impl LanguageServer { binary_path: &Path, arguments: &[T], root_path: &Path, + code_action_kinds: Option>, cx: AsyncAppContext, ) -> Result { let working_dir = if root_path.is_dir() { @@ -135,6 +137,7 @@ impl LanguageServer { stout, Some(server), root_path, + code_action_kinds, cx, |notification| { log::info!( @@ -160,6 +163,7 @@ impl LanguageServer { stdout: Stdout, server: Option, root_path: &Path, + code_action_kinds: Option>, cx: AsyncAppContext, on_unhandled_notification: F, ) -> Self @@ -197,6 +201,7 @@ impl LanguageServer { response_handlers, name: Default::default(), capabilities: Default::default(), + code_action_kinds, next_id: Default::default(), outbound_tx, executor: cx.background(), @@ -207,6 +212,10 @@ impl LanguageServer { } } + pub fn code_action_kinds(&self) -> Option> { + self.code_action_kinds.clone() + } + async fn handle_input( stdout: Stdout, mut on_unhandled_notification: F, @@ -715,6 +724,7 @@ impl LanguageServer { stdout_reader, None, Path::new("/"), + None, cx.clone(), |_| {}, ); @@ -725,6 +735,7 @@ impl LanguageServer { stdin_reader, None, Path::new("/"), + None, cx, move |msg| { notifications_tx diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8e3fc77aa8..4d6f42cd0e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3773,7 +3773,7 @@ impl Project { worktree = file.worktree.clone(); buffer_abs_path = file.as_local().map(|f| f.abs_path(cx)); } else { - return Task::ready(Ok(Default::default())); + return Task::ready(Ok(Vec::new())); }; let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end); @@ -3783,13 +3783,13 @@ impl Project { { server.clone() } else { - return Task::ready(Ok(Default::default())); + return Task::ready(Ok(Vec::new())); }; let lsp_range = range_to_lsp(range.to_point_utf16(buffer)); cx.foreground().spawn(async move { if lang_server.capabilities().code_action_provider.is_none() { - return Ok(Default::default()); + return Ok(Vec::new()); } Ok(lang_server @@ -3802,13 +3802,7 @@ impl Project { partial_result_params: Default::default(), context: lsp::CodeActionContext { diagnostics: relevant_diagnostics, - only: Some(vec![ - lsp::CodeActionKind::EMPTY, - lsp::CodeActionKind::QUICKFIX, - lsp::CodeActionKind::REFACTOR, - lsp::CodeActionKind::REFACTOR_EXTRACT, - lsp::CodeActionKind::SOURCE, - ]), + only: lang_server.code_action_kinds(), }, }) .await? diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index e8d851fa08..c056b68852 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -21,3 +21,4 @@ workspace = { path = "../workspace" } ordered-float = "2.1.1" postage = { workspace = true } smol = "1.2" +util = { path = "../util"} diff --git a/crates/recent_projects/src/highlighted_workspace_location.rs b/crates/recent_projects/src/highlighted_workspace_location.rs index 8e75b291a0..a71a7dd2d7 100644 --- a/crates/recent_projects/src/highlighted_workspace_location.rs +++ b/crates/recent_projects/src/highlighted_workspace_location.rs @@ -61,6 +61,7 @@ impl HighlightedWorkspaceLocation { .paths() .iter() .map(|path| { + let path = util::paths::compact(&path); let highlighted_text = Self::highlights_for_path( path.as_ref(), &string_match.positions, diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 0e3a8d96be..38124dcc11 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -6,7 +6,7 @@ publish = false [lib] path = "src/util.rs" -doctest = false +doctest = true [features] test-support = ["tempdir", "git2"] diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index e38f76d8a6..a324b21a31 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; lazy_static::lazy_static! { pub static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory"); @@ -24,3 +24,49 @@ pub mod legacy { pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json"); } } + +/// Compacts a given file path by replacing the user's home directory +/// prefix with a tilde (`~`). +/// +/// # Arguments +/// +/// * `path` - A reference to a `Path` representing the file path to compact. +/// +/// # Examples +/// +/// ``` +/// use std::path::{Path, PathBuf}; +/// use util::paths::compact; +/// let path: PathBuf = [ +/// util::paths::HOME.to_string_lossy().to_string(), +/// "some_file.txt".to_string(), +/// ] +/// .iter() +/// .collect(); +/// if cfg!(target_os = "linux") || cfg!(target_os = "macos") { +/// assert_eq!(compact(&path).to_str(), Some("~/some_file.txt")); +/// } else { +/// assert_eq!(compact(&path).to_str(), path.to_str()); +/// } +/// ``` +/// +/// # Returns +/// +/// * A `PathBuf` containing the compacted file path. If the input path +/// does not have the user's home directory prefix, or if we are not on +/// Linux or macOS, the original path is returned unchanged. +pub fn compact(path: &Path) -> PathBuf { + if cfg!(target_os = "linux") || cfg!(target_os = "macos") { + match path.strip_prefix(HOME.as_path()) { + Ok(relative_path) => { + let mut shortened_path = PathBuf::new(); + shortened_path.push("~"); + shortened_path.push(relative_path); + shortened_path + } + Err(_) => path.to_path_buf(), + } + } else { + path.to_path_buf() + } +} diff --git a/crates/util/src/test/marked_text.rs b/crates/util/src/test/marked_text.rs index c2aaca3831..dc91e7b5b6 100644 --- a/crates/util/src/test/marked_text.rs +++ b/crates/util/src/test/marked_text.rs @@ -87,21 +87,21 @@ pub fn marked_text_ranges_by( /// 1. To mark a range of text, surround it with the `«` and `»` angle brackets, /// which can be typed on a US keyboard with the `alt-|` and `alt-shift-|` keys. /// -/// ``` +/// ```text /// foo «selected text» bar /// ``` /// /// 2. To mark a single position in the text, use the `ˇ` caron, /// which can be typed on a US keyboard with the `alt-shift-t` key. /// -/// ``` +/// ```text /// the cursors are hereˇ and hereˇ. /// ``` /// /// 3. To mark a range whose direction is meaningful (like a selection), /// put a caron character beside one of its bounds, on the inside: /// -/// ``` +/// ```text /// one «ˇreversed» selection and one «forwardˇ» selection /// ``` pub fn marked_text_ranges( diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 69a135e6ec..013958704b 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -3,6 +3,7 @@ use async_trait::async_trait; use futures::StreamExt; use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; use node_runtime::NodeRuntime; +use lsp::CodeActionKind; use serde_json::json; use smol::fs; use std::{ @@ -134,6 +135,15 @@ impl LspAdapter for TypeScriptLspAdapter { .log_err() } + fn code_action_kinds(&self) -> Option> { + Some(vec![ + CodeActionKind::QUICKFIX, + CodeActionKind::REFACTOR, + CodeActionKind::REFACTOR_EXTRACT, + CodeActionKind::SOURCE, + ]) + } + async fn label_for_completion( &self, item: &lsp::CompletionItem, diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 8f7b858dfd..19cf2d505e 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -103,7 +103,8 @@ fn main() { .map_err(|_| anyhow!("no listener for open urls requests")) .log_err(); } - }); + }) + .on_reopen(move |cx| cx.dispatch_global_action(NewFile)); app.run(move |cx| { cx.set_global(*RELEASE_CHANNEL);