From 87c1b190a8c38d1e6aa648160bae67225e8c7f44 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Tue, 28 Mar 2023 17:21:59 +0300 Subject: [PATCH 01/14] Replace home directory with the tilde substitution --- Cargo.lock | 1 + crates/recent_projects/Cargo.toml | 1 + .../src/highlighted_workspace_location.rs | 12 ++++++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02b27566e4..e24d7a9e60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5014,6 +5014,7 @@ dependencies = [ "settings", "smol", "text", + "util", "workspace", ] 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..b414b84f4a 100644 --- a/crates/recent_projects/src/highlighted_workspace_location.rs +++ b/crates/recent_projects/src/highlighted_workspace_location.rs @@ -1,4 +1,4 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use fuzzy::StringMatch; use gpui::{ @@ -61,8 +61,16 @@ impl HighlightedWorkspaceLocation { .paths() .iter() .map(|path| { + let mut full_path = PathBuf::new(); + if path.starts_with(util::paths::HOME.as_path()) { + full_path.push("~"); + full_path.push(path.strip_prefix(util::paths::HOME.as_path()).unwrap()); + } else { + full_path.push(path) + } + let highlighted_text = Self::highlights_for_path( - path.as_ref(), + full_path.as_ref(), &string_match.positions, path_start_offset, ); From a1284396992d1d11b62ad47c5d456026c8929b33 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Tue, 28 Mar 2023 19:23:52 +0300 Subject: [PATCH 02/14] Move code into the platform Co-Authored-By: Joseph T. Lyons <19867440+JosephTLyons@users.noreply.github.com> --- crates/gpui/src/platform.rs | 1 + crates/gpui/src/platform/mac/platform.rs | 31 +++++++++++++++++++ crates/gpui/src/platform/test.rs | 4 +++ .../src/highlighted_workspace_location.rs | 17 +++++----- crates/recent_projects/src/recent_projects.rs | 1 + 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 538b46ee77..482de94162 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -68,6 +68,7 @@ pub trait Platform: Send + Sync { fn write_to_clipboard(&self, item: ClipboardItem); fn read_from_clipboard(&self) -> Option; fn open_url(&self, url: &str); + fn convert_to_shortened_path(&self, path: &Path) -> PathBuf; fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>; fn read_credentials(&self, url: &str) -> Result)>>; diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index ab4fd873c6..d9ac7237ce 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -674,6 +674,18 @@ impl platform::Platform for MacPlatform { } } + fn convert_to_shortened_path(&self, path: &Path) -> PathBuf { + match path.strip_prefix(util::paths::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(), + } + } + fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> { let url = CFString::from(url); let username = CFString::from(username); @@ -1113,4 +1125,23 @@ mod tests { platform.pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) }; platform } + + #[test] + fn test_convert_to_shortened_path() { + let platform = build_platform(); + + let full_path: PathBuf = [ + util::paths::HOME.to_string_lossy().to_string(), + "a".to_string(), + "b".to_string(), + "c".to_string(), + ] + .iter() + .collect(); + + let shortened_path_actual = platform.convert_to_shortened_path(&full_path); + let shortened_path_expected = PathBuf::from("~/a/b/c"); + + assert_eq!(shortened_path_actual, shortened_path_expected); + } } diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index a3532dd96e..6b1cdf5e05 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -175,6 +175,10 @@ impl super::Platform for Platform { fn open_url(&self, _: &str) {} + fn convert_to_shortened_path(&self, _path: &Path) -> PathBuf { + PathBuf::new() + } + fn write_credentials(&self, _: &str, _: &str, _: &[u8]) -> Result<()> { Ok(()) } diff --git a/crates/recent_projects/src/highlighted_workspace_location.rs b/crates/recent_projects/src/highlighted_workspace_location.rs index b414b84f4a..bee7594d33 100644 --- a/crates/recent_projects/src/highlighted_workspace_location.rs +++ b/crates/recent_projects/src/highlighted_workspace_location.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path::Path; use fuzzy::StringMatch; use gpui::{ @@ -55,20 +55,17 @@ pub struct HighlightedWorkspaceLocation { } impl HighlightedWorkspaceLocation { - pub fn new(string_match: &StringMatch, location: &WorkspaceLocation) -> Self { + pub fn new( + string_match: &StringMatch, + location: &WorkspaceLocation, + cx: &gpui::AppContext, + ) -> Self { let mut path_start_offset = 0; let (names, paths): (Vec<_>, Vec<_>) = location .paths() .iter() .map(|path| { - let mut full_path = PathBuf::new(); - if path.starts_with(util::paths::HOME.as_path()) { - full_path.push("~"); - full_path.push(path.strip_prefix(util::paths::HOME.as_path()).unwrap()); - } else { - full_path.push(path) - } - + let full_path = cx.platform().convert_to_shortened_path(&path); let highlighted_text = Self::highlights_for_path( full_path.as_ref(), &string_match.positions, diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index e73d0b4fb5..81afd694d0 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -192,6 +192,7 @@ impl PickerDelegate for RecentProjectsView { let highlighted_location = HighlightedWorkspaceLocation::new( &string_match, &self.workspace_locations[string_match.candidate_id], + &cx, ); Flex::column() From d5f53111e849b0e2b0bc30de5bac5c3506fa76ef Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 29 Mar 2023 17:31:55 +0300 Subject: [PATCH 03/14] Enable doctests in util crate --- crates/util/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index b13b8af956..8a77cc358c 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"] From 69989d0463011f6101fbd32f837b46260f4e7f00 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 29 Mar 2023 17:32:27 +0300 Subject: [PATCH 04/14] Introduce compact function in util create --- crates/util/src/paths.rs | 54 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 63c3c6d884..b04c12a204 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"); @@ -23,3 +23,55 @@ 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. +/// +/// # Errors +/// +/// This function will not produce any errors, but in case the input path +/// cannot be stripped of the home directory prefix, the original path +/// will be 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() + } +} From b4593cd90b5584517a3102bfdd2336a4a5c4b3eb Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 29 Mar 2023 17:40:39 +0300 Subject: [PATCH 05/14] Use util::paths::compact --- .../recent_projects/src/highlighted_workspace_location.rs | 8 ++------ crates/recent_projects/src/recent_projects.rs | 1 - 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/recent_projects/src/highlighted_workspace_location.rs b/crates/recent_projects/src/highlighted_workspace_location.rs index bee7594d33..ca8b8b2828 100644 --- a/crates/recent_projects/src/highlighted_workspace_location.rs +++ b/crates/recent_projects/src/highlighted_workspace_location.rs @@ -55,17 +55,13 @@ pub struct HighlightedWorkspaceLocation { } impl HighlightedWorkspaceLocation { - pub fn new( - string_match: &StringMatch, - location: &WorkspaceLocation, - cx: &gpui::AppContext, - ) -> Self { + pub fn new(string_match: &StringMatch, location: &WorkspaceLocation) -> Self { let mut path_start_offset = 0; let (names, paths): (Vec<_>, Vec<_>) = location .paths() .iter() .map(|path| { - let full_path = cx.platform().convert_to_shortened_path(&path); + let full_path = util::paths::compact(&path); let highlighted_text = Self::highlights_for_path( full_path.as_ref(), &string_match.positions, diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 81afd694d0..e73d0b4fb5 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -192,7 +192,6 @@ impl PickerDelegate for RecentProjectsView { let highlighted_location = HighlightedWorkspaceLocation::new( &string_match, &self.workspace_locations[string_match.candidate_id], - &cx, ); Flex::column() From b15632bd453bd2f24e6589aa29b1d56e14aa4960 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Wed, 29 Mar 2023 17:41:01 +0300 Subject: [PATCH 06/14] Remove convert_to_shortened_path from gpui platform --- crates/gpui/src/platform.rs | 1 - crates/gpui/src/platform/mac/platform.rs | 31 ------------------------ crates/gpui/src/platform/test.rs | 4 --- 3 files changed, 36 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 482de94162..538b46ee77 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -68,7 +68,6 @@ pub trait Platform: Send + Sync { fn write_to_clipboard(&self, item: ClipboardItem); fn read_from_clipboard(&self) -> Option; fn open_url(&self, url: &str); - fn convert_to_shortened_path(&self, path: &Path) -> PathBuf; fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>; fn read_credentials(&self, url: &str) -> Result)>>; diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index d9ac7237ce..ab4fd873c6 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -674,18 +674,6 @@ impl platform::Platform for MacPlatform { } } - fn convert_to_shortened_path(&self, path: &Path) -> PathBuf { - match path.strip_prefix(util::paths::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(), - } - } - fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> { let url = CFString::from(url); let username = CFString::from(username); @@ -1125,23 +1113,4 @@ mod tests { platform.pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) }; platform } - - #[test] - fn test_convert_to_shortened_path() { - let platform = build_platform(); - - let full_path: PathBuf = [ - util::paths::HOME.to_string_lossy().to_string(), - "a".to_string(), - "b".to_string(), - "c".to_string(), - ] - .iter() - .collect(); - - let shortened_path_actual = platform.convert_to_shortened_path(&full_path); - let shortened_path_expected = PathBuf::from("~/a/b/c"); - - assert_eq!(shortened_path_actual, shortened_path_expected); - } } diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 6b1cdf5e05..a3532dd96e 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -175,10 +175,6 @@ impl super::Platform for Platform { fn open_url(&self, _: &str) {} - fn convert_to_shortened_path(&self, _path: &Path) -> PathBuf { - PathBuf::new() - } - fn write_credentials(&self, _: &str, _: &str, _: &[u8]) -> Result<()> { Ok(()) } From b5f762ab25f1e9ccf5438010181dcc5644ac2ead Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 29 Mar 2023 17:24:44 -0700 Subject: [PATCH 07/14] Open a new window when activating Zed from the dock w/ no windows open --- crates/gpui/src/app.rs | 17 +++++++++-- crates/gpui/src/platform.rs | 4 +++ crates/gpui/src/platform/mac/platform.rs | 37 ++++++++++++++++++------ crates/gpui/src/platform/test.rs | 5 +--- crates/zed/src/main.rs | 3 +- 5 files changed, 49 insertions(+), 17 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 0c8256fefb..755568431d 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/zed/src/main.rs b/crates/zed/src/main.rs index fb6c6227c3..99628da8d9 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -105,7 +105,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); From 79346b070637ef75721779ab199e069513341775 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 30 Mar 2023 09:49:07 +0200 Subject: [PATCH 08/14] Use `+` instead of `|` to break out of tab expansion in `TabMap::sync` --- crates/editor/src/display_map/tab_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index f5776270a0..8c136f4b18 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -97,7 +97,7 @@ impl TabMap { 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 + && new_end_column + offset_from_edit >= new_snapshot.max_expansion_column { break; } From af4c4c7cf032d41d437fce68e0bdfa42b070b3fc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 30 Mar 2023 10:03:13 +0200 Subject: [PATCH 09/14] Speed up tab searching in `TabMap::sync` by looking for `\t` only Instead of looking for `\n` as a stopping condition, we cap the range we pass to `SuggestionSnapshot::chunks` to stop on the next line. This makes character searching faster, because looking for a single character uses `memchr`. Also, this avoids an extra conditional in a tight loop such as the chunk scanning one contained in `TabMap::sync`. --- crates/editor/src/display_map/tab_map.rs | 52 +++++++++++------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 8c136f4b18..2299edf61e 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -47,7 +47,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 { @@ -55,49 +54,46 @@ 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, ) { - 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; } From 9ef3e45bcd55ba27287085a63c4247aad33841a1 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Thu, 30 Mar 2023 11:35:17 +0300 Subject: [PATCH 10/14] Update crates/recent_projects/src/highlighted_workspace_location.rs Co-authored-by: Antonio Scandurra --- crates/recent_projects/src/highlighted_workspace_location.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/recent_projects/src/highlighted_workspace_location.rs b/crates/recent_projects/src/highlighted_workspace_location.rs index ca8b8b2828..5c3505cccd 100644 --- a/crates/recent_projects/src/highlighted_workspace_location.rs +++ b/crates/recent_projects/src/highlighted_workspace_location.rs @@ -61,7 +61,7 @@ impl HighlightedWorkspaceLocation { .paths() .iter() .map(|path| { - let full_path = util::paths::compact(&path); + let path = util::paths::compact(&path); let highlighted_text = Self::highlights_for_path( full_path.as_ref(), &string_match.positions, From 18c6c7ebb7af44c4d42db928c2a4b44d9e0bd185 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Thu, 30 Mar 2023 14:03:59 +0300 Subject: [PATCH 11/14] Fix error --- crates/recent_projects/src/highlighted_workspace_location.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/recent_projects/src/highlighted_workspace_location.rs b/crates/recent_projects/src/highlighted_workspace_location.rs index 5c3505cccd..a71a7dd2d7 100644 --- a/crates/recent_projects/src/highlighted_workspace_location.rs +++ b/crates/recent_projects/src/highlighted_workspace_location.rs @@ -63,7 +63,7 @@ impl HighlightedWorkspaceLocation { .map(|path| { let path = util::paths::compact(&path); let highlighted_text = Self::highlights_for_path( - full_path.as_ref(), + path.as_ref(), &string_match.positions, path_start_offset, ); From adc5ef911fbd3b9a61feb79971f2161c075945fc Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Thu, 30 Mar 2023 14:04:37 +0300 Subject: [PATCH 12/14] Remove the Errors section from rust docs The section does not add anything that we don't already know. Co-Authored-By: Antonio Scandurra --- crates/util/src/paths.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index b04c12a204..3fccb0c896 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -54,12 +54,6 @@ pub mod legacy { /// * 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. -/// -/// # Errors -/// -/// This function will not produce any errors, but in case the input path -/// cannot be stripped of the home directory prefix, the original path -/// will be returned unchanged. pub fn compact(path: &Path) -> PathBuf { if cfg!(target_os = "linux") || cfg!(target_os = "macos") { match path.strip_prefix(HOME.as_path()) { From dc51735112d3649dc44372bb6b0edeceb3ab4f70 Mon Sep 17 00:00:00 2001 From: Petros Amoiridis Date: Thu, 30 Mar 2023 17:57:14 +0300 Subject: [PATCH 13/14] Fix doctests --- crates/util/src/test/marked_text.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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( From cdde523ea4e3088099b5a19f3966ea9924c335ef Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 30 Mar 2023 15:41:54 -0400 Subject: [PATCH 14/14] Allow each language adapter to provide their own code action kinds array --- crates/language/src/language.rs | 16 ++++++++++++++++ crates/lsp/src/lsp.rs | 11 +++++++++++ crates/project/src/project.rs | 14 ++++---------- crates/zed/src/languages/typescript.rs | 10 ++++++++++ 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 195a285d74..2a9ba95b33 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -20,6 +20,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 fedfa0c863..b49460bd4b 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/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index f9baf4f8f7..4fc2e0978c 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -4,6 +4,7 @@ use async_trait::async_trait; use client::http::HttpClient; use futures::StreamExt; use language::{LanguageServerBinary, LanguageServerName, LspAdapter}; +use lsp::CodeActionKind; use serde_json::json; use smol::fs; use std::{ @@ -142,6 +143,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,