From e2ba8d6df76925fbaafd2c1b260b7cabff865cc6 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Tue, 25 Oct 2022 17:24:19 -0700 Subject: [PATCH 01/67] Add active pane magnification setting which grows the active pane making it easier to see it's contents --- assets/settings/default.json | 445 +++++++++++++++-------------- crates/settings/src/settings.rs | 9 + crates/workspace/src/pane_group.rs | 40 ++- crates/workspace/src/workspace.rs | 1 + 4 files changed, 269 insertions(+), 226 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 3eb82e94c7e13c99fc7a420a1053dc02d156980a..0487b11c19fdcbcd3f2ef2d7db0e66cc3a4c73c8 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1,230 +1,233 @@ { - // The name of the Zed theme to use for the UI - "theme": "One Dark", - // The name of a font to use for rendering text in the editor - "buffer_font_family": "Zed Mono", - // The default font size for text in the editor - "buffer_font_size": 15, - // Whether to enable vim modes and key bindings - "vim_mode": false, - // Whether to show the informational hover box when moving the mouse - // over symbols in the editor. - "hover_popover_enabled": true, - // Whether the cursor blinks in the editor. - "cursor_blink": true, - // Whether to pop the completions menu while typing in an editor without - // explicitly requesting it. - "show_completions_on_input": true, - // Whether new projects should start out 'online'. Online projects - // appear in the contacts panel under your name, so that your contacts - // can see which projects you are working on. Regardless of this - // setting, projects keep their last online status when you reopen them. - "projects_online_by_default": true, - // Whether to use language servers to provide code intelligence. - "enable_language_server": true, - // When to automatically save edited buffers. This setting can - // take four values. - // - // 1. Never automatically save: - // "autosave": "off", - // 2. Save when changing focus away from the Zed window: - // "autosave": "on_window_change", - // 3. Save when changing focus away from a specific buffer: - // "autosave": "on_focus_change", - // 4. Save when idle for a certain amount of time: - // "autosave": { "after_delay": {"milliseconds": 500} }, - "autosave": "off", - // Where to place the dock by default. This setting can take three - // values: - // - // 1. Position the dock attached to the bottom of the workspace - // "default_dock_anchor": "bottom" - // 2. Position the dock to the right of the workspace like a side panel - // "default_dock_anchor": "right" - // 3. Position the dock full screen over the entire workspace" - // "default_dock_anchor": "expanded" - "default_dock_anchor": "right", - // Whether or not to perform a buffer format before saving - "format_on_save": "on", - // How to perform a buffer format. This setting can take two values: - // - // 1. Format code using the current language server: - // "format_on_save": "language_server" - // 2. Format code using an external command: - // "format_on_save": { - // "external": { - // "command": "prettier", - // "arguments": ["--stdin-filepath", "{buffer_path}"] - // } - // } - "formatter": "language_server", - // How to soft-wrap long lines of text. This setting can take - // three values: - // - // 1. Do not soft wrap. - // "soft_wrap": "none", - // 2. Soft wrap lines that overflow the editor: - // "soft_wrap": "editor_width", - // 3. Soft wrap lines at the preferred line length - // "soft_wrap": "preferred_line_length", - "soft_wrap": "none", - // The column at which to soft-wrap lines, for buffers where soft-wrap - // is enabled. - "preferred_line_length": 80, - // Whether to indent lines using tab characters, as opposed to multiple - // spaces. - "hard_tabs": false, - // How many columns a tab should occupy. - "tab_size": 4, - // Git gutter behavior configuration. - "git": { - // Control whether the git gutter is shown. May take 2 values: - // 1. Show the gutter - // "git_gutter": "tracked_files" - // 2. Hide the gutter - // "git_gutter": "hide" - "git_gutter": "tracked_files" - }, - // Settings specific to journaling - "journal": { - // The path of the directory where journal entries are stored - "path": "~", - // What format to display the hours in - // May take 2 values: - // 1. hour12 - // 2. hour24 - "hour_format": "hour12" - }, - // Settings specific to the terminal - "terminal": { - // What shell to use when opening a terminal. May take 3 values: - // 1. Use the system's default terminal configuration (e.g. $TERM). - // "shell": "system" - // 2. A program: - // "shell": { - // "program": "sh" - // } - // 3. A program with arguments: - // "shell": { - // "with_arguments": { - // "program": "/bin/bash", - // "arguments": ["--login"] - // } - // } - "shell": "system", - // What working directory to use when launching the terminal. - // May take 4 values: - // 1. Use the current file's project directory. Will Fallback to the - // first project directory strategy if unsuccessful - // "working_directory": "current_project_directory" - // 2. Use the first project in this workspace's directory - // "working_directory": "first_project_directory" - // 3. Always use this platform's home directory (if we can find it) - // "working_directory": "always_home" - // 4. Always use a specific directory. This value will be shell expanded. - // If this path is not a valid directory the terminal will default to - // this platform's home directory (if we can find it) - // "working_directory": { - // "always": { - // "directory": "~/zed/projects/" - // } - // } + // The name of the Zed theme to use for the UI + "theme": "One Dark", + // The name of a font to use for rendering text in the editor + "buffer_font_family": "Zed Mono", + // The default font size for text in the editor + "buffer_font_size": 15, + // The factor to grow the active pane by. Defaults to 1.0 + // which gives the same size as all other panes. + "active_pane_magnification": 1.0, + // Whether to enable vim modes and key bindings + "vim_mode": false, + // Whether to show the informational hover box when moving the mouse + // over symbols in the editor. + "hover_popover_enabled": true, + // Whether the cursor blinks in the editor. + "cursor_blink": true, + // Whether to pop the completions menu while typing in an editor without + // explicitly requesting it. + "show_completions_on_input": true, + // Whether new projects should start out 'online'. Online projects + // appear in the contacts panel under your name, so that your contacts + // can see which projects you are working on. Regardless of this + // setting, projects keep their last online status when you reopen them. + "projects_online_by_default": true, + // Whether to use language servers to provide code intelligence. + "enable_language_server": true, + // When to automatically save edited buffers. This setting can + // take four values. // + // 1. Never automatically save: + // "autosave": "off", + // 2. Save when changing focus away from the Zed window: + // "autosave": "on_window_change", + // 3. Save when changing focus away from a specific buffer: + // "autosave": "on_focus_change", + // 4. Save when idle for a certain amount of time: + // "autosave": { "after_delay": {"milliseconds": 500} }, + "autosave": "off", + // Where to place the dock by default. This setting can take three + // values: // - "working_directory": "current_project_directory", - // Set the cursor blinking behavior in the terminal. - // May take 4 values: - // 1. Never blink the cursor, ignoring the terminal mode - // "blinking": "off", - // 2. Default the cursor blink to off, but allow the terminal to - // set blinking - // "blinking": "terminal_controlled", - // 3. Always blink the cursor, ignoring the terminal mode - // "blinking": "on", - "blinking": "terminal_controlled", - // Set whether Alternate Scroll mode (code: ?1007) is active by default. - // Alternate Scroll mode converts mouse scroll events into up / down key - // presses when in the alternate screen (e.g. when running applications - // like vim or less). The terminal can still set and unset this mode. - // May take 2 values: - // 1. Default alternate scroll mode to on - // "alternate_scroll": "on", - // 2. Default alternate scroll mode to off - // "alternate_scroll": "off", - "alternate_scroll": "off", - // Set whether the option key behaves as the meta key. - // May take 2 values: - // 1. Rely on default platform handling of option key, on macOS - // this means generating certain unicode characters - // "option_to_meta": false, - // 2. Make the option keys behave as a 'meta' key, e.g. for emacs - // "option_to_meta": true, - "option_as_meta": false, - // Whether or not selecting text in the terminal will automatically - // copy to the system clipboard. - "copy_on_select": false, - // Any key-value pairs added to this list will be added to the terminal's - // enviroment. Use `:` to seperate multiple values. - "env": { - // "KEY": "value1:value2" - } - // Set the terminal's font size. If this option is not included, - // the terminal will default to matching the buffer's font size. - // "font_size": "15" - // Set the terminal's font family. If this option is not included, - // the terminal will default to matching the buffer's font family. - // "font_family": "Zed Mono" - }, - // Different settings for specific languages. - "languages": { - "Plain Text": { - "soft_wrap": "preferred_line_length" - }, - "C": { - "tab_size": 2 - }, - "C++": { - "tab_size": 2 - }, - "Elixir": { - "tab_size": 2 - }, - "Go": { - "tab_size": 4, - "hard_tabs": true - }, - "Markdown": { - "soft_wrap": "preferred_line_length" + // 1. Position the dock attached to the bottom of the workspace + // "default_dock_anchor": "bottom" + // 2. Position the dock to the right of the workspace like a side panel + // "default_dock_anchor": "right" + // 3. Position the dock full screen over the entire workspace" + // "default_dock_anchor": "expanded" + "default_dock_anchor": "right", + // Whether or not to perform a buffer format before saving + "format_on_save": "on", + // How to perform a buffer format. This setting can take two values: + // + // 1. Format code using the current language server: + // "format_on_save": "language_server" + // 2. Format code using an external command: + // "format_on_save": { + // "external": { + // "command": "prettier", + // "arguments": ["--stdin-filepath", "{buffer_path}"] + // } + // } + "formatter": "language_server", + // How to soft-wrap long lines of text. This setting can take + // three values: + // + // 1. Do not soft wrap. + // "soft_wrap": "none", + // 2. Soft wrap lines that overflow the editor: + // "soft_wrap": "editor_width", + // 3. Soft wrap lines at the preferred line length + // "soft_wrap": "preferred_line_length", + "soft_wrap": "none", + // The column at which to soft-wrap lines, for buffers where soft-wrap + // is enabled. + "preferred_line_length": 80, + // Whether to indent lines using tab characters, as opposed to multiple + // spaces. + "hard_tabs": false, + // How many columns a tab should occupy. + "tab_size": 4, + // Git gutter behavior configuration. + "git": { + // Control whether the git gutter is shown. May take 2 values: + // 1. Show the gutter + // "git_gutter": "tracked_files" + // 2. Hide the gutter + // "git_gutter": "hide" + "git_gutter": "tracked_files" }, - "Rust": { - "tab_size": 4 + // Settings specific to journaling + "journal": { + // The path of the directory where journal entries are stored + "path": "~", + // What format to display the hours in + // May take 2 values: + // 1. hour12 + // 2. hour24 + "hour_format": "hour12" }, - "JavaScript": { - "tab_size": 2 + // Settings specific to the terminal + "terminal": { + // What shell to use when opening a terminal. May take 3 values: + // 1. Use the system's default terminal configuration (e.g. $TERM). + // "shell": "system" + // 2. A program: + // "shell": { + // "program": "sh" + // } + // 3. A program with arguments: + // "shell": { + // "with_arguments": { + // "program": "/bin/bash", + // "arguments": ["--login"] + // } + // } + "shell": "system", + // What working directory to use when launching the terminal. + // May take 4 values: + // 1. Use the current file's project directory. Will Fallback to the + // first project directory strategy if unsuccessful + // "working_directory": "current_project_directory" + // 2. Use the first project in this workspace's directory + // "working_directory": "first_project_directory" + // 3. Always use this platform's home directory (if we can find it) + // "working_directory": "always_home" + // 4. Always use a specific directory. This value will be shell expanded. + // If this path is not a valid directory the terminal will default to + // this platform's home directory (if we can find it) + // "working_directory": { + // "always": { + // "directory": "~/zed/projects/" + // } + // } + // + // + "working_directory": "current_project_directory", + // Set the cursor blinking behavior in the terminal. + // May take 4 values: + // 1. Never blink the cursor, ignoring the terminal mode + // "blinking": "off", + // 2. Default the cursor blink to off, but allow the terminal to + // set blinking + // "blinking": "terminal_controlled", + // 3. Always blink the cursor, ignoring the terminal mode + // "blinking": "on", + "blinking": "terminal_controlled", + // Set whether Alternate Scroll mode (code: ?1007) is active by default. + // Alternate Scroll mode converts mouse scroll events into up / down key + // presses when in the alternate screen (e.g. when running applications + // like vim or less). The terminal can still set and unset this mode. + // May take 2 values: + // 1. Default alternate scroll mode to on + // "alternate_scroll": "on", + // 2. Default alternate scroll mode to off + // "alternate_scroll": "off", + "alternate_scroll": "off", + // Set whether the option key behaves as the meta key. + // May take 2 values: + // 1. Rely on default platform handling of option key, on macOS + // this means generating certain unicode characters + // "option_to_meta": false, + // 2. Make the option keys behave as a 'meta' key, e.g. for emacs + // "option_to_meta": true, + "option_as_meta": false, + // Whether or not selecting text in the terminal will automatically + // copy to the system clipboard. + "copy_on_select": false, + // Any key-value pairs added to this list will be added to the terminal's + // enviroment. Use `:` to seperate multiple values. + "env": { + // "KEY": "value1:value2" + } + // Set the terminal's font size. If this option is not included, + // the terminal will default to matching the buffer's font size. + // "font_size": "15" + // Set the terminal's font family. If this option is not included, + // the terminal will default to matching the buffer's font family. + // "font_family": "Zed Mono" }, - "TypeScript": { - "tab_size": 2 + // Different settings for specific languages. + "languages": { + "Plain Text": { + "soft_wrap": "preferred_line_length" + }, + "C": { + "tab_size": 2 + }, + "C++": { + "tab_size": 2 + }, + "Elixir": { + "tab_size": 2 + }, + "Go": { + "tab_size": 4, + "hard_tabs": true + }, + "Markdown": { + "soft_wrap": "preferred_line_length" + }, + "Rust": { + "tab_size": 4 + }, + "JavaScript": { + "tab_size": 2 + }, + "TypeScript": { + "tab_size": 2 + }, + "TSX": { + "tab_size": 2 + } }, - "TSX": { - "tab_size": 2 + // LSP Specific settings. + "lsp": { + // Specify the LSP name as a key here. + // As of 8/10/22, supported LSPs are: + // pyright + // gopls + // rust-analyzer + // typescript-language-server + // vscode-json-languageserver + // "rust_analyzer": { + // //These initialization options are merged into Zed's defaults + // "initialization_options": { + // "checkOnSave": { + // "command": "clippy" + // } + // } + // } } - }, - // LSP Specific settings. - "lsp": { - // Specify the LSP name as a key here. - // As of 8/10/22, supported LSPs are: - // pyright - // gopls - // rust-analyzer - // typescript-language-server - // vscode-json-languageserver - // "rust_analyzer": { - // //These initialization options are merged into Zed's defaults - // "initialization_options": { - // "checkOnSave": { - // "command": "clippy" - // } - // } - // } - } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index d8f8e8926a9968f72368004710d3dd33b9271bf3..a8a98f649ac2b0e84ff9615474e6122d72a89681 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -28,6 +28,7 @@ pub struct Settings { pub buffer_font_family: FamilyId, pub default_buffer_font_size: f32, pub buffer_font_size: f32, + pub active_pane_magnification: f32, pub cursor_blink: bool, pub hover_popover_enabled: bool, pub show_completions_on_input: bool, @@ -235,6 +236,8 @@ pub struct SettingsFileContent { #[serde(default)] pub buffer_font_size: Option, #[serde(default)] + pub active_pane_magnification: Option, + #[serde(default)] pub cursor_blink: Option, #[serde(default)] pub hover_popover_enabled: Option, @@ -294,6 +297,7 @@ impl Settings { .load_family(&[defaults.buffer_font_family.as_ref().unwrap()]) .unwrap(), buffer_font_size: defaults.buffer_font_size.unwrap(), + active_pane_magnification: defaults.active_pane_magnification.unwrap(), default_buffer_font_size: defaults.buffer_font_size.unwrap(), cursor_blink: defaults.cursor_blink.unwrap(), hover_popover_enabled: defaults.hover_popover_enabled.unwrap(), @@ -349,6 +353,10 @@ impl Settings { data.projects_online_by_default, ); merge(&mut self.buffer_font_size, data.buffer_font_size); + merge( + &mut self.active_pane_magnification, + data.active_pane_magnification, + ); merge(&mut self.default_buffer_font_size, data.buffer_font_size); merge(&mut self.cursor_blink, data.cursor_blink); merge(&mut self.hover_popover_enabled, data.hover_popover_enabled); @@ -440,6 +448,7 @@ impl Settings { experiments: FeatureFlags::default(), buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(), buffer_font_size: 14., + active_pane_magnification: 1., default_buffer_font_size: 14., cursor_blink: true, hover_popover_enabled: true, diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 6c379ffd2a9dac960730dcbbba9ee1ab471d72e2..c6a2ef49a3f7fed5221784eae33764c6635695ad 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -6,6 +6,7 @@ use gpui::{ }; use project::Project; use serde::Deserialize; +use settings::Settings; use theme::Theme; #[derive(Clone, Debug, Eq, PartialEq)] @@ -61,10 +62,17 @@ impl PaneGroup { theme: &Theme, follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, + active_pane: &ViewHandle, cx: &mut RenderContext, ) -> ElementBox { - self.root - .render(project, theme, follower_states, active_call, cx) + self.root.render( + project, + theme, + follower_states, + active_call, + active_pane, + cx, + ) } pub(crate) fn panes(&self) -> Vec<&ViewHandle> { @@ -102,12 +110,20 @@ impl Member { Member::Axis(PaneAxis { axis, members }) } + fn contains(&self, needle: &ViewHandle) -> bool { + match self { + Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)), + Member::Pane(pane) => pane == needle, + } + } + pub fn render( &self, project: &ModelHandle, theme: &Theme, follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, + active_pane: &ViewHandle, cx: &mut RenderContext, ) -> ElementBox { enum FollowIntoExternalProject {} @@ -234,7 +250,14 @@ impl Member { .with_children(prompt) .boxed() } - Member::Axis(axis) => axis.render(project, theme, follower_states, active_call, cx), + Member::Axis(axis) => axis.render( + project, + theme, + follower_states, + active_call, + active_pane, + cx, + ), } } @@ -340,12 +363,19 @@ impl PaneAxis { theme: &Theme, follower_state: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, + active_pane: &ViewHandle, cx: &mut RenderContext, ) -> ElementBox { let last_member_ix = self.members.len() - 1; Flex::new(self.axis) .with_children(self.members.iter().enumerate().map(|(ix, member)| { - let mut member = member.render(project, theme, follower_state, active_call, cx); + let mut flex = 1.0; + if member.contains(active_pane) { + flex = cx.global::().active_pane_magnification; + } + + let mut member = + member.render(project, theme, follower_state, active_call, active_pane, cx); if ix < last_member_ix { let mut border = theme.workspace.pane_divider; border.left = false; @@ -359,7 +389,7 @@ impl PaneAxis { member = Container::new(member).with_border(border).boxed(); } - FlexItem::new(member).flex(1.0, true).boxed() + FlexItem::new(member).flex(flex, true).boxed() })) .boxed() } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e7752219c52db7c1e62bc2ce2138015cb28a20ae..289ba5e05e588a73868f47b5414d03d2fb45fa65 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2670,6 +2670,7 @@ impl View for Workspace { &theme, &self.follower_states_by_leader, self.active_call(), + self.active_pane(), cx, )) .flex(1., true) From be6ee3cbffccc7a1f5548094844317ccbe2d1047 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 3 Nov 2022 16:02:29 -0700 Subject: [PATCH 02/67] Start work on ERB language support --- Cargo.lock | 11 ++++++++ crates/language/src/language.rs | 30 ++++++++++++++------- crates/language/src/syntax_map.rs | 2 +- crates/zed/Cargo.toml | 1 + crates/zed/src/languages.rs | 1 + crates/zed/src/languages/erb/config.toml | 8 ++++++ crates/zed/src/languages/erb/injections.scm | 7 +++++ 7 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 crates/zed/src/languages/erb/config.toml create mode 100644 crates/zed/src/languages/erb/injections.scm diff --git a/Cargo.lock b/Cargo.lock index f79a7b851d04598f00db12bf89d3fdc6fb35fa63..29c444bfee4e3316f4515394091cae545b768eef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6426,6 +6426,16 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-embedded-template" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33817ade928c73a32d4f904a602321e09de9fc24b71d106f3b4b3f8ab30dcc38" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-go" version = "0.19.1" @@ -7719,6 +7729,7 @@ dependencies = [ "tree-sitter-cpp", "tree-sitter-css", "tree-sitter-elixir", + "tree-sitter-embedded-template", "tree-sitter-go", "tree-sitter-html", "tree-sitter-json 0.20.0", diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 5abc89321cfe936de64ef4515c7f7a64b54e0035..4436ab416e2a7d1725f6b8843435ea886a800ebf 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -326,7 +326,13 @@ struct InjectionConfig { query: Query, content_capture_ix: u32, language_capture_ix: Option, - languages_by_pattern_ix: Vec>>, + patterns: Vec, +} + +#[derive(Default, Clone)] +struct InjectionPatternConfig { + language: Option>, + combined: bool, } struct BracketConfig { @@ -730,15 +736,21 @@ impl Language { ("content", &mut content_capture_ix), ], ); - let languages_by_pattern_ix = (0..query.pattern_count()) + let patterns = (0..query.pattern_count()) .map(|ix| { - query.property_settings(ix).iter().find_map(|setting| { - if setting.key.as_ref() == "language" { - return setting.value.clone(); - } else { - None + let mut config = InjectionPatternConfig::default(); + for setting in query.property_settings(ix) { + match setting.key.as_ref() { + "language" => { + config.language = setting.value.clone(); + } + "combined" => { + config.combined = true; + } + _ => {} } - }) + } + config }) .collect(); if let Some(content_capture_ix) = content_capture_ix { @@ -746,7 +758,7 @@ impl Language { query, language_capture_ix, content_capture_ix, - languages_by_pattern_ix, + patterns, }); } Ok(self) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 5dd9c483afff6d148fa8a96597fa60d46f044ec8..3eb15c9c5e3c87e601d5be3d39e4e065a8aa37a8 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -961,7 +961,7 @@ fn get_injections( } prev_match = Some((mat.pattern_index, content_range.clone())); - let language_name = config.languages_by_pattern_ix[mat.pattern_index] + let language_name = config.patterns[mat.pattern_index].language .as_ref() .map(|s| Cow::Borrowed(s.as_ref())) .or_else(|| { diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index cca40cbe5ae729145f20ab38368e1656479bcd18..3bfe10eedc6fb4cb45e8319a582fada27cbe7e04 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -95,6 +95,7 @@ tree-sitter-c = "0.20.1" tree-sitter-cpp = "0.20.0" tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev = "769203d0f9abe1a9a691ac2b9fe4bb4397a73c51" } tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "05e3631c6a0701c1fa518b0fee7be95a2ceef5e2" } +tree-sitter-embedded-template = "0.20.0" tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" } tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "137e1ce6a02698fc246cdb9c6b886ed1de9a1ed8" } tree-sitter-rust = "0.20.3" diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index c1e17a8e1aa5144550bc04d93516473aa874295f..76bb4394dd5c238963c1680512317c186f60448c 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -117,6 +117,7 @@ pub async fn init(languages: Arc, _executor: Arc) Some(CachedLspAdapter::new(html::HtmlLspAdapter).await), ), ("ruby", tree_sitter_ruby::language(), None), + ("erb", tree_sitter_embedded_template::language(), None), ] { languages.add(language(name, grammar, lsp_adapter)); } diff --git a/crates/zed/src/languages/erb/config.toml b/crates/zed/src/languages/erb/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..280219a1191c1638190a6d52e1d7e717daa665fd --- /dev/null +++ b/crates/zed/src/languages/erb/config.toml @@ -0,0 +1,8 @@ +name = "ERB" +path_suffixes = ["erb"] +autoclose_before = ">})" +brackets = [ + { start = "<", end = ">", close = true, newline = true }, +] + +block_comment = ["<%#", "%>"] \ No newline at end of file diff --git a/crates/zed/src/languages/erb/injections.scm b/crates/zed/src/languages/erb/injections.scm new file mode 100644 index 0000000000000000000000000000000000000000..7a69a818ef31d7fa3822466209b08c15280c6f5b --- /dev/null +++ b/crates/zed/src/languages/erb/injections.scm @@ -0,0 +1,7 @@ +((code) @content + (#set! "language" "ruby") + (#set! "combined")) + +((content) @content + (#set! "language" "html") + (#set! "combined")) From 5efe2ed6d364aaf6a40370d13f3c62129889716a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 7 Nov 2022 14:45:17 -0800 Subject: [PATCH 03/67] Start work on handling combined injections in SyntaxMap --- Cargo.lock | 5 +- Cargo.toml | 2 +- crates/language/Cargo.toml | 1 + crates/language/src/language.rs | 19 ++ crates/language/src/syntax_map.rs | 343 +++++++++++++++++++++++------- 5 files changed, 288 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29c444bfee4e3316f4515394091cae545b768eef..e43c8473eabc81c453efa9454f2c73fb027595da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3005,6 +3005,7 @@ dependencies = [ "text", "theme", "tree-sitter", + "tree-sitter-embedded-template", "tree-sitter-html", "tree-sitter-javascript", "tree-sitter-json 0.19.0", @@ -6381,8 +6382,8 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.20.8" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=366210ae925d7ea0891bc7a0c738f60c77c04d7b#366210ae925d7ea0891bc7a0c738f60c77c04d7b" +version = "0.20.9" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=f0177f216e3f76a5f68e792b6f9e45fd32383eb6#f0177f216e3f76a5f68e792b6f9e45fd32383eb6" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index 205017da1fbc156543b143fc13238780767e7734..a46a56de58aa7e8dd00888db4d55ead52847e362 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } rand = { version = "0.8" } [patch.crates-io] -tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "366210ae925d7ea0891bc7a0c738f60c77c04d7b" } +tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "f0177f216e3f76a5f68e792b6f9e45fd32383eb6" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 419c7a79a51114a207762af9aab91feec63cfd9d..6c074a2d75e0ef59523bd52dbfaf53bb563025d2 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -72,4 +72,5 @@ tree-sitter-rust = "*" tree-sitter-python = "*" tree-sitter-typescript = "*" tree-sitter-ruby = "*" +tree-sitter-embedded-template = "*" unindent = "0.1.7" diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 4436ab416e2a7d1725f6b8843435ea886a800ebf..5e9319b1289bb3ea0530dbade269f436706b328a 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -28,6 +28,7 @@ use std::{ any::Any, cell::RefCell, fmt::Debug, + hash::Hash, mem, ops::Range, path::{Path, PathBuf}, @@ -643,6 +644,10 @@ impl Language { self.adapter.clone() } + pub fn id(&self) -> Option { + self.grammar.as_ref().map(|g| g.id) + } + pub fn with_highlights_query(mut self, source: &str) -> Result { let grammar = self.grammar_mut(); grammar.highlights_query = Some(Query::new(grammar.ts_language, source)?); @@ -895,6 +900,20 @@ impl Language { } } +impl Hash for Language { + fn hash(&self, state: &mut H) { + self.id().hash(state) + } +} + +impl PartialEq for Language { + fn eq(&self, other: &Self) -> bool { + self.id().eq(&other.id()) + } +} + +impl Eq for Language {} + impl Debug for Language { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Language") diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 3eb15c9c5e3c87e601d5be3d39e4e065a8aa37a8..be735df9c089111b28c02d5abe0fb00637d6d2f2 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -1,4 +1,5 @@ use crate::{Grammar, InjectionConfig, Language, LanguageRegistry}; +use collections::HashMap; use lazy_static::lazy_static; use parking_lot::Mutex; use std::{ @@ -90,6 +91,7 @@ struct SyntaxLayer { range: Range, tree: tree_sitter::Tree, language: Arc, + combined: bool, } #[derive(Debug)] @@ -105,22 +107,39 @@ struct SyntaxLayerSummary { max_depth: usize, range: Range, last_layer_range: Range, + last_layer_language: Option, } #[derive(Clone, Debug)] -struct DepthAndRange(usize, Range); +struct SyntaxLayerPosition { + depth: usize, + range: Range, + language: Option, +} #[derive(Clone, Debug)] struct DepthAndMaxPosition(usize, Anchor); #[derive(Clone, Debug)] -struct DepthAndRangeOrMaxPosition(DepthAndRange, DepthAndMaxPosition); +struct SyntaxLayerPositionBeforeChange { + position: SyntaxLayerPosition, + change: DepthAndMaxPosition, +} struct ReparseStep { depth: usize, language: Arc, - ranges: Vec, range: Range, + included_ranges: Vec, + mode: ReparseMode, +} + +enum ReparseMode { + Single, + Combined { + parent_layer_range: Range, + parent_layer_changed_ranges: Vec>, + }, } #[derive(Debug, PartialEq, Eq)] @@ -225,7 +244,11 @@ impl SyntaxSnapshot { // subsequent layers at this same depth. else if cursor.item().is_some() { let slice = cursor.slice( - &DepthAndRange(depth + 1, Anchor::MIN..Anchor::MAX), + &SyntaxLayerPosition { + depth: depth + 1, + range: Anchor::MIN..Anchor::MAX, + language: None, + }, Bias::Left, text, ); @@ -320,28 +343,44 @@ impl SyntaxSnapshot { let mut changed_regions = ChangeRegionSet::default(); let mut queue = BinaryHeap::new(); + let mut combined_injection_ranges = HashMap::default(); queue.push(ReparseStep { depth: 0, language: language.clone(), - ranges: Vec::new(), + included_ranges: vec![tree_sitter::Range { + start_byte: 0, + end_byte: text.len(), + start_point: Point::zero().to_ts_point(), + end_point: text.max_point().to_ts_point(), + }], range: Anchor::MIN..Anchor::MAX, + mode: ReparseMode::Single, }); loop { let step = queue.pop(); - let (depth, range) = if let Some(step) = &step { - (step.depth, step.range.clone()) + let target = if let Some(step) = &step { + SyntaxLayerPosition { + depth: step.depth, + range: step.range.clone(), + language: step.language.id(), + } } else { - (max_depth + 1, Anchor::MAX..Anchor::MAX) + SyntaxLayerPosition { + depth: max_depth + 1, + range: Anchor::MAX..Anchor::MAX, + language: None, + } }; - let target = DepthAndRange(depth, range.clone()); let mut done = cursor.item().is_none(); while !done && target.cmp(&cursor.end(text), &text).is_gt() { done = true; - let bounded_target = - DepthAndRangeOrMaxPosition(target.clone(), changed_regions.start_position()); + let bounded_target = SyntaxLayerPositionBeforeChange { + position: target.clone(), + change: changed_regions.start_position(), + }; if bounded_target.cmp(&cursor.start(), &text).is_gt() { let slice = cursor.slice(&bounded_target, Bias::Left, text); if !slice.is_empty() { @@ -353,11 +392,7 @@ impl SyntaxSnapshot { } while target.cmp(&cursor.end(text), text).is_gt() { - let layer = if let Some(layer) = cursor.item() { - layer - } else { - break; - }; + let Some(layer) = cursor.item() else { break }; if changed_regions.intersects(&layer, text) { changed_regions.insert( @@ -378,70 +413,79 @@ impl SyntaxSnapshot { } } - let (ranges, language) = if let Some(step) = step { - (step.ranges, step.language) - } else { - break; - }; - - let start_point; - let start_byte; - let end_byte; - if let Some((first, last)) = ranges.first().zip(ranges.last()) { - start_point = first.start_point; - start_byte = first.start_byte; - end_byte = last.end_byte; - } else { - start_point = Point::zero().to_ts_point(); - start_byte = 0; - end_byte = text.len(); - }; + let Some(step) = step else { break }; + let (step_start_byte, step_start_point) = + step.range.start.summary::<(usize, Point)>(text); + let step_end_byte = step.range.end.to_offset(text); + let Some(grammar) = step.language.grammar.as_deref() else { continue }; let mut old_layer = cursor.item(); if let Some(layer) = old_layer { - if layer.range.to_offset(text) == (start_byte..end_byte) { + if layer.range.to_offset(text) == (step_start_byte..step_end_byte) + && layer.language.id() == step.language.id() + { cursor.next(&text); } else { old_layer = None; } } - let grammar = if let Some(grammar) = language.grammar.as_deref() { - grammar - } else { - continue; - }; + let mut combined = false; + let mut included_ranges = step.included_ranges; let tree; let changed_ranges; if let Some(old_layer) = old_layer { + if let ReparseMode::Combined { + parent_layer_changed_ranges, + .. + } = step.mode + { + combined = true; + included_ranges = splice_included_ranges( + old_layer.tree.included_ranges(), + &parent_layer_changed_ranges, + &included_ranges, + ); + } + tree = parse_text( grammar, text.as_rope(), + step_start_byte, + step_start_point, + included_ranges, Some(old_layer.tree.clone()), - ranges, ); changed_ranges = join_ranges( edits .iter() .map(|e| e.new.clone()) - .filter(|range| range.start < end_byte && range.end > start_byte), + .filter(|range| range.start < step_end_byte && range.end > step_start_byte), old_layer .tree .changed_ranges(&tree) - .map(|r| start_byte + r.start_byte..start_byte + r.end_byte), + .map(|r| step_start_byte + r.start_byte..step_start_byte + r.end_byte), ); } else { - tree = parse_text(grammar, text.as_rope(), None, ranges); - changed_ranges = vec![start_byte..end_byte]; + tree = parse_text( + grammar, + text.as_rope(), + step_start_byte, + step_start_point, + included_ranges, + None, + ); + changed_ranges = vec![step_start_byte..step_end_byte]; } layers.push( SyntaxLayer { - depth, - range, + depth: step.depth, + range: step.range, tree: tree.clone(), language: language.clone(), + combined, }, &text, ); @@ -450,11 +494,10 @@ impl SyntaxSnapshot { grammar.injection_config.as_ref().zip(registry.as_ref()), changed_ranges.is_empty(), ) { - let depth = depth + 1; for range in &changed_ranges { changed_regions.insert( ChangedRegion { - depth, + depth: step.depth + 1, range: text.anchor_before(range.start)..text.anchor_after(range.end), }, text, @@ -463,10 +506,11 @@ impl SyntaxSnapshot { get_injections( config, text, - tree.root_node_with_offset(start_byte, start_point), + tree.root_node_with_offset(step_start_byte, step_start_point.to_ts_point()), registry, - depth, + step.depth + 1, &changed_ranges, + &mut combined_injection_ranges, &mut queue, ); } @@ -547,7 +591,6 @@ impl SyntaxSnapshot { } }); - // let mut result = Vec::new(); cursor.next(buffer); std::iter::from_fn(move || { if let Some(layer) = cursor.item() { @@ -565,8 +608,6 @@ impl SyntaxSnapshot { None } }) - - // result } } @@ -892,14 +933,11 @@ fn join_ranges( fn parse_text( grammar: &Grammar, text: &Rope, - old_tree: Option, + start_byte: usize, + start_point: Point, mut ranges: Vec, + old_tree: Option, ) -> Tree { - let (start_byte, start_point) = ranges - .first() - .map(|range| (range.start_byte, Point::from_ts_point(range.start_point))) - .unwrap_or_default(); - for range in &mut ranges { range.start_byte -= start_byte; range.end_byte -= start_byte; @@ -934,13 +972,16 @@ fn get_injections( node: Node, language_registry: &LanguageRegistry, depth: usize, - query_ranges: &[Range], + changed_ranges: &[Range], + combined_injection_ranges: &mut HashMap, Vec>, queue: &mut BinaryHeap, ) -> bool { let mut result = false; let mut query_cursor = QueryCursorHandle::new(); let mut prev_match = None; - for query_range in query_ranges { + + combined_injection_ranges.clear(); + for query_range in changed_ranges { query_cursor.set_byte_range(query_range.start.saturating_sub(1)..query_range.end); for mat in query_cursor.matches(&config.query, node, TextProvider(text.as_rope())) { let content_ranges = mat @@ -961,7 +1002,9 @@ fn get_injections( } prev_match = Some((mat.pattern_index, content_range.clone())); - let language_name = config.patterns[mat.pattern_index].language + let combined = config.patterns[mat.pattern_index].combined; + let language_name = config.patterns[mat.pattern_index] + .language .as_ref() .map(|s| Cow::Borrowed(s.as_ref())) .or_else(|| { @@ -975,19 +1018,93 @@ fn get_injections( result = true; let range = text.anchor_before(content_range.start) ..text.anchor_after(content_range.end); - queue.push(ReparseStep { - depth, - language, - ranges: content_ranges, - range, - }) + if combined { + combined_injection_ranges + .entry(language.clone()) + .or_default() + .extend(content_ranges); + } else { + queue.push(ReparseStep { + depth, + language, + included_ranges: content_ranges, + range, + mode: ReparseMode::Single, + }); + } } } } } + + for (language, mut included_ranges) in combined_injection_ranges.drain() { + included_ranges.sort_unstable(); + let range = text.anchor_before(node.start_byte())..text.anchor_after(node.end_byte()); + queue.push(ReparseStep { + depth, + language, + range, + included_ranges, + mode: ReparseMode::Combined { + parent_layer_range: node.start_byte()..node.end_byte(), + parent_layer_changed_ranges: changed_ranges.to_vec(), + }, + }) + } + result } +fn splice_included_ranges( + mut ranges: Vec, + changed_ranges: &[Range], + new_ranges: &[tree_sitter::Range], +) -> Vec { + let mut changed_ranges = changed_ranges.into_iter().peekable(); + let mut new_ranges = new_ranges.into_iter().peekable(); + let mut ranges_ix = 0; + loop { + let new_range = new_ranges.peek(); + let mut changed_range = changed_ranges.peek(); + + // process changed ranges before any overlapping new ranges + if let Some((changed, new)) = changed_range.zip(new_range) { + if new.end_byte < changed.start { + changed_range = None; + } + } + + if let Some(changed) = changed_range { + let start_ix = ranges_ix + + match ranges[ranges_ix..].binary_search_by_key(&changed.start, |r| r.end_byte) { + Ok(ix) | Err(ix) => ix, + }; + let end_ix = ranges_ix + + match ranges[ranges_ix..].binary_search_by_key(&changed.end, |r| r.start_byte) { + Ok(ix) | Err(ix) => ix, + }; + if end_ix > start_ix { + ranges.splice(start_ix..end_ix, []); + } + changed_ranges.next(); + ranges_ix = start_ix; + } else if let Some(new_range) = new_range { + let ix = ranges_ix + + match ranges[ranges_ix..] + .binary_search_by_key(&new_range.start_byte, |r| r.start_byte) + { + Ok(ix) | Err(ix) => ix, + }; + ranges.insert(ix, **new_range); + new_ranges.next(); + ranges_ix = ix + 1; + } else { + break; + } + } + ranges +} + impl std::ops::Deref for SyntaxMap { type Target = SyntaxSnapshot; @@ -1017,14 +1134,22 @@ impl Ord for ReparseStep { Ord::cmp(&other.depth, &self.depth) .then_with(|| Ord::cmp(&range_b.start, &range_a.start)) .then_with(|| Ord::cmp(&range_a.end, &range_b.end)) + .then_with(|| self.language.id().cmp(&other.language.id())) } } impl ReparseStep { fn range(&self) -> Range { - let start = self.ranges.first().map_or(0, |r| r.start_byte); - let end = self.ranges.last().map_or(0, |r| r.end_byte); - start..end + if let ReparseMode::Combined { + parent_layer_range, .. + } = &self.mode + { + parent_layer_range.clone() + } else { + let start = self.included_ranges.first().map_or(0, |r| r.start_byte); + let end = self.included_ranges.last().map_or(0, |r| r.end_byte); + start..end + } } } @@ -1094,6 +1219,7 @@ impl Default for SyntaxLayerSummary { min_depth: 0, range: Anchor::MAX..Anchor::MIN, last_layer_range: Anchor::MIN..Anchor::MAX, + last_layer_language: None, } } } @@ -1114,14 +1240,15 @@ impl sum_tree::Summary for SyntaxLayerSummary { } } self.last_layer_range = other.last_layer_range.clone(); + self.last_layer_language = other.last_layer_language; } } -impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for DepthAndRange { +impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for SyntaxLayerPosition { fn cmp(&self, cursor_location: &SyntaxLayerSummary, buffer: &BufferSnapshot) -> Ordering { - Ord::cmp(&self.0, &cursor_location.max_depth) + Ord::cmp(&self.depth, &cursor_location.max_depth) .then_with(|| { - self.1 + self.range .start .cmp(&cursor_location.last_layer_range.start, buffer) }) @@ -1129,8 +1256,9 @@ impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for DepthAndRang cursor_location .last_layer_range .end - .cmp(&self.1.end, buffer) + .cmp(&self.range.end, buffer) }) + .then_with(|| self.language.cmp(&cursor_location.last_layer_language)) } } @@ -1141,12 +1269,14 @@ impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for DepthAndMaxP } } -impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for DepthAndRangeOrMaxPosition { +impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> + for SyntaxLayerPositionBeforeChange +{ fn cmp(&self, cursor_location: &SyntaxLayerSummary, buffer: &BufferSnapshot) -> Ordering { - if self.1.cmp(cursor_location, buffer).is_le() { + if self.change.cmp(cursor_location, buffer).is_le() { return Ordering::Less; } else { - self.0.cmp(cursor_location, buffer) + self.position.cmp(cursor_location, buffer) } } } @@ -1160,6 +1290,7 @@ impl sum_tree::Item for SyntaxLayer { max_depth: self.depth, range: self.range.clone(), last_layer_range: self.range.clone(), + last_layer_language: self.language.id(), } } } @@ -1246,6 +1377,60 @@ mod tests { use unindent::Unindent as _; use util::test::marked_text_ranges; + #[test] + fn test_splice_included_ranges() { + let ranges = vec![ts_range(20..30), ts_range(50..60), ts_range(80..90)]; + + let new_ranges = splice_included_ranges( + ranges.clone(), + &[54..56, 58..68], + &[ts_range(50..54), ts_range(59..67)], + ); + assert_eq!( + new_ranges, + &[ + ts_range(20..30), + ts_range(50..54), + ts_range(59..67), + ts_range(80..90), + ] + ); + + let new_ranges = splice_included_ranges(ranges.clone(), &[70..71, 91..100], &[]); + assert_eq!( + new_ranges, + &[ts_range(20..30), ts_range(50..60), ts_range(80..90)] + ); + + let new_ranges = + splice_included_ranges(ranges.clone(), &[], &[ts_range(0..2), ts_range(70..75)]); + assert_eq!( + new_ranges, + &[ + ts_range(0..2), + ts_range(20..30), + ts_range(50..60), + ts_range(70..75), + ts_range(80..90) + ] + ); + + fn ts_range(range: Range) -> tree_sitter::Range { + tree_sitter::Range { + start_byte: range.start, + start_point: tree_sitter::Point { + row: 0, + column: range.start, + }, + end_byte: range.end, + end_point: tree_sitter::Point { + row: 0, + column: range.end, + }, + } + } + } + #[gpui::test] fn test_syntax_map_layers_for_range() { let registry = Arc::new(LanguageRegistry::test()); From c838a7d973078f01843e7fe9f5ab20157382656d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 7 Nov 2022 16:58:12 -0800 Subject: [PATCH 04/67] Get combined injections basically working Co-authored-by: Nathan Sobo Co-authored-by: Mikayla Maki --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/language/src/syntax_map.rs | 530 +++++++++++++++++++----------- 3 files changed, 344 insertions(+), 190 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e43c8473eabc81c453efa9454f2c73fb027595da..bc127b24c40cb5a5f589edba88d7273b7c7288f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6383,7 +6383,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.20.9" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=f0177f216e3f76a5f68e792b6f9e45fd32383eb6#f0177f216e3f76a5f68e792b6f9e45fd32383eb6" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=da6e24de1751aef6a944adfcefb192b751c56f76#da6e24de1751aef6a944adfcefb192b751c56f76" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index a46a56de58aa7e8dd00888db4d55ead52847e362..8ac180fcc12156f7149305651ce5ba5e6a48f246 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } rand = { version = "0.8" } [patch.crates-io] -tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "f0177f216e3f76a5f68e792b6f9e45fd32383eb6" } +tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "da6e24de1751aef6a944adfcefb192b751c56f76" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index be735df9c089111b28c02d5abe0fb00637d6d2f2..7cfbd9de2fcf6dc900ec0231e97a84351df734d8 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -126,15 +126,15 @@ struct SyntaxLayerPositionBeforeChange { change: DepthAndMaxPosition, } -struct ReparseStep { +struct ParseStep { depth: usize, language: Arc, range: Range, included_ranges: Vec, - mode: ReparseMode, + mode: ParseMode, } -enum ReparseMode { +enum ParseMode { Single, Combined { parent_layer_range: Range, @@ -333,7 +333,7 @@ impl SyntaxSnapshot { from_version: &clock::Global, text: &BufferSnapshot, registry: Option>, - language: Arc, + root_language: Arc, ) { let edits = text.edits_since::(from_version).collect::>(); let max_depth = self.layers.summary().max_depth; @@ -344,9 +344,9 @@ impl SyntaxSnapshot { let mut changed_regions = ChangeRegionSet::default(); let mut queue = BinaryHeap::new(); let mut combined_injection_ranges = HashMap::default(); - queue.push(ReparseStep { + queue.push(ParseStep { depth: 0, - language: language.clone(), + language: root_language.clone(), included_ranges: vec![tree_sitter::Range { start_byte: 0, end_byte: text.len(), @@ -354,7 +354,7 @@ impl SyntaxSnapshot { end_point: text.max_point().to_ts_point(), }], range: Anchor::MIN..Anchor::MAX, - mode: ReparseMode::Single, + mode: ParseMode::Single, }); loop { @@ -394,7 +394,7 @@ impl SyntaxSnapshot { while target.cmp(&cursor.end(text), text).is_gt() { let Some(layer) = cursor.item() else { break }; - if changed_regions.intersects(&layer, text) { + if changed_regions.intersects(&layer, text) && !layer.combined { changed_regions.insert( ChangedRegion { depth: layer.depth + 1, @@ -430,18 +430,17 @@ impl SyntaxSnapshot { } } - let mut combined = false; + let combined = matches!(step.mode, ParseMode::Combined { .. }); let mut included_ranges = step.included_ranges; let tree; let changed_ranges; if let Some(old_layer) = old_layer { - if let ReparseMode::Combined { + if let ParseMode::Combined { parent_layer_changed_ranges, .. } = step.mode { - combined = true; included_ranges = splice_included_ranges( old_layer.tree.included_ranges(), &parent_layer_changed_ranges, @@ -484,7 +483,7 @@ impl SyntaxSnapshot { depth: step.depth, range: step.range, tree: tree.clone(), - language: language.clone(), + language: step.language.clone(), combined, }, &text, @@ -974,13 +973,21 @@ fn get_injections( depth: usize, changed_ranges: &[Range], combined_injection_ranges: &mut HashMap, Vec>, - queue: &mut BinaryHeap, + queue: &mut BinaryHeap, ) -> bool { let mut result = false; let mut query_cursor = QueryCursorHandle::new(); let mut prev_match = None; combined_injection_ranges.clear(); + for pattern in &config.patterns { + if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) { + if let Some(language) = language_registry.get_language(language_name) { + combined_injection_ranges.insert(language, Vec::new()); + } + } + } + for query_range in changed_ranges { query_cursor.set_byte_range(query_range.start.saturating_sub(1)..query_range.end); for mat in query_cursor.matches(&config.query, node, TextProvider(text.as_rope())) { @@ -1020,16 +1027,16 @@ fn get_injections( ..text.anchor_after(content_range.end); if combined { combined_injection_ranges - .entry(language.clone()) - .or_default() + .get_mut(&language.clone()) + .unwrap() .extend(content_ranges); } else { - queue.push(ReparseStep { + queue.push(ParseStep { depth, language, included_ranges: content_ranges, range, - mode: ReparseMode::Single, + mode: ParseMode::Single, }); } } @@ -1040,12 +1047,12 @@ fn get_injections( for (language, mut included_ranges) in combined_injection_ranges.drain() { included_ranges.sort_unstable(); let range = text.anchor_before(node.start_byte())..text.anchor_after(node.end_byte()); - queue.push(ReparseStep { + queue.push(ParseStep { depth, language, range, included_ranges, - mode: ReparseMode::Combined { + mode: ParseMode::Combined { parent_layer_range: node.start_byte()..node.end_byte(), parent_layer_changed_ranges: changed_ranges.to_vec(), }, @@ -1081,7 +1088,8 @@ fn splice_included_ranges( }; let end_ix = ranges_ix + match ranges[ranges_ix..].binary_search_by_key(&changed.end, |r| r.start_byte) { - Ok(ix) | Err(ix) => ix, + Ok(ix) => ix + 1, + Err(ix) => ix, }; if end_ix > start_ix { ranges.splice(start_ix..end_ix, []); @@ -1113,21 +1121,21 @@ impl std::ops::Deref for SyntaxMap { } } -impl PartialEq for ReparseStep { +impl PartialEq for ParseStep { fn eq(&self, _: &Self) -> bool { false } } -impl Eq for ReparseStep {} +impl Eq for ParseStep {} -impl PartialOrd for ReparseStep { +impl PartialOrd for ParseStep { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(&other)) } } -impl Ord for ReparseStep { +impl Ord for ParseStep { fn cmp(&self, other: &Self) -> Ordering { let range_a = self.range(); let range_b = other.range(); @@ -1138,9 +1146,9 @@ impl Ord for ReparseStep { } } -impl ReparseStep { +impl ParseStep { fn range(&self) -> Range { - if let ReparseMode::Combined { + if let ParseMode::Combined { parent_layer_range, .. } = &self.mode { @@ -1415,6 +1423,9 @@ mod tests { ] ); + let new_ranges = splice_included_ranges(ranges.clone(), &[30..50], &[ts_range(25..55)]); + assert_eq!(new_ranges, &[ts_range(25..55), ts_range(80..90)]); + fn ts_range(range: Range) -> tree_sitter::Range { tree_sitter::Range { start_byte: range.start, @@ -1530,21 +1541,24 @@ mod tests { #[gpui::test] fn test_typing_multiple_new_injections() { - let (buffer, syntax_map) = test_edit_sequence(&[ - "fn a() { dbg }", - "fn a() { dbg«!» }", - "fn a() { dbg!«()» }", - "fn a() { dbg!(«b») }", - "fn a() { dbg!(b«.») }", - "fn a() { dbg!(b.«c») }", - "fn a() { dbg!(b.c«()») }", - "fn a() { dbg!(b.c(«vec»)) }", - "fn a() { dbg!(b.c(vec«!»)) }", - "fn a() { dbg!(b.c(vec!«[]»)) }", - "fn a() { dbg!(b.c(vec![«d»])) }", - "fn a() { dbg!(b.c(vec![d«.»])) }", - "fn a() { dbg!(b.c(vec![d.«e»])) }", - ]); + let (buffer, syntax_map) = test_edit_sequence( + "Rust", + &[ + "fn a() { dbg }", + "fn a() { dbg«!» }", + "fn a() { dbg!«()» }", + "fn a() { dbg!(«b») }", + "fn a() { dbg!(b«.») }", + "fn a() { dbg!(b.«c») }", + "fn a() { dbg!(b.c«()») }", + "fn a() { dbg!(b.c(«vec»)) }", + "fn a() { dbg!(b.c(vec«!»)) }", + "fn a() { dbg!(b.c(vec!«[]»)) }", + "fn a() { dbg!(b.c(vec![«d»])) }", + "fn a() { dbg!(b.c(vec![d«.»])) }", + "fn a() { dbg!(b.c(vec![d.«e»])) }", + ], + ); assert_capture_ranges( &syntax_map, @@ -1556,29 +1570,32 @@ mod tests { #[gpui::test] fn test_pasting_new_injection_line_between_others() { - let (buffer, syntax_map) = test_edit_sequence(&[ - " - fn a() { - b!(B {}); - c!(C {}); - d!(D {}); - e!(E {}); - f!(F {}); - g!(G {}); - } - ", - " - fn a() { - b!(B {}); - c!(C {}); - d!(D {}); - « h!(H {}); - » e!(E {}); - f!(F {}); - g!(G {}); - } - ", - ]); + let (buffer, syntax_map) = test_edit_sequence( + "Rust", + &[ + " + fn a() { + b!(B {}); + c!(C {}); + d!(D {}); + e!(E {}); + f!(F {}); + g!(G {}); + } + ", + " + fn a() { + b!(B {}); + c!(C {}); + d!(D {}); + « h!(H {}); + » e!(E {}); + f!(F {}); + g!(G {}); + } + ", + ], + ); assert_capture_ranges( &syntax_map, @@ -1600,28 +1617,31 @@ mod tests { #[gpui::test] fn test_joining_injections_with_child_injections() { - let (buffer, syntax_map) = test_edit_sequence(&[ - " - fn a() { - b!( - c![one.two.three], - d![four.five.six], - ); - e!( - f![seven.eight], - ); - } - ", - " - fn a() { - b!( - c![one.two.three], - d![four.five.six], - ˇ f![seven.eight], - ); - } - ", - ]); + let (buffer, syntax_map) = test_edit_sequence( + "Rust", + &[ + " + fn a() { + b!( + c![one.two.three], + d![four.five.six], + ); + e!( + f![seven.eight], + ); + } + ", + " + fn a() { + b!( + c![one.two.three], + d![four.five.six], + ˇ f![seven.eight], + ); + } + ", + ], + ); assert_capture_ranges( &syntax_map, @@ -1641,128 +1661,193 @@ mod tests { #[gpui::test] fn test_editing_edges_of_injection() { - test_edit_sequence(&[ - " - fn a() { - b!(c!()) - } - ", - " - fn a() { - «d»!(c!()) - } - ", - " - fn a() { - «e»d!(c!()) - } - ", - " - fn a() { - ed!«[»c!()«]» - } + test_edit_sequence( + "Rust", + &[ + " + fn a() { + b!(c!()) + } + ", + " + fn a() { + «d»!(c!()) + } + ", + " + fn a() { + «e»d!(c!()) + } + ", + " + fn a() { + ed!«[»c!()«]» + } ", - ]); + ], + ); } #[gpui::test] fn test_edits_preceding_and_intersecting_injection() { - test_edit_sequence(&[ - // - "const aaaaaaaaaaaa: B = c!(d(e.f));", - "const aˇa: B = c!(d(eˇ));", - ]); + test_edit_sequence( + "Rust", + &[ + // + "const aaaaaaaaaaaa: B = c!(d(e.f));", + "const aˇa: B = c!(d(eˇ));", + ], + ); } #[gpui::test] fn test_non_local_changes_create_injections() { - test_edit_sequence(&[ - " - // a! { - static B: C = d; - // } - ", - " - ˇa! { - static B: C = d; - ˇ} - ", - ]); + test_edit_sequence( + "Rust", + &[ + " + // a! { + static B: C = d; + // } + ", + " + ˇa! { + static B: C = d; + ˇ} + ", + ], + ); } #[gpui::test] fn test_creating_many_injections_in_one_edit() { - test_edit_sequence(&[ - " - fn a() { - one(Two::three(3)); - four(Five::six(6)); - seven(Eight::nine(9)); - } - ", - " - fn a() { - one«!»(Two::three(3)); - four«!»(Five::six(6)); - seven«!»(Eight::nine(9)); - } - ", - " - fn a() { - one!(Two::three«!»(3)); - four!(Five::six«!»(6)); - seven!(Eight::nine«!»(9)); - } - ", - ]); + test_edit_sequence( + "Rust", + &[ + " + fn a() { + one(Two::three(3)); + four(Five::six(6)); + seven(Eight::nine(9)); + } + ", + " + fn a() { + one«!»(Two::three(3)); + four«!»(Five::six(6)); + seven«!»(Eight::nine(9)); + } + ", + " + fn a() { + one!(Two::three«!»(3)); + four!(Five::six«!»(6)); + seven!(Eight::nine«!»(9)); + } + ", + ], + ); } #[gpui::test] fn test_editing_across_injection_boundary() { - test_edit_sequence(&[ - " - fn one() { - two(); - three!( - three.four, - five.six, - ); - } - ", - " - fn one() { - two(); - th«irty_five![» - three.four, - five.six, - « seven.eight, - ];» - } - ", - ]); + test_edit_sequence( + "Rust", + &[ + " + fn one() { + two(); + three!( + three.four, + five.six, + ); + } + ", + " + fn one() { + two(); + th«irty_five![» + three.four, + five.six, + « seven.eight, + ];» + } + ", + ], + ); } #[gpui::test] fn test_removing_injection_by_replacing_across_boundary() { - test_edit_sequence(&[ - " - fn one() { - two!( - three.four, - ); - } - ", + test_edit_sequence( + "Rust", + &[ + " + fn one() { + two!( + three.four, + ); + } + ", + " + fn one() { + t«en + .eleven( + twelve, + » + three.four, + ); + } + ", + ], + ); + } + + #[gpui::test] + fn test_combined_injections() { + let (buffer, syntax_map) = test_edit_sequence( + "ERB", + &[ + " + + <% if @one %> +
+ <% else %> +
+ <% end %> +
+ + ", + " + + <% if @one %> +
+ ˇ else ˇ +
+ <% end %> +
+ + ", + " + + <% if @one «;» end %> +
+ + ", + ], + ); + + assert_capture_ranges( + &syntax_map, + &buffer, + &["tag", "ivar"], " - fn one() { - t«en - .eleven( - twelve, - » - three.four, - ); - } + <«body»> + <% if «@one» ; end %> + + ", - ]); + ); } #[gpui::test(iterations = 100)] @@ -1952,10 +2037,13 @@ mod tests { } } - fn test_edit_sequence(steps: &[&str]) -> (Buffer, SyntaxMap) { + fn test_edit_sequence(language_name: &str, steps: &[&str]) -> (Buffer, SyntaxMap) { let registry = Arc::new(LanguageRegistry::test()); - let language = Arc::new(rust_lang()); - registry.add(language.clone()); + registry.add(Arc::new(rust_lang())); + registry.add(Arc::new(ruby_lang())); + registry.add(Arc::new(html_lang())); + registry.add(Arc::new(erb_lang())); + let language = registry.get_language(language_name).unwrap(); let mut buffer = Buffer::new(0, 0, Default::default()); let mut mutated_syntax_map = SyntaxMap::new(); @@ -2001,6 +2089,72 @@ mod tests { (buffer, mutated_syntax_map) } + fn html_lang() -> Language { + Language::new( + LanguageConfig { + name: "HTML".into(), + path_suffixes: vec!["html".to_string()], + ..Default::default() + }, + Some(tree_sitter_html::language()), + ) + .with_highlights_query( + r#" + (tag_name) @tag + (erroneous_end_tag_name) @tag + (attribute_name) @property + "#, + ) + .unwrap() + } + + fn ruby_lang() -> Language { + Language::new( + LanguageConfig { + name: "Ruby".into(), + path_suffixes: vec!["rb".to_string()], + ..Default::default() + }, + Some(tree_sitter_ruby::language()), + ) + .with_highlights_query( + r#" + ["if" "do" "else" "end"] @keyword + (instance_variable) @ivar + "#, + ) + .unwrap() + } + + fn erb_lang() -> Language { + Language::new( + LanguageConfig { + name: "ERB".into(), + path_suffixes: vec!["erb".to_string()], + ..Default::default() + }, + Some(tree_sitter_embedded_template::language()), + ) + .with_highlights_query( + r#" + ["<%" "%>"] @keyword + "#, + ) + .unwrap() + .with_injection_query( + r#" + ((code) @content + (#set! "language" "ruby") + (#set! "combined")) + + ((content) @content + (#set! "language" "html") + (#set! "combined")) + "#, + ) + .unwrap() + } + fn rust_lang() -> Language { Language::new( LanguageConfig { From 86f51ade60f5b7e56c2f03b25163135d4f921809 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 7 Nov 2022 17:32:15 -0800 Subject: [PATCH 05/67] Fix panic in handling edits to combined injections --- crates/language/src/syntax_map.rs | 41 +++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 7cfbd9de2fcf6dc900ec0231e97a84351df734d8..11811b999fb6a5b4c8bf2cfe1a458e9c7f0b4ac5 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -1082,15 +1082,34 @@ fn splice_included_ranges( } if let Some(changed) = changed_range { - let start_ix = ranges_ix + let mut start_ix = ranges_ix + match ranges[ranges_ix..].binary_search_by_key(&changed.start, |r| r.end_byte) { Ok(ix) | Err(ix) => ix, }; - let end_ix = ranges_ix + let mut end_ix = ranges_ix + match ranges[ranges_ix..].binary_search_by_key(&changed.end, |r| r.start_byte) { Ok(ix) => ix + 1, Err(ix) => ix, }; + + // If there are empty ranges, then there may be multiple ranges with the same + // start or end. Expand the splice to include any adjacent ranges. That touch + // the changed range. + while start_ix > 0 { + if ranges[start_ix - 1].end_byte == changed.start { + start_ix -= 1; + } else { + break; + } + } + while let Some(range) = ranges.get(end_ix) { + if range.start_byte == changed.end { + end_ix += 1; + } else { + break; + } + } + if end_ix > start_ix { ranges.splice(start_ix..end_ix, []); } @@ -1850,6 +1869,24 @@ mod tests { ); } + #[gpui::test] + fn test_combined_injections_empty_ranges() { + test_edit_sequence( + "ERB", + &[ + " + <% if @one %> + <% else %> + <% end %> + ", + " + <% if @one %> + ˇ<% end %> + ", + ], + ); + } + #[gpui::test(iterations = 100)] fn test_random_syntax_map_edits(mut rng: StdRng) { let operations = env::var("OPERATIONS") From ea42bc3c9b960bdb866b3c382d3277585bcd0e0a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 8 Nov 2022 10:36:44 -0800 Subject: [PATCH 06/67] Rename some sum_tree seek targets in SyntaxMap --- crates/language/src/syntax_map.rs | 66 +++++++++++++++++-------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 11811b999fb6a5b4c8bf2cfe1a458e9c7f0b4ac5..0a8919d6a348631137927cfb9d681bdad70e9f1f 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -91,7 +91,6 @@ struct SyntaxLayer { range: Range, tree: tree_sitter::Tree, language: Arc, - combined: bool, } #[derive(Debug)] @@ -118,12 +117,15 @@ struct SyntaxLayerPosition { } #[derive(Clone, Debug)] -struct DepthAndMaxPosition(usize, Anchor); +struct ChangeStartPosition { + depth: usize, + position: Anchor, +} #[derive(Clone, Debug)] struct SyntaxLayerPositionBeforeChange { position: SyntaxLayerPosition, - change: DepthAndMaxPosition, + change: ChangeStartPosition, } struct ParseStep { @@ -234,9 +236,12 @@ impl SyntaxSnapshot { // Preserve any layers at this depth that precede the first edit. if let Some((_, edit_range)) = edits.get(first_edit_ix_for_depth) { - let target = DepthAndMaxPosition(depth, edit_range.start); - if target.cmp(&cursor.start(), text).is_gt() { - let slice = cursor.slice(&target, Bias::Left, text); + let position = ChangeStartPosition { + depth, + position: edit_range.start, + }; + if position.cmp(&cursor.start(), text).is_gt() { + let slice = cursor.slice(&position, Bias::Left, text); layers.push_tree(slice, text); } } @@ -359,7 +364,7 @@ impl SyntaxSnapshot { loop { let step = queue.pop(); - let target = if let Some(step) = &step { + let position = if let Some(step) = &step { SyntaxLayerPosition { depth: step.depth, range: step.range.clone(), @@ -374,15 +379,15 @@ impl SyntaxSnapshot { }; let mut done = cursor.item().is_none(); - while !done && target.cmp(&cursor.end(text), &text).is_gt() { + while !done && position.cmp(&cursor.end(text), &text).is_gt() { done = true; - let bounded_target = SyntaxLayerPositionBeforeChange { - position: target.clone(), + let bounded_position = SyntaxLayerPositionBeforeChange { + position: position.clone(), change: changed_regions.start_position(), }; - if bounded_target.cmp(&cursor.start(), &text).is_gt() { - let slice = cursor.slice(&bounded_target, Bias::Left, text); + if bounded_position.cmp(&cursor.start(), &text).is_gt() { + let slice = cursor.slice(&bounded_position, Bias::Left, text); if !slice.is_empty() { layers.push_tree(slice, &text); if changed_regions.prune(cursor.end(text), text) { @@ -391,10 +396,10 @@ impl SyntaxSnapshot { } } - while target.cmp(&cursor.end(text), text).is_gt() { + while position.cmp(&cursor.end(text), text).is_gt() { let Some(layer) = cursor.item() else { break }; - if changed_regions.intersects(&layer, text) && !layer.combined { + if changed_regions.intersects(&layer, text) { changed_regions.insert( ChangedRegion { depth: layer.depth + 1, @@ -430,11 +435,9 @@ impl SyntaxSnapshot { } } - let combined = matches!(step.mode, ParseMode::Combined { .. }); - let mut included_ranges = step.included_ranges; - let tree; let changed_ranges; + let mut included_ranges = step.included_ranges; if let Some(old_layer) = old_layer { if let ParseMode::Combined { parent_layer_changed_ranges, @@ -484,7 +487,6 @@ impl SyntaxSnapshot { range: step.range, tree: tree.clone(), language: step.language.clone(), - combined, }, &text, ); @@ -1074,7 +1076,8 @@ fn splice_included_ranges( let new_range = new_ranges.peek(); let mut changed_range = changed_ranges.peek(); - // process changed ranges before any overlapping new ranges + // Remove ranges that have changed before inserting any new ranges + // into those ranges. if let Some((changed, new)) = changed_range.zip(new_range) { if new.end_byte < changed.start { changed_range = None; @@ -1093,7 +1096,7 @@ fn splice_included_ranges( }; // If there are empty ranges, then there may be multiple ranges with the same - // start or end. Expand the splice to include any adjacent ranges. That touch + // start or end. Expand the splice to include any adjacent ranges that touch // the changed range. while start_ix > 0 { if ranges[start_ix - 1].end_byte == changed.start { @@ -1191,12 +1194,17 @@ impl ChangedRegion { } impl ChangeRegionSet { - fn start_position(&self) -> DepthAndMaxPosition { - self.0 - .first() - .map_or(DepthAndMaxPosition(usize::MAX, Anchor::MAX), |region| { - DepthAndMaxPosition(region.depth, region.range.start) - }) + fn start_position(&self) -> ChangeStartPosition { + self.0.first().map_or( + ChangeStartPosition { + depth: usize::MAX, + position: Anchor::MAX, + }, + |region| ChangeStartPosition { + depth: region.depth, + position: region.range.start, + }, + ) } fn intersects(&self, layer: &SyntaxLayer, text: &BufferSnapshot) -> bool { @@ -1289,10 +1297,10 @@ impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for SyntaxLayerP } } -impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for DepthAndMaxPosition { +impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for ChangeStartPosition { fn cmp(&self, cursor_location: &SyntaxLayerSummary, text: &BufferSnapshot) -> Ordering { - Ord::cmp(&self.0, &cursor_location.max_depth) - .then_with(|| self.1.cmp(&cursor_location.range.end, text)) + Ord::cmp(&self.depth, &cursor_location.max_depth) + .then_with(|| self.position.cmp(&cursor_location.range.end, text)) } } From 7dcd6c920fafb270aebdeb3785fe50259a0ecafb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 8 Nov 2022 11:29:23 -0800 Subject: [PATCH 07/67] Add randomized test for syntax map with combined injections --- crates/language/src/syntax_map.rs | 108 +++++++++++++++++++++++++----- 1 file changed, 92 insertions(+), 16 deletions(-) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 0a8919d6a348631137927cfb9d681bdad70e9f1f..711a65c3e9424e59ce1bad597a1bf85ef2bc9560 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -236,12 +236,12 @@ impl SyntaxSnapshot { // Preserve any layers at this depth that precede the first edit. if let Some((_, edit_range)) = edits.get(first_edit_ix_for_depth) { - let position = ChangeStartPosition { + let target = ChangeStartPosition { depth, position: edit_range.start, }; - if position.cmp(&cursor.start(), text).is_gt() { - let slice = cursor.slice(&position, Bias::Left, text); + if target.cmp(&cursor.start(), text).is_gt() { + let slice = cursor.slice(&target, Bias::Left, text); layers.push_tree(slice, text); } } @@ -261,24 +261,17 @@ impl SyntaxSnapshot { continue; }; - let layer = if let Some(layer) = cursor.item() { - layer - } else { - break; - }; + let Some(layer) = cursor.item() else { break }; let (start_byte, start_point) = layer.range.start.summary::<(usize, Point)>(text); // Ignore edits that end before the start of this layer, and don't consider them // for any subsequent layers at this same depth. loop { - if let Some((_, edit_range)) = edits.get(first_edit_ix_for_depth) { - if edit_range.end.cmp(&layer.range.start, text).is_le() { - first_edit_ix_for_depth += 1; - } else { - break; - } + let Some((_, edit_range)) = edits.get(first_edit_ix_for_depth) else { continue 'outer }; + if edit_range.end.cmp(&layer.range.start, text).is_le() { + first_edit_ix_for_depth += 1; } else { - continue 'outer; + break; } } @@ -1895,7 +1888,7 @@ mod tests { ); } - #[gpui::test(iterations = 100)] + #[gpui::test(iterations = 50)] fn test_random_syntax_map_edits(mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) @@ -1975,6 +1968,89 @@ mod tests { } } + #[gpui::test(iterations = 50)] + fn test_random_syntax_map_edits_with_combined_injections(mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let text = r#" +
+ <% if one?(:two) %> +

+ <%= yield :five %> +

+ <% elsif Six.seven(8) %> +

+ <%= yield :five %> +

+ <% else %> + Ok + <% end %> +
+ "# + .unindent() + .repeat(2); + + let registry = Arc::new(LanguageRegistry::test()); + let language = Arc::new(erb_lang()); + registry.add(language.clone()); + registry.add(Arc::new(ruby_lang())); + registry.add(Arc::new(html_lang())); + let mut buffer = Buffer::new(0, 0, text); + + let mut syntax_map = SyntaxMap::new(); + syntax_map.set_language_registry(registry.clone()); + syntax_map.reparse(language.clone(), &buffer); + + let mut reference_syntax_map = SyntaxMap::new(); + reference_syntax_map.set_language_registry(registry.clone()); + + log::info!("initial text:\n{}", buffer.text()); + + for _ in 0..operations { + let prev_buffer = buffer.snapshot(); + let prev_syntax_map = syntax_map.snapshot(); + + buffer.randomly_edit(&mut rng, 3); + log::info!("text:\n{}", buffer.text()); + + syntax_map.interpolate(&buffer); + check_interpolation(&prev_syntax_map, &syntax_map, &prev_buffer, &buffer); + + syntax_map.reparse(language.clone(), &buffer); + + reference_syntax_map.clear(); + reference_syntax_map.reparse(language.clone(), &buffer); + } + + for i in 0..operations { + let i = operations - i - 1; + buffer.undo(); + log::info!("undoing operation {}", i); + log::info!("text:\n{}", buffer.text()); + + syntax_map.interpolate(&buffer); + syntax_map.reparse(language.clone(), &buffer); + + reference_syntax_map.clear(); + reference_syntax_map.reparse(language.clone(), &buffer); + assert_eq!( + syntax_map.layers(&buffer).len(), + reference_syntax_map.layers(&buffer).len(), + "wrong number of layers after undoing edit {i}" + ); + } + + let layers = syntax_map.layers(&buffer); + let reference_layers = reference_syntax_map.layers(&buffer); + for (edited_layer, reference_layer) in layers.into_iter().zip(reference_layers.into_iter()) + { + assert_eq!(edited_layer.node.to_sexp(), reference_layer.node.to_sexp()); + assert_eq!(edited_layer.node.range(), reference_layer.node.range()); + } + } + fn check_interpolation( old_syntax_map: &SyntaxSnapshot, new_syntax_map: &SyntaxSnapshot, From 2f5004c238cade89ca29fd5c665770e4eb4076aa Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 8 Nov 2022 11:29:57 -0800 Subject: [PATCH 08/67] Add highlight query for ERB --- crates/zed/src/languages/erb/highlights.scm | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 crates/zed/src/languages/erb/highlights.scm diff --git a/crates/zed/src/languages/erb/highlights.scm b/crates/zed/src/languages/erb/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..91b21d081f9c5883ef69e4c38d05dcf6de25dc42 --- /dev/null +++ b/crates/zed/src/languages/erb/highlights.scm @@ -0,0 +1,12 @@ +(comment_directive) @comment + +[ + "<%#" + "<%" + "<%=" + "<%_" + "<%-" + "%>" + "-%>" + "_%>" +] @keyword \ No newline at end of file From 8e70e1934ab9bfff1621e5bdd9cb13ed3ff4896a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 10 Nov 2022 09:33:57 -0700 Subject: [PATCH 09/67] Avoid unwrapping when computing tab description A bug caused the assumptions of this method to be violated. We will fix that in the next commit, but we want to be more conservative in our assumptions here going forward. Co-Authored-By: Antonio Scandurra --- crates/editor/src/items.rs | 86 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 47e06fc545a419e8c2a25af430340e61bd6f6a6c..a80a6b529804e5c0b8f16101030b9fd894f41684 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -819,11 +819,20 @@ impl StatusItemView for CursorPosition { fn path_for_buffer<'a>( buffer: &ModelHandle, - mut height: usize, + height: usize, include_filename: bool, cx: &'a AppContext, ) -> Option> { let file = buffer.read(cx).as_singleton()?.read(cx).file()?; + path_for_file(file, height, include_filename, cx) +} + +fn path_for_file<'a>( + file: &'a dyn language::File, + mut height: usize, + include_filename: bool, + cx: &'a AppContext, +) -> Option> { // Ensure we always render at least the filename. height += 1; @@ -845,13 +854,82 @@ fn path_for_buffer<'a>( if include_filename { Some(full_path.into()) } else { - Some(full_path.parent().unwrap().to_path_buf().into()) + Some(full_path.parent()?.to_path_buf().into()) } } else { - let mut path = file.path().strip_prefix(prefix).unwrap(); + let mut path = file.path().strip_prefix(prefix).ok()?; if !include_filename { - path = path.parent().unwrap(); + path = path.parent()?; } Some(path.into()) } } + +#[cfg(test)] +mod tests { + use super::*; + use gpui::MutableAppContext; + use std::{ + path::{Path, PathBuf}, + sync::Arc, + }; + + #[gpui::test] + fn test_path_for_file(cx: &mut MutableAppContext) { + let file = TestFile { + path: Path::new("").into(), + full_path: PathBuf::from(""), + }; + assert_eq!(path_for_file(&file, 0, false, cx), None); + } + + struct TestFile { + path: Arc, + full_path: PathBuf, + } + + impl language::File for TestFile { + fn path(&self) -> &Arc { + &self.path + } + + fn full_path(&self, _: &gpui::AppContext) -> PathBuf { + self.full_path.clone() + } + + fn as_local(&self) -> Option<&dyn language::LocalFile> { + todo!() + } + + fn mtime(&self) -> std::time::SystemTime { + todo!() + } + + fn file_name<'a>(&'a self, _: &'a gpui::AppContext) -> &'a std::ffi::OsStr { + todo!() + } + + fn is_deleted(&self) -> bool { + todo!() + } + + fn save( + &self, + _: u64, + _: language::Rope, + _: clock::Global, + _: project::LineEnding, + _: &mut MutableAppContext, + ) -> gpui::Task> { + todo!() + } + + fn as_any(&self) -> &dyn std::any::Any { + todo!() + } + + fn to_proto(&self) -> rpc::proto::File { + todo!() + } + } +} From fb03eb7a3c8f2b365033d178f354287c580ce684 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 10 Nov 2022 09:34:16 -0700 Subject: [PATCH 10/67] Store absolute path on server when sharing worktree Co-Authored-By: Antonio Scandurra --- crates/collab/src/integration_tests.rs | 7 +++++++ crates/collab/src/rpc.rs | 6 +++--- crates/collab/src/rpc/store.rs | 16 +++++++++++++--- crates/project/src/worktree.rs | 8 ++++---- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 2bf2701f23bf45462e6b8b0e28e4560a72615c1e..d3bf1f28e52ff13cd508132e0578e7bb1dcca896 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -5987,6 +5987,13 @@ async fn test_random_collaboration( guest_client.username, id ); + assert_eq!( + guest_snapshot.abs_path(), + host_snapshot.abs_path(), + "{} has different abs path than the host for worktree {}", + guest_client.username, + id + ); assert_eq!( guest_snapshot.entries(false).collect::>(), host_snapshot.entries(false).collect::>(), diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 9fd9bef825e0176dc7db6ab71244a40b2272e191..3a92c3ef14745460067ead4051d96c5756e14a51 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -42,7 +42,6 @@ use std::{ marker::PhantomData, net::SocketAddr, ops::{Deref, DerefMut}, - os::unix::prelude::OsStrExt, rc::Rc, sync::{ atomic::{AtomicBool, Ordering::SeqCst}, @@ -1024,7 +1023,7 @@ impl Server { id: *id, root_name: worktree.root_name.clone(), visible: worktree.visible, - abs_path: worktree.abs_path.as_os_str().as_bytes().to_vec(), + abs_path: worktree.abs_path.clone(), }) .collect::>(); @@ -1075,7 +1074,7 @@ impl Server { let message = proto::UpdateWorktree { project_id: project_id.to_proto(), worktree_id: *worktree_id, - abs_path: worktree.abs_path.as_os_str().as_bytes().to_vec(), + abs_path: worktree.abs_path.clone(), root_name: worktree.root_name.clone(), updated_entries: worktree.entries.values().cloned().collect(), removed_entries: Default::default(), @@ -1195,6 +1194,7 @@ impl Server { project_id, worktree_id, &request.payload.root_name, + &request.payload.abs_path, &request.payload.removed_entries, &request.payload.updated_entries, request.payload.scan_id, diff --git a/crates/collab/src/rpc/store.rs b/crates/collab/src/rpc/store.rs index c9358ddc2a356e3f1c3e624f1851d955287c840c..8306978c9c4465453b73d9626e148ee1384c3e20 100644 --- a/crates/collab/src/rpc/store.rs +++ b/crates/collab/src/rpc/store.rs @@ -67,7 +67,7 @@ pub struct Collaborator { #[derive(Default, Serialize)] pub struct Worktree { - pub abs_path: PathBuf, + pub abs_path: Vec, pub root_name: String, pub visible: bool, #[serde(skip)] @@ -773,7 +773,11 @@ impl Store { Worktree { root_name: worktree.root_name, visible: worktree.visible, - ..Default::default() + abs_path: worktree.abs_path.clone(), + entries: Default::default(), + diagnostic_summaries: Default::default(), + scan_id: Default::default(), + is_complete: Default::default(), }, ) }) @@ -852,7 +856,11 @@ impl Store { Worktree { root_name: worktree.root_name.clone(), visible: worktree.visible, - ..Default::default() + abs_path: worktree.abs_path.clone(), + entries: Default::default(), + diagnostic_summaries: Default::default(), + scan_id: Default::default(), + is_complete: false, }, ); } @@ -1006,6 +1014,7 @@ impl Store { project_id: ProjectId, worktree_id: u64, worktree_root_name: &str, + worktree_abs_path: &[u8], removed_entries: &[u64], updated_entries: &[proto::Entry], scan_id: u64, @@ -1016,6 +1025,7 @@ impl Store { let connection_ids = project.connection_ids(); let mut worktree = project.worktrees.entry(worktree_id).or_default(); worktree.root_name = worktree_root_name.to_string(); + worktree.abs_path = worktree_abs_path.to_vec(); for entry_id in removed_entries { worktree.entries.remove(entry_id); diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index db8fb8e3ff8e4cea90a33c805311d4032302a890..055aa0670631f04caf269eccb7c343b31114f92e 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -1179,6 +1179,10 @@ impl Snapshot { self.id } + pub fn abs_path(&self) -> &Arc { + &self.abs_path + } + pub fn contains_entry(&self, entry_id: ProjectEntryId) -> bool { self.entries_by_id.get(&entry_id, &()).is_some() } @@ -1370,10 +1374,6 @@ impl Snapshot { } impl LocalSnapshot { - pub fn abs_path(&self) -> &Arc { - &self.abs_path - } - pub fn extension_counts(&self) -> &HashMap { &self.extension_counts } From 03115c8d719183e23bf6c19c9a44ceb65d18ffe7 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 10 Nov 2022 15:28:11 -0500 Subject: [PATCH 11/67] Skip LSP additional completion edits which fall within primary edit --- crates/project/src/project.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 3c28f6b512e38f9f803398641b7b9676beec234f..0675610dcb7a27e3c08d687089c39068b0cdec16 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3453,29 +3453,39 @@ impl Project { let buffer_id = buffer.remote_id(); if self.is_local() { - let lang_server = if let Some((_, server)) = self.language_server_for_buffer(buffer, cx) - { - server.clone() - } else { - return Task::ready(Ok(Default::default())); + let lang_server = match self.language_server_for_buffer(buffer, cx) { + Some((_, server)) => server.clone(), + _ => return Task::ready(Ok(Default::default())), }; cx.spawn(|this, mut cx| async move { let resolved_completion = lang_server .request::(completion.lsp_completion) .await?; + if let Some(edits) = resolved_completion.additional_text_edits { let edits = this .update(&mut cx, |this, cx| { this.edits_from_lsp(&buffer_handle, edits, None, cx) }) .await?; + buffer_handle.update(&mut cx, |buffer, cx| { buffer.finalize_last_transaction(); buffer.start_transaction(); + for (range, text) in edits { - buffer.edit([(range, text)], None, cx); + let primary = &completion.old_range; + let within_primary = primary.start.cmp(&range.start, buffer).is_ge() + && primary.end.cmp(&range.end, buffer).is_le(); + let within_additional = range.start.cmp(&primary.start, buffer).is_ge() + && range.end.cmp(&primary.end, buffer).is_le(); + + if !within_primary && !within_additional { + buffer.edit([(range, text)], None, cx); + } } + let transaction = if buffer.end_transaction(cx).is_some() { let transaction = buffer.finalize_last_transaction().unwrap().clone(); if !push_to_history { From 44c3cedc48e5f3245429e694eee7aa9af9db0a78 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 10 Nov 2022 18:53:37 -0500 Subject: [PATCH 12/67] Skip additional completions on any kind of overlap with primary edit --- crates/project/src/project.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0675610dcb7a27e3c08d687089c39068b0cdec16..0e2723201a7dab20fdd2c5f71048e81ad4a4af8f 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3476,12 +3476,12 @@ impl Project { for (range, text) in edits { let primary = &completion.old_range; - let within_primary = primary.start.cmp(&range.start, buffer).is_ge() - && primary.end.cmp(&range.end, buffer).is_le(); - let within_additional = range.start.cmp(&primary.start, buffer).is_ge() - && range.end.cmp(&primary.end, buffer).is_le(); + let start_within = primary.start.cmp(&range.start, buffer).is_le() + && primary.end.cmp(&range.start, buffer).is_ge(); + let end_within = range.start.cmp(&primary.end, buffer).is_le() + && range.end.cmp(&primary.end, buffer).is_ge(); - if !within_primary && !within_additional { + if !start_within && !end_within { buffer.edit([(range, text)], None, cx); } } From 9ad8731897ec8d070d7ca695c0539d26361577b6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 10 Nov 2022 17:04:40 -0800 Subject: [PATCH 13/67] Fix boundary condition where injection was not found after an edit --- crates/language/src/syntax_map.rs | 33 ++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 711a65c3e9424e59ce1bad597a1bf85ef2bc9560..026e4857c54ef10a48ec814a3d14ddad407874c0 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -984,7 +984,7 @@ fn get_injections( } for query_range in changed_ranges { - query_cursor.set_byte_range(query_range.start.saturating_sub(1)..query_range.end); + query_cursor.set_byte_range(query_range.start.saturating_sub(1)..query_range.end + 1); for mat in query_cursor.matches(&config.query, node, TextProvider(text.as_rope())) { let content_ranges = mat .nodes_for_capture_index(config.content_capture_ix) @@ -1888,6 +1888,37 @@ mod tests { ); } + #[gpui::test] + fn test_combined_injections_edit_edges_of_ranges() { + let (buffer, syntax_map) = test_edit_sequence( + "ERB", + &[ + " + <%= one @two %> + <%= three @four %> + ", + " + <%= one @two %ˇ + <%= three @four %> + ", + " + <%= one @two %«>» + <%= three @four %> + ", + ], + ); + + assert_capture_ranges( + &syntax_map, + &buffer, + &["tag", "ivar"], + " + <%= one «@two» %> + <%= three «@four» %> + ", + ); + } + #[gpui::test(iterations = 50)] fn test_random_syntax_map_edits(mut rng: StdRng) { let operations = env::var("OPERATIONS") From d61c0fb24c39c1de1ab896d97071b72c6788c7b8 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Thu, 10 Nov 2022 20:43:55 -0800 Subject: [PATCH 14/67] Allow dragging and dropping project entries --- crates/drag_and_drop/src/drag_and_drop.rs | 80 ++++++++++------- crates/project_panel/src/project_panel.rs | 104 +++++++++++++++++++++- 2 files changed, 150 insertions(+), 34 deletions(-) diff --git a/crates/drag_and_drop/src/drag_and_drop.rs b/crates/drag_and_drop/src/drag_and_drop.rs index 65f5985edf7b8c93fc11c1ed877f5b0c33fe1f70..6884de7e2087d9dc73a4cb4eeef3f25f3e15dd4d 100644 --- a/crates/drag_and_drop/src/drag_and_drop.rs +++ b/crates/drag_and_drop/src/drag_and_drop.rs @@ -4,12 +4,16 @@ use collections::HashSet; use gpui::{ elements::{Empty, MouseEventHandler, Overlay}, geometry::{rect::RectF, vector::Vector2F}, - scene::MouseDrag, + scene::{MouseDown, MouseDrag}, CursorStyle, Element, ElementBox, EventContext, MouseButton, MutableAppContext, RenderContext, View, WeakViewHandle, }; enum State { + Down { + region_offset: Vector2F, + region: RectF, + }, Dragging { window_id: usize, position: Vector2F, @@ -24,6 +28,13 @@ enum State { impl Clone for State { fn clone(&self) -> Self { match self { + &State::Down { + region_offset, + region, + } => State::Down { + region_offset, + region, + }, State::Dragging { window_id, position, @@ -87,6 +98,15 @@ impl DragAndDrop { }) } + pub fn drag_started(event: MouseDown, cx: &mut EventContext) { + cx.update_global(|this: &mut Self, _| { + this.currently_dragged = Some(State::Down { + region_offset: event.region.origin() - event.position, + region: event.region, + }); + }) + } + pub fn dragging( event: MouseDrag, payload: Rc, @@ -94,37 +114,32 @@ impl DragAndDrop { render: Rc) -> ElementBox>, ) { let window_id = cx.window_id(); - cx.update_global::(|this, cx| { + cx.update_global(|this: &mut Self, cx| { this.notify_containers_for_window(window_id, cx); - if matches!(this.currently_dragged, Some(State::Canceled)) { - return; + match this.currently_dragged.as_ref() { + Some(&State::Down { + region_offset, + region, + }) + | Some(&State::Dragging { + region_offset, + region, + .. + }) => { + this.currently_dragged = Some(State::Dragging { + window_id, + region_offset, + region, + position: event.position, + payload, + render: Rc::new(move |payload, cx| { + render(payload.downcast_ref::().unwrap(), cx) + }), + }); + } + _ => {} } - - let (region_offset, region) = if let Some(State::Dragging { - region_offset, - region, - .. - }) = this.currently_dragged.as_ref() - { - (*region_offset, *region) - } else { - ( - event.region.origin() - event.prev_mouse_position, - event.region, - ) - }; - - this.currently_dragged = Some(State::Dragging { - window_id, - region_offset, - region, - position: event.position, - payload, - render: Rc::new(move |payload, cx| { - render(payload.downcast_ref::().unwrap(), cx) - }), - }); }); } @@ -135,6 +150,7 @@ impl DragAndDrop { .clone() .and_then(|state| { match state { + State::Down { .. } => None, State::Dragging { window_id, region_offset, @@ -263,7 +279,11 @@ impl Draggable for MouseEventHandler { { let payload = Rc::new(payload); let render = Rc::new(render); - self.on_drag(MouseButton::Left, move |e, cx| { + self.on_down(MouseButton::Left, move |e, cx| { + cx.propagate_event(); + DragAndDrop::::drag_started(e, cx); + }) + .on_drag(MouseButton::Left, move |e, cx| { let payload = payload.clone(); let render = render.clone(); DragAndDrop::::dragging(e, payload, cx, render) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 3eb5d685166b569d2c6d73b09c086afc10949c55..b6787c930c1cc3bab3cd201db0b1dd6649d5256e 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -43,6 +43,7 @@ pub struct ProjectPanel { filename_editor: ViewHandle, clipboard_entry: Option, context_menu: ViewHandle, + dragged_entry_destination: Option>, } #[derive(Copy, Clone)] @@ -95,6 +96,13 @@ pub struct Open { pub change_focus: bool, } +#[derive(Clone, PartialEq)] +pub struct MoveProjectEntry { + pub entry_to_move: ProjectEntryId, + pub destination: ProjectEntryId, + pub destination_is_file: bool, +} + #[derive(Clone, PartialEq)] pub struct DeployContextMenu { pub position: Vector2F, @@ -117,7 +125,10 @@ actions!( ToggleFocus ] ); -impl_internal_actions!(project_panel, [Open, ToggleExpanded, DeployContextMenu]); +impl_internal_actions!( + project_panel, + [Open, ToggleExpanded, DeployContextMenu, MoveProjectEntry] +); pub fn init(cx: &mut MutableAppContext) { cx.add_action(ProjectPanel::deploy_context_menu); @@ -141,6 +152,7 @@ pub fn init(cx: &mut MutableAppContext) { this.paste(action, cx); }, ); + cx.add_action(ProjectPanel::move_entry); } pub enum Event { @@ -219,6 +231,7 @@ impl ProjectPanel { filename_editor, clipboard_entry: None, context_menu: cx.add_view(ContextMenu::new), + dragged_entry_destination: None, }; this.update_visible_entries(None, cx); this @@ -774,6 +787,39 @@ impl ProjectPanel { } } + fn move_entry( + &mut self, + &MoveProjectEntry { + entry_to_move, + destination, + destination_is_file, + }: &MoveProjectEntry, + cx: &mut ViewContext, + ) { + let destination_worktree = self.project.update(cx, |project, cx| { + let entry_path = project.path_for_entry(entry_to_move, cx)?; + let destination_entry_path = project.path_for_entry(destination, cx)?.path.clone(); + + let mut destination_path = destination_entry_path.as_ref(); + if destination_is_file { + destination_path = destination_path.parent()?; + } + + let mut new_path = destination_path.to_path_buf(); + new_path.push(entry_path.path.file_name()?); + if new_path != entry_path.path.as_ref() { + let task = project.rename_entry(entry_to_move, new_path, cx)?; + cx.foreground().spawn(task).detach_and_log_err(cx); + } + + Some(project.worktree_id_for_entry(destination, cx)?) + }); + + if let Some(destination_worktree) = destination_worktree { + self.expand_entry(destination_worktree, destination, cx); + } + } + fn index_for_selection(&self, selection: Selection) -> Option<(usize, usize, usize)> { let mut entry_index = 0; let mut visible_entries_index = 0; @@ -1079,10 +1125,13 @@ impl ProjectPanel { entry_id: ProjectEntryId, details: EntryDetails, editor: &ViewHandle, + dragged_entry_destination: &mut Option>, theme: &theme::ProjectPanel, cx: &mut RenderContext, ) -> ElementBox { + let this = cx.handle(); let kind = details.kind; + let path = details.path.clone(); let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width; let entry_style = if details.is_cut { @@ -1096,7 +1145,20 @@ impl ProjectPanel { let show_editor = details.is_editing && !details.is_processing; MouseEventHandler::::new(entry_id.to_usize(), cx, |state, cx| { - let style = entry_style.style_for(state, details.is_selected).clone(); + let mut style = entry_style.style_for(state, details.is_selected).clone(); + + if cx + .global::>() + .currently_dragged::(cx.window_id()) + .is_some() + && dragged_entry_destination + .as_ref() + .filter(|destination| details.path.starts_with(destination)) + .is_some() + { + style = entry_style.active.clone().unwrap(); + } + let row_container_style = if show_editor { theme.filename_editor.container } else { @@ -1128,6 +1190,35 @@ impl ProjectPanel { position: e.position, }) }) + .on_up(MouseButton::Left, move |_, cx| { + if let Some((_, dragged_entry)) = cx + .global::>() + .currently_dragged::(cx.window_id()) + { + cx.dispatch_action(MoveProjectEntry { + entry_to_move: *dragged_entry, + destination: entry_id, + destination_is_file: matches!(details.kind, EntryKind::File(_)), + }); + } + }) + .on_move(move |_, cx| { + if cx + .global::>() + .currently_dragged::(cx.window_id()) + .is_some() + { + if let Some(this) = this.upgrade(cx.app) { + this.update(cx.app, |this, _| { + this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) { + path.parent().map(|parent| Arc::from(parent)) + } else { + Some(path.clone()) + }; + }) + } + } + }) .as_draggable(entry_id, { let row_container_style = theme.dragged_entry.container; @@ -1154,14 +1245,15 @@ impl View for ProjectPanel { } fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox { - enum Tag {} + enum ProjectPanel {} let theme = &cx.global::().theme.project_panel; let mut container_style = theme.container; let padding = std::mem::take(&mut container_style.padding); let last_worktree_root_id = self.last_worktree_root_id; + Stack::new() .with_child( - MouseEventHandler::::new(0, cx, |_, cx| { + MouseEventHandler::::new(0, cx, |_, cx| { UniformList::new( self.list.clone(), self.visible_entries @@ -1171,15 +1263,19 @@ impl View for ProjectPanel { cx, move |this, range, items, cx| { let theme = cx.global::().theme.clone(); + let mut dragged_entry_destination = + this.dragged_entry_destination.clone(); this.for_each_visible_entry(range, cx, |id, details, cx| { items.push(Self::render_entry( id, details, &this.filename_editor, + &mut dragged_entry_destination, &theme.project_panel, cx, )); }); + this.dragged_entry_destination = dragged_entry_destination; }, ) .with_padding_top(padding.top) From ad698fd11002c7ff24f5657d9637796d66ce4dc8 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 11 Nov 2022 10:28:07 -0500 Subject: [PATCH 15/67] Test for filtering out of faulty LSP completion additional edits --- crates/editor/src/editor_tests.rs | 47 +++++++++++++++++++++---------- crates/project/src/project.rs | 2 ++ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 763917a46489739ad41e6c5eb501ab5275c819ed..7bd5dda522f6298067619074ce79647e07f1b133 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -4146,14 +4146,26 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { handle_resolve_completion_request( &mut cx, - Some(( - indoc! {" - one.second_completion - two - threeˇ - "}, - "\nadditional edit", - )), + Some(vec![ + ( + //This overlaps with the primary completion edit which is + //misbehavior from the LSP spec, test that we filter it out + indoc! {" + one.second_ˇcompletion + two + threeˇ + "}, + "overlapping aditional edit", + ), + ( + indoc! {" + one.second_completion + two + threeˇ + "}, + "\nadditional edit", + ), + ]), ) .await; apply_additional_edits.await.unwrap(); @@ -4303,19 +4315,24 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { async fn handle_resolve_completion_request<'a>( cx: &mut EditorLspTestContext<'a>, - edit: Option<(&'static str, &'static str)>, + edits: Option>, ) { - let edit = edit.map(|(marked_string, new_text)| { - let (_, marked_ranges) = marked_text_ranges(marked_string, false); - let replace_range = cx.to_lsp_range(marked_ranges[0].clone()); - vec![lsp::TextEdit::new(replace_range, new_text.to_string())] + let edits = edits.map(|edits| { + edits + .iter() + .map(|(marked_string, new_text)| { + let (_, marked_ranges) = marked_text_ranges(marked_string, false); + let replace_range = cx.to_lsp_range(marked_ranges[0].clone()); + lsp::TextEdit::new(replace_range, new_text.to_string()) + }) + .collect::>() }); cx.handle_request::(move |_, _, _| { - let edit = edit.clone(); + let edits = edits.clone(); async move { Ok(lsp::CompletionItem { - additional_text_edits: edit, + additional_text_edits: edits, ..Default::default() }) } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0e2723201a7dab20fdd2c5f71048e81ad4a4af8f..1563cb9de4268b66802e239107ce310b32b5ec21 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3481,6 +3481,8 @@ impl Project { let end_within = range.start.cmp(&primary.end, buffer).is_le() && range.end.cmp(&primary.end, buffer).is_ge(); + //Skip addtional edits which overlap with the primary completion edit + //https://github.com/zed-industries/zed/pull/1871 if !start_within && !end_within { buffer.edit([(range, text)], None, cx); } From 5bb7701de7f51138ad249af6a2d0aeae503ca15a Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 11 Nov 2022 14:00:01 -0500 Subject: [PATCH 16/67] Propagate mouse up event through drop receiver in early return --- crates/workspace/src/pane/dragged_item_receiver.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/workspace/src/pane/dragged_item_receiver.rs b/crates/workspace/src/pane/dragged_item_receiver.rs index 7a71bfe0e5c372ebd0fe60df9055282b36d3866d..b110252250eda8d9862cd39f3cee7946b888123e 100644 --- a/crates/workspace/src/pane/dragged_item_receiver.rs +++ b/crates/workspace/src/pane/dragged_item_receiver.rs @@ -118,6 +118,7 @@ pub fn handle_dropped_item( { Action::Open(*project_entry) } else { + cx.propagate_event(); return; }; From ea8778921b31652b1152f41e34a779ff08592045 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 11 Nov 2022 15:26:12 -0500 Subject: [PATCH 17/67] Use `EMPTY` code action kind to get more RA actions without breaking TS --- crates/project/src/project.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 1563cb9de4268b66802e239107ce310b32b5ec21..e1a0b9c00a5bc8489ed1ba817feadcfce7599227 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3585,7 +3585,13 @@ impl Project { partial_result_params: Default::default(), context: lsp::CodeActionContext { diagnostics: relevant_diagnostics, - only: None, + only: Some(vec![ + lsp::CodeActionKind::EMPTY, + lsp::CodeActionKind::QUICKFIX, + lsp::CodeActionKind::REFACTOR, + lsp::CodeActionKind::REFACTOR_EXTRACT, + lsp::CodeActionKind::SOURCE, + ]), }, }) .await? From 3612c46d6d7748c06dc025b8a398104e96035284 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 11 Nov 2022 16:36:04 -0800 Subject: [PATCH 18/67] Bump tree-sitter for included range bugfix --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc127b24c40cb5a5f589edba88d7273b7c7288f1..17ac0a2283d9cacd6c87ece51829b041a42b4462 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6383,7 +6383,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.20.9" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=da6e24de1751aef6a944adfcefb192b751c56f76#da6e24de1751aef6a944adfcefb192b751c56f76" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=d07f864815ecb1e0f1f0bab17fec80438eb4c455#d07f864815ecb1e0f1f0bab17fec80438eb4c455" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index 8ac180fcc12156f7149305651ce5ba5e6a48f246..ac8bf018f910d6bde525990ef0df912d41a65708 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } rand = { version = "0.8" } [patch.crates-io] -tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "da6e24de1751aef6a944adfcefb192b751c56f76" } +tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "d07f864815ecb1e0f1f0bab17fec80438eb4c455" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 From ee66adbb492b06d4ce7acf72e2fbfc280c978288 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 11 Nov 2022 16:43:57 -0800 Subject: [PATCH 19/67] SyntaxMap - Don't ignore deletions at the boundaries of layers --- crates/language/src/syntax_map.rs | 32 +++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 026e4857c54ef10a48ec814a3d14ddad407874c0..d843f5e85b03dc742b013ec4ecf31d24f61bae94 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -453,10 +453,9 @@ impl SyntaxSnapshot { Some(old_layer.tree.clone()), ); changed_ranges = join_ranges( - edits - .iter() - .map(|e| e.new.clone()) - .filter(|range| range.start < step_end_byte && range.end > step_start_byte), + edits.iter().map(|e| e.new.clone()).filter(|range| { + range.start <= step_end_byte && range.end >= step_start_byte + }), old_layer .tree .changed_ranges(&tree) @@ -1919,6 +1918,31 @@ mod tests { ); } + #[gpui::test] + fn test_combined_injections_splitting_some_injections() { + let (_buffer, _syntax_map) = test_edit_sequence( + "ERB", + &[ + r#" + <%A if b(:c) %> + d + <% end %> + eee + <% f %> + "#, + r#" + <%« AAAAAAA %> + hhhhhhh + <%=» if b(:c) %> + d + <% end %> + eee + <% f %> + "#, + ], + ); + } + #[gpui::test(iterations = 50)] fn test_random_syntax_map_edits(mut rng: StdRng) { let operations = env::var("OPERATIONS") From 1da5be6e8fee5b42752100cd8729ccf2355f47b8 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Sat, 12 Nov 2022 21:39:08 -0500 Subject: [PATCH 20/67] Update release urls to match new zed.dev url format --- .github/workflows/release_actions.yml | 2 +- crates/auto_update/src/auto_update.rs | 9 ++++++++- crates/zed/src/main.rs | 15 --------------- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 65866baf7f18c03486d5a3cbc808f4681dc10c16..3866ee6c7b6751ca4a34b24ec01850bfc4bf2199 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -14,7 +14,7 @@ jobs: content: | 📣 Zed ${{ github.event.release.tag_name }} was just released! - Restart your Zed or head to https://zed.dev/releases to grab it. + Restart your Zed or head to https://zed.dev/releases/latest to grab it. ```md ### Changelog diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index d73523c8bd0a603c6dd39723de78a27fa21f6719..bda45053b1330377f4ac7bec66d80244a344774e 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -70,7 +70,14 @@ pub fn init(db: project::Db, http_client: Arc, cx: &mut MutableA } }); cx.add_global_action(move |_: &ViewReleaseNotes, cx| { - cx.platform().open_url(&format!("{server_url}/releases")); + let latest_release_url = if cx.has_global::() + && *cx.global::() == ReleaseChannel::Preview + { + format!("{server_url}/releases/preview/latest") + } else { + format!("{server_url}/releases/latest") + }; + cx.platform().open_url(&latest_release_url); }); cx.add_action(UpdateNotification::dismiss); } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e849632a2df38945fcf34bf8b5967491f19df9e9..c6862e66e41e01d3a51ffa068ed7a00e5e09d6fa 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -213,21 +213,6 @@ fn init_paths() { std::fs::create_dir_all(&*zed::paths::LANGUAGES_DIR).expect("could not create languages path"); std::fs::create_dir_all(&*zed::paths::DB_DIR).expect("could not create database path"); std::fs::create_dir_all(&*zed::paths::LOGS_DIR).expect("could not create logs path"); - - // Copy setting files from legacy locations. TODO: remove this after a few releases. - thread::spawn(|| { - if std::fs::metadata(&*zed::paths::legacy::SETTINGS).is_ok() - && std::fs::metadata(&*zed::paths::SETTINGS).is_err() - { - std::fs::copy(&*zed::paths::legacy::SETTINGS, &*zed::paths::SETTINGS).log_err(); - } - - if std::fs::metadata(&*zed::paths::legacy::KEYMAP).is_ok() - && std::fs::metadata(&*zed::paths::KEYMAP).is_err() - { - std::fs::copy(&*zed::paths::legacy::KEYMAP, &*zed::paths::KEYMAP).log_err(); - } - }); } fn init_logger() { From a66aa9c09cc71df8cfede2dafe6b09a0f2568020 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 14 Nov 2022 10:20:55 -0800 Subject: [PATCH 21/67] Refactored rendering to squash all wakeups into 1 --- crates/terminal/src/terminal.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 473bbd4f52aa2ebbf4885830cded6d44252fa75c..014eeecc0c701e4a9e58147d67e3a97cd624f0df 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -407,13 +407,18 @@ impl TerminalBuilder { 'outer: loop { let mut events = vec![]; let mut timer = cx.background().timer(Duration::from_millis(4)).fuse(); - + let mut wakeup = false; loop { futures::select_biased! { _ = timer => break, event = self.events_rx.next() => { if let Some(event) = event { - events.push(event); + if matches!(event, AlacTermEvent::Wakeup) { + wakeup = true; + } else { + events.push(event); + } + if events.len() > 100 { break; } @@ -432,6 +437,9 @@ impl TerminalBuilder { for event in events { this.process_event(&event, cx); } + if wakeup { + this.process_event(&AlacTermEvent::Wakeup, cx); + } }); smol::future::yield_now().await; } From 6659dac2e5beb14f8b740c665be78b89b40dd34f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 14 Nov 2022 11:12:25 -0800 Subject: [PATCH 22/67] Fix compile errors in seed script, ensure it is compiled on CI Co-authored-by: Nate Butler --- .github/workflows/ci.yml | 2 +- crates/collab/src/bin/seed.rs | 78 ++++------------------------------- 2 files changed, 10 insertions(+), 70 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13dcf4fef1d357b76ad2d11ebfc5eb589852be29..7072a3fe94cc66746bbe04f6d045af58b89bd433 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: run: cargo build -p collab - name: Build other binaries - run: cargo build --bins --all-features + run: cargo build --workspace --bins --all-features bundle: name: Bundle app diff --git a/crates/collab/src/bin/seed.rs b/crates/collab/src/bin/seed.rs index cabea7d013776d4f3cb248d1b0c8985a0f3090a2..324ccdc0c63716813115a7e645a277c314980d23 100644 --- a/crates/collab/src/bin/seed.rs +++ b/crates/collab/src/bin/seed.rs @@ -1,9 +1,7 @@ use collab::{Error, Result}; -use db::{Db, PostgresDb, UserId}; -use rand::prelude::*; +use db::DefaultDb; use serde::{de::DeserializeOwned, Deserialize}; use std::fmt::Write; -use time::{Duration, OffsetDateTime}; #[allow(unused)] #[path = "../db.rs"] @@ -18,9 +16,8 @@ struct GitHubUser { #[tokio::main] async fn main() { - let mut rng = StdRng::from_entropy(); let database_url = std::env::var("DATABASE_URL").expect("missing DATABASE_URL env var"); - let db = PostgresDb::new(&database_url, 5) + let db = DefaultDb::new(&database_url, 5) .await .expect("failed to connect to postgres database"); let github_token = std::env::var("GITHUB_TOKEN").expect("missing GITHUB_TOKEN env var"); @@ -64,16 +61,14 @@ async fn main() { } } - let mut zed_user_ids = Vec::::new(); for (github_user, admin) in zed_users { - if let Some(user) = db + if db .get_user_by_github_account(&github_user.login, Some(github_user.id)) .await .expect("failed to fetch user") + .is_none() { - zed_user_ids.push(user.id); - } else if let Some(email) = &github_user.email { - zed_user_ids.push( + if let Some(email) = &github_user.email { db.create_user( email, admin, @@ -84,11 +79,8 @@ async fn main() { }, ) .await - .expect("failed to insert user") - .user_id, - ); - } else if admin { - zed_user_ids.push( + .expect("failed to insert user"); + } else if admin { db.create_user( &format!("{}@zed.dev", github_user.login), admin, @@ -99,61 +91,9 @@ async fn main() { }, ) .await - .expect("failed to insert user") - .user_id, - ); - } - } - - let zed_org_id = if let Some(org) = db - .find_org_by_slug("zed") - .await - .expect("failed to fetch org") - { - org.id - } else { - db.create_org("Zed", "zed") - .await - .expect("failed to insert org") - }; - - let general_channel_id = if let Some(channel) = db - .get_org_channels(zed_org_id) - .await - .expect("failed to fetch channels") - .iter() - .find(|c| c.name == "General") - { - channel.id - } else { - let channel_id = db - .create_org_channel(zed_org_id, "General") - .await - .expect("failed to insert channel"); - - let now = OffsetDateTime::now_utc(); - let max_seconds = Duration::days(100).as_seconds_f64(); - let mut timestamps = (0..1000) - .map(|_| now - Duration::seconds_f64(rng.gen_range(0_f64..=max_seconds))) - .collect::>(); - timestamps.sort(); - for timestamp in timestamps { - let sender_id = *zed_user_ids.choose(&mut rng).unwrap(); - let body = lipsum::lipsum_words(rng.gen_range(1..=50)); - db.create_channel_message(channel_id, sender_id, &body, timestamp, rng.gen()) - .await - .expect("failed to insert message"); + .expect("failed to insert user"); + } } - channel_id - }; - - for user_id in zed_user_ids { - db.add_org_member(zed_org_id, user_id, true) - .await - .expect("failed to insert org membership"); - db.add_channel_member(general_channel_id, user_id, true) - .await - .expect("failed to insert channel membership"); } } From fb356313375c10eccf175d141395310bb2167ad7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 14 Nov 2022 16:56:09 -0800 Subject: [PATCH 23/67] Bump tree-sitter after merging included-ranges PR --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17ac0a2283d9cacd6c87ece51829b041a42b4462..158791ac97f7efe9faefcc931b185c1a313c1519 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6383,7 +6383,7 @@ dependencies = [ [[package]] name = "tree-sitter" version = "0.20.9" -source = "git+https://github.com/tree-sitter/tree-sitter?rev=d07f864815ecb1e0f1f0bab17fec80438eb4c455#d07f864815ecb1e0f1f0bab17fec80438eb4c455" +source = "git+https://github.com/tree-sitter/tree-sitter?rev=36b5b6c89e55ad1a502f8b3234bb3e12ec83a5da#36b5b6c89e55ad1a502f8b3234bb3e12ec83a5da" dependencies = [ "cc", "regex", diff --git a/Cargo.toml b/Cargo.toml index ac8bf018f910d6bde525990ef0df912d41a65708..8e9814c4481c0472033d8818776c5edba946cf6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } rand = { version = "0.8" } [patch.crates-io] -tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "d07f864815ecb1e0f1f0bab17fec80438eb4c455" } +tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "36b5b6c89e55ad1a502f8b3234bb3e12ec83a5da" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 From b222e8eb5a97ce3023e2bf0ecf90dcce373c9678 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 14 Nov 2022 16:56:21 -0800 Subject: [PATCH 24/67] Use a longer example text in random combined injections test --- crates/language/src/syntax_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index d843f5e85b03dc742b013ec4ecf31d24f61bae94..65d01e949317bf64ab7ee65258df3c0c848e5602 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -2045,7 +2045,7 @@ mod tests {
"# .unindent() - .repeat(2); + .repeat(8); let registry = Arc::new(LanguageRegistry::test()); let language = Arc::new(erb_lang()); From 01929037f123aa1f9008b920b74d3fa4a403cd82 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 15 Nov 2022 12:02:09 -0800 Subject: [PATCH 25/67] fixed clear problem --- crates/terminal/src/mappings/keys.rs | 12 ------------ crates/terminal/src/terminal.rs | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index 199f42df65c26a9a32b3fbff239f49c7079b7b33..ddcd6c58984d5e21e087c5c0c4f0262a1e2bb4ab 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -36,18 +36,6 @@ impl Modifiers { } } -///This function checks if to_esc_str would work, assuming all terminal settings are off. -///Note that this function is conservative. It can fail in cases where the actual to_esc_str succeeds. -///This is unavoidable for our use case. GPUI cannot wait until we acquire the terminal -///lock to determine whether we could actually send the keystroke with the current settings. Therefore, -///This conservative guess is used instead. Note that in practice the case where this method -///Returns false when the actual terminal would consume the keystroke never happens. All keystrokes -///that depend on terminal modes also have a mapping that doesn't depend on the terminal mode. -///This is fragile, but as these mappings are locked up in legacy compatibility, it's probably good enough -pub fn might_convert(keystroke: &Keystroke) -> bool { - to_esc_str(keystroke, &TermMode::NONE, false).is_some() -} - pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode, alt_is_meta: bool) -> Option { let modifiers = Modifiers::new(keystroke); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 761dad6675f36a5b14fe9347e3f74ade7f13c295..5d52a664decbababf809c54fc49140fbcddf23d1 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -635,7 +635,7 @@ impl Terminal { term.grid_mut().reset_region(..cursor.line); // Copy the current line up - let line = term.grid()[cursor.line][..cursor.column] + let line = term.grid()[cursor.line][..Column(term.grid().columns())] .iter() .cloned() .enumerate() From 36c07f940c41f29610e1b79f32dae535d0fbb49c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 15 Nov 2022 12:34:43 -0800 Subject: [PATCH 26/67] Add ruby LSP support via SolarGraph --- crates/zed/src/languages.rs | 13 ++- crates/zed/src/languages/ruby.rs | 145 +++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 crates/zed/src/languages/ruby.rs diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index 76bb4394dd5c238963c1680512317c186f60448c..4c33e7329f829797e1900c346052b3262d2a5959 100644 --- a/crates/zed/src/languages.rs +++ b/crates/zed/src/languages.rs @@ -12,6 +12,7 @@ mod installation; mod json; mod language_plugin; mod python; +mod ruby; mod rust; mod typescript; @@ -116,8 +117,16 @@ pub async fn init(languages: Arc, _executor: Arc) tree_sitter_html::language(), Some(CachedLspAdapter::new(html::HtmlLspAdapter).await), ), - ("ruby", tree_sitter_ruby::language(), None), - ("erb", tree_sitter_embedded_template::language(), None), + ( + "ruby", + tree_sitter_ruby::language(), + Some(CachedLspAdapter::new(ruby::RubyLanguageServer).await), + ), + ( + "erb", + tree_sitter_embedded_template::language(), + Some(CachedLspAdapter::new(ruby::RubyLanguageServer).await), + ), ] { languages.add(language(name, grammar, lsp_adapter)); } diff --git a/crates/zed/src/languages/ruby.rs b/crates/zed/src/languages/ruby.rs new file mode 100644 index 0000000000000000000000000000000000000000..6aad293d34a1ac674aa1b01c30a1dc218d4aa92d --- /dev/null +++ b/crates/zed/src/languages/ruby.rs @@ -0,0 +1,145 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use client::http::HttpClient; +use language::{LanguageServerName, LspAdapter}; +use std::{any::Any, path::PathBuf, sync::Arc}; + +pub struct RubyLanguageServer; + +#[async_trait] +impl LspAdapter for RubyLanguageServer { + async fn name(&self) -> LanguageServerName { + LanguageServerName("solargraph".into()) + } + + async fn server_args(&self) -> Vec { + vec!["stdio".into()] + } + + async fn fetch_latest_server_version( + &self, + _: Arc, + ) -> Result> { + Ok(Box::new(())) + } + + async fn fetch_server_binary( + &self, + _version: Box, + _: Arc, + _container_dir: PathBuf, + ) -> Result { + Err(anyhow!("solargraph must be installed manually")) + } + + async fn cached_server_binary(&self, _container_dir: PathBuf) -> Option { + Some("solargraph".into()) + } + + async fn label_for_completion( + &self, + item: &lsp::CompletionItem, + language: &Arc, + ) -> Option { + let label = &item.label; + let grammar = language.grammar()?; + let highlight_id = match item.kind? { + lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?, + lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?, + lsp::CompletionItemKind::CLASS | lsp::CompletionItemKind::MODULE => { + grammar.highlight_id_for_name("type")? + } + lsp::CompletionItemKind::KEYWORD => { + if label.starts_with(":") { + grammar.highlight_id_for_name("string.special.symbol")? + } else { + grammar.highlight_id_for_name("keyword")? + } + } + lsp::CompletionItemKind::VARIABLE => { + if label.starts_with("@") { + grammar.highlight_id_for_name("property")? + } else { + return None; + } + } + _ => return None, + }; + Some(language::CodeLabel { + text: label.clone(), + runs: vec![(0..label.len(), highlight_id)], + filter_range: 0..label.len(), + }) + } + + async fn label_for_symbol( + &self, + label: &str, + kind: lsp::SymbolKind, + language: &Arc, + ) -> Option { + let grammar = language.grammar()?; + match kind { + lsp::SymbolKind::METHOD => { + let mut parts = label.split('#'); + let classes = parts.next()?; + let method = parts.next()?; + if parts.next().is_some() { + return None; + } + + let class_id = grammar.highlight_id_for_name("type")?; + let method_id = grammar.highlight_id_for_name("function.method")?; + + let mut ix = 0; + let mut runs = Vec::new(); + for (i, class) in classes.split("::").enumerate() { + if i > 0 { + ix += 2; + } + let end_ix = ix + class.len(); + runs.push((ix..end_ix, class_id)); + ix = end_ix; + } + + ix += 1; + let end_ix = ix + method.len(); + runs.push((ix..end_ix, method_id)); + Some(language::CodeLabel { + text: label.to_string(), + runs, + filter_range: 0..label.len(), + }) + } + lsp::SymbolKind::CONSTANT => { + let constant_id = grammar.highlight_id_for_name("constant")?; + Some(language::CodeLabel { + text: label.to_string(), + runs: vec![(0..label.len(), constant_id)], + filter_range: 0..label.len(), + }) + } + lsp::SymbolKind::CLASS | lsp::SymbolKind::MODULE => { + let class_id = grammar.highlight_id_for_name("type")?; + + let mut ix = 0; + let mut runs = Vec::new(); + for (i, class) in label.split("::").enumerate() { + if i > 0 { + ix += "::".len(); + } + let end_ix = ix + class.len(); + runs.push((ix..end_ix, class_id)); + ix = end_ix; + } + + Some(language::CodeLabel { + text: label.to_string(), + runs, + filter_range: 0..label.len(), + }) + } + _ => return None, + } + } +} From fdf758e05075e15325a798107dced49063dfa423 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 15 Nov 2022 15:36:59 -0700 Subject: [PATCH 27/67] Once we email someone an invite, honor the invitation Previously, we were waiting to decrement the invite_count until a user confirmed their email address, which created weird situations where we would email people only to have them get a 500 when trying to sign up. Now, we decrement the invite_count upon sending the email and always honor the invitation. Co-Authored-By: Joseph Lyons Co-Authored-By: Max Brunsfeld --- crates/collab/src/db.rs | 46 +++++++++-------------------------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 10da609d57b9b7cfe04927b681b378c07e099b4b..281c1763608c27968fb0a157bb1b886f5e8719c8 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -350,25 +350,6 @@ impl Db { .await?; if let Some(inviting_user_id) = inviting_user_id { - let id: Option = sqlx::query_scalar( - " - UPDATE users - SET invite_count = invite_count - 1 - WHERE id = $1 AND invite_count > 0 - RETURNING id - ", - ) - .bind(&inviting_user_id) - .fetch_optional(&mut tx) - .await?; - - if id.is_none() { - Err(Error::Http( - StatusCode::UNAUTHORIZED, - "no invites remaining".to_string(), - ))?; - } - sqlx::query( " INSERT INTO contacts @@ -453,31 +434,24 @@ impl Db { Err(anyhow!("email address is already in use"))?; } - let row: Option<(UserId, i32)> = sqlx::query_as( + let inviting_user_id_with_invites: Option = sqlx::query_scalar( " - SELECT id, invite_count - FROM users - WHERE invite_code = $1 + UPDATE users + SET invite_count = invite_count - 1 + WHERE invite_code = $1 AND invite_count > 0 + RETURNING id ", ) .bind(code) .fetch_optional(&mut tx) .await?; - let (inviter_id, invite_count) = match row { - Some(row) => row, - None => Err(Error::Http( - StatusCode::NOT_FOUND, - "invite code not found".to_string(), - ))?, - }; - - if invite_count == 0 { - Err(Error::Http( + let Some(inviter_id) = inviting_user_id_with_invites else { + return Err(Error::Http( StatusCode::UNAUTHORIZED, - "no invites remaining".to_string(), - ))?; - } + "unable to find an invite code with invites remaining".to_string(), + )); + }; let email_confirmation_code: String = sqlx::query_scalar( " From 275f0ae4926a9a32641f0d563f593a1addf8a766 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 15 Nov 2022 15:45:04 -0700 Subject: [PATCH 28/67] collab 0.2.3 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- crates/drag_and_drop/Cargo.toml | 2 +- crates/journal/Cargo.toml | 2 +- crates/project_symbols/Cargo.toml | 2 +- crates/theme_testbench/Cargo.toml | 2 +- crates/vim/Cargo.toml | 2 +- crates/workspace/Cargo.toml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 558a68770375f812f1d71db46d755f0ce356a710..d6cb733ce6902d31fe93eb29d86cbb91f166961d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1028,7 +1028,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.2.2" +version = "0.2.3" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 7456cb5598f64bd497fd2b73252ac40219e439b6..33a6ffa6e3c648d396da1f1fb3a5bd27d88dda45 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.2.2" +version = "0.2.3" [[bin]] name = "collab" diff --git a/crates/drag_and_drop/Cargo.toml b/crates/drag_and_drop/Cargo.toml index 2fd8ce27b8b48af1ce241aa98076c329a4e39c93..4ab54ad8e659dddb1ba79f066db895c2e56c79fc 100644 --- a/crates/drag_and_drop/Cargo.toml +++ b/crates/drag_and_drop/Cargo.toml @@ -12,4 +12,4 @@ collections = { path = "../collections" } gpui = { path = "../gpui" } [dev-dependencies] -gpui = { path = "../gpui", features = ["test-support"] } \ No newline at end of file +gpui = { path = "../gpui", features = ["test-support"] } diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml index 8c900d9f4a24f517b48218b1d38c34e8dffd539d..9622049a9cf2cafc80b9f703fa851f81afa8c632 100644 --- a/crates/journal/Cargo.toml +++ b/crates/journal/Cargo.toml @@ -16,4 +16,4 @@ chrono = "0.4" dirs = "4.0" log = { version = "0.4.16", features = ["kv_unstable_serde"] } settings = { path = "../settings" } -shellexpand = "2.1.0" \ No newline at end of file +shellexpand = "2.1.0" diff --git a/crates/project_symbols/Cargo.toml b/crates/project_symbols/Cargo.toml index cb1f186bdec84d2aa9bcf2b11d094ce22a2ef6ae..a426e2e0d49ce55c64374dca325b23f7aa17eda5 100644 --- a/crates/project_symbols/Cargo.toml +++ b/crates/project_symbols/Cargo.toml @@ -28,4 +28,4 @@ settings = { path = "../settings", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } lsp = { path = "../lsp", features = ["test-support"] } -project = { path = "../project", features = ["test-support"] } \ No newline at end of file +project = { path = "../project", features = ["test-support"] } diff --git a/crates/theme_testbench/Cargo.toml b/crates/theme_testbench/Cargo.toml index 9cb063c1e5c9646845b4d5a6edacb1f54e22b0c9..5fb263501f0adf99851010dbfc00b2e4561ca80d 100644 --- a/crates/theme_testbench/Cargo.toml +++ b/crates/theme_testbench/Cargo.toml @@ -15,4 +15,4 @@ settings = { path = "../settings" } workspace = { path = "../workspace" } project = { path = "../project" } -smallvec = { version = "1.6", features = ["union"] } \ No newline at end of file +smallvec = { version = "1.6", features = ["union"] } diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index 44f2a8cb16487481fba513bbe0d0771894a7b773..daefebdbdda7fabc308b4103efccd5aa7fafb11a 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -42,4 +42,4 @@ language = { path = "../language", features = ["test-support"] } project = { path = "../project", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } settings = { path = "../settings" } -workspace = { path = "../workspace", features = ["test-support"] } \ No newline at end of file +workspace = { path = "../workspace", features = ["test-support"] } diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 54e7eaf463a344049bc8b1b5d97a64eddaea1b07..2db4ef2d3ddbd7fc4a60429729944533bdc95ffe 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -46,4 +46,4 @@ client = { path = "../client", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } project = { path = "../project", features = ["test-support"] } settings = { path = "../settings", features = ["test-support"] } -fs = { path = "../fs", features = ["test-support"] } \ No newline at end of file +fs = { path = "../fs", features = ["test-support"] } From c3cf056fc512ffeda6d4348451954c527ee24bb7 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 15 Nov 2022 20:04:56 -0500 Subject: [PATCH 29/67] allow users to sign up multiple times without throwing a 500 --- crates/collab/src/api.rs | 2 +- crates/collab/src/db.rs | 10 ++++---- crates/collab/src/db_tests.rs | 45 ++++++++++++++++++++++------------- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 5fcdc5fcfdf59a983d3d4c04d98242eb3d97fa41..eb750bed550a8e935a837567127f3ebfe20ccb00 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -338,7 +338,7 @@ async fn create_signup( Json(params): Json, Extension(app): Extension>, ) -> Result<()> { - app.db.create_signup(params).await?; + app.db.create_signup(¶ms).await?; Ok(()) } diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 281c1763608c27968fb0a157bb1b886f5e8719c8..1609764f6e0d5b88d41c56300c977e024eaa8c83 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -157,7 +157,7 @@ impl Db { unimplemented!() } - pub async fn create_signup(&self, _signup: Signup) -> Result<()> { + pub async fn create_signup(&self, _signup: &Signup) -> Result<()> { unimplemented!() } @@ -375,7 +375,7 @@ impl Db { }) } - pub async fn create_signup(&self, signup: Signup) -> Result<()> { + pub async fn create_signup(&self, signup: &Signup) -> Result<()> { test_support!(self, { sqlx::query( " @@ -394,6 +394,8 @@ impl Db { ) VALUES ($1, $2, FALSE, $3, $4, $5, FALSE, $6, $7, $8) + ON CONFLICT (email_address) DO UPDATE SET + email_address = excluded.email_address RETURNING id ", ) @@ -1259,7 +1261,7 @@ pub struct IncomingContactRequest { pub should_notify: bool, } -#[derive(Clone, Deserialize)] +#[derive(Clone, Deserialize, Default)] pub struct Signup { pub email_address: String, pub platform_mac: bool, @@ -1284,7 +1286,7 @@ pub struct WaitlistSummary { pub unknown_count: i64, } -#[derive(FromRow, PartialEq, Debug, Serialize, Deserialize)] +#[derive(Clone, FromRow, PartialEq, Debug, Serialize, Deserialize)] pub struct Invite { pub email_address: String, pub email_confirmation_code: String, diff --git a/crates/collab/src/db_tests.rs b/crates/collab/src/db_tests.rs index 8eda7d34e298c975e53140c9ce3a7aed1551b706..c4e95f10ce5a6a184242017f70c864a271639017 100644 --- a/crates/collab/src/db_tests.rs +++ b/crates/collab/src/db_tests.rs @@ -644,10 +644,14 @@ async fn test_signups() { let test_db = PostgresTestDb::new(build_background_executor()); let db = test_db.db(); + let usernames = (0..8).map(|i| format!("person-{i}")).collect::>(); + // people sign up on the waitlist - for i in 0..8 { - db.create_signup(Signup { - email_address: format!("person-{i}@example.com"), + let all_signups = usernames + .iter() + .enumerate() + .map(|(i, username)| Signup { + email_address: format!("{username}@example.com"), platform_mac: true, platform_linux: i % 2 == 0, platform_windows: i % 4 == 0, @@ -655,8 +659,13 @@ async fn test_signups() { programming_languages: vec!["rust".into(), "c".into()], device_id: Some(format!("device_id_{i}")), }) - .await - .unwrap(); + .collect::>(); + + for signup in &all_signups { + // Users can sign up multiple times without issues + for _ in 0..2 { + db.create_signup(&signup).await.unwrap(); + } } assert_eq!( @@ -679,9 +688,9 @@ async fn test_signups() { assert_eq!( addresses, &[ - "person-0@example.com", - "person-1@example.com", - "person-2@example.com" + all_signups[0].email_address.as_str(), + all_signups[1].email_address.as_str(), + all_signups[2].email_address.as_str() ] ); assert_ne!( @@ -705,9 +714,9 @@ async fn test_signups() { assert_eq!( addresses, &[ - "person-3@example.com", - "person-4@example.com", - "person-5@example.com" + all_signups[3].email_address.as_str(), + all_signups[4].email_address.as_str(), + all_signups[5].email_address.as_str() ] ); @@ -733,11 +742,10 @@ async fn test_signups() { } = db .create_user_from_invite( &Invite { - email_address: signups_batch1[0].email_address.clone(), - email_confirmation_code: signups_batch1[0].email_confirmation_code.clone(), + ..signups_batch1[0].clone() }, NewUserParams { - github_login: "person-0".into(), + github_login: usernames[0].clone(), github_user_id: 0, invite_count: 5, }, @@ -747,8 +755,11 @@ async fn test_signups() { .unwrap(); let user = db.get_user_by_id(user_id).await.unwrap().unwrap(); assert!(inviting_user_id.is_none()); - assert_eq!(user.github_login, "person-0"); - assert_eq!(user.email_address.as_deref(), Some("person-0@example.com")); + assert_eq!(user.github_login, usernames[0]); + assert_eq!( + user.email_address, + Some(all_signups[0].email_address.clone()) + ); assert_eq!(user.invite_count, 5); assert_eq!(signup_device_id.unwrap(), "device_id_0"); @@ -776,7 +787,7 @@ async fn test_signups() { email_confirmation_code: "the-wrong-code".to_string(), }, NewUserParams { - github_login: "person-1".into(), + github_login: usernames[1].clone(), github_user_id: 2, invite_count: 5, }, From 3c53fcdb4330ccc0fd2cbbe0dc47187b397c78b7 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 16 Nov 2022 09:59:23 -0800 Subject: [PATCH 30/67] Added alt-left: move word left and alt-right: move word right in the terminal for for antonio --- assets/keymaps/default.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 7c89eacde844b109a0b8996c0697d67d85d0432f..774d8fbc9ee0183c6a98b0f0c79f92bf9c6a4b3e 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -472,6 +472,15 @@ "terminal::SendText", "\u0001" ], + // Terminal.app compatability + "alt-left": [ + "terminal::SendText", + "\u001bb" + ], + "alt-right": [ + "terminal::SendText", + "\u001bf" + ], // There are conflicting bindings for these keys in the global context. // these bindings override them, remove at your own risk: "up": [ From 8e6c5dbc3b06bc2343345ed0193cfc1a88b1540a Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 16 Nov 2022 10:44:13 -0800 Subject: [PATCH 31/67] Fix unscaled scrolling when using an imprecise mouse wheel --- crates/editor/src/element.rs | 10 ++++-- crates/gpui/src/elements/flex.rs | 12 ++++--- crates/gpui/src/elements/list.rs | 4 +-- crates/gpui/src/elements/uniform_list.rs | 14 ++++++--- crates/gpui/src/platform/event.rs | 40 ++++++++++++++++++++++-- crates/gpui/src/platform/mac/event.rs | 19 +++++++---- crates/terminal/src/mappings/mouse.rs | 2 +- crates/terminal/src/terminal.rs | 10 +++--- 8 files changed, 83 insertions(+), 28 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 25100037d79f2261cb28a73e2e07ecb6642163d8..f62f27bb0d8747d9779fda0814d0708411909d53 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -192,8 +192,14 @@ impl EditorElement { .on_scroll({ let position_map = position_map.clone(); move |e, cx| { - if !Self::scroll(e.position, e.delta, e.precise, &position_map, bounds, cx) - { + if !Self::scroll( + e.position, + *e.delta.raw(), + e.delta.precise(), + &position_map, + bounds, + cx, + ) { cx.propagate_event() } } diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index 129d36dadd3818e8d215ad4a1e85d5850c9add66..f6a1a5d8e6e52ec572835d34fe7cc9e0655f664a 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -257,17 +257,19 @@ impl Element for Flex { let axis = self.axis; move |e, cx| { if remaining_space < 0. { + let scroll_delta = e.delta.raw(); + let mut delta = match axis { Axis::Horizontal => { - if e.delta.x().abs() >= e.delta.y().abs() { - e.delta.x() + if scroll_delta.x().abs() >= scroll_delta.y().abs() { + scroll_delta.x() } else { - e.delta.y() + scroll_delta.y() } } - Axis::Vertical => e.delta.y(), + Axis::Vertical => scroll_delta.y(), }; - if !e.precise { + if !e.delta.precise() { delta *= 20.; } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index f1b747d6471230a040fba88859dff75449406aef..53a0b70b358b66d3186132412a6fab371d29ead7 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -258,8 +258,8 @@ impl Element for List { state.0.borrow_mut().scroll( &scroll_top, height, - e.platform_event.delta, - e.platform_event.precise, + *e.platform_event.delta.raw(), + e.platform_event.delta.precise(), cx, ) } diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 0eab7f0bc9baaee74a2090ecdcd4a00be051c83d..79836b70e86aceea65b6e7eb75a0d66ea3a294f5 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -295,15 +295,19 @@ impl Element for UniformList { move |MouseScrollWheel { platform_event: ScrollWheelEvent { - position, - delta, - precise, - .. + position, delta, .. }, .. }, cx| { - if !Self::scroll(state.clone(), position, delta, precise, scroll_max, cx) { + if !Self::scroll( + state.clone(), + position, + *delta.raw(), + delta.precise(), + scroll_max, + cx, + ) { cx.propagate_event(); } } diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index a309f5123fb7d22c0c3360afbb120bc506222e44..862807a74d8956362baaf25adee7e0c871ddfda9 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -1,5 +1,7 @@ use std::ops::Deref; +use pathfinder_geometry::vector::vec2f; + use crate::{geometry::vector::Vector2F, keymap::Keystroke}; #[derive(Clone, Debug)] @@ -44,11 +46,45 @@ pub enum TouchPhase { Ended, } +#[derive(Clone, Copy, Debug)] +pub enum ScrollDelta { + Pixels(Vector2F), + Lines(Vector2F), +} + +impl Default for ScrollDelta { + fn default() -> Self { + Self::Lines(Default::default()) + } +} + +impl ScrollDelta { + pub fn raw(&self) -> &Vector2F { + match self { + ScrollDelta::Pixels(v) => v, + ScrollDelta::Lines(v) => v, + } + } + + pub fn precise(&self) -> bool { + match self { + ScrollDelta::Pixels(_) => true, + ScrollDelta::Lines(_) => false, + } + } + + pub fn pixel_delta(&self, line_height: f32) -> Vector2F { + match self { + ScrollDelta::Pixels(delta) => *delta, + ScrollDelta::Lines(delta) => vec2f(delta.x() * line_height, delta.y() * line_height), + } + } +} + #[derive(Clone, Copy, Debug, Default)] pub struct ScrollWheelEvent { pub position: Vector2F, - pub delta: Vector2F, - pub precise: bool, + pub delta: ScrollDelta, pub modifiers: Modifiers, /// If the platform supports returning the phase of a scroll wheel event, it will be stored here pub phase: Option, diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index 77d1e67caeff9f84de1e9f74a3e5a1b256dca8f1..36dab7314925eda17052d37fe8fc2b7e619f0c70 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -3,7 +3,7 @@ use crate::{ keymap::Keystroke, platform::{Event, NavigationDirection}, KeyDownEvent, KeyUpEvent, Modifiers, ModifiersChangedEvent, MouseButton, MouseButtonEvent, - MouseMovedEvent, ScrollWheelEvent, TouchPhase, + MouseMovedEvent, ScrollDelta, ScrollWheelEvent, TouchPhase, }; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType}, @@ -164,17 +164,24 @@ impl Event { _ => Some(TouchPhase::Moved), }; + let raw_data = vec2f( + native_event.scrollingDeltaX() as f32, + native_event.scrollingDeltaY() as f32, + ); + + let delta = if native_event.hasPreciseScrollingDeltas() == YES { + ScrollDelta::Pixels(raw_data) + } else { + ScrollDelta::Lines(raw_data) + }; + Self::ScrollWheel(ScrollWheelEvent { position: vec2f( native_event.locationInWindow().x as f32, window_height - native_event.locationInWindow().y as f32, ), - delta: vec2f( - native_event.scrollingDeltaX() as f32, - native_event.scrollingDeltaY() as f32, - ), + delta, phase, - precise: native_event.hasPreciseScrollingDeltas() == YES, modifiers: read_modifiers(native_event), }) }), diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index 2254eea5af0965351c61adc7e0fe1e7baab40097..4dd97aa4c6f14b6b992eeb9f1d0ebe803cd04485 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -97,7 +97,7 @@ impl MouseButton { } fn from_scroll(e: &ScrollWheelEvent) -> Self { - if e.delta.y() > 0. { + if e.delta.raw().y() > 0. { MouseButton::ScrollUp } else { MouseButton::ScrollDown diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 5d52a664decbababf809c54fc49140fbcddf23d1..8b5b20e81e7f4e74cbcbb0e85f0269d0162318dd 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1144,7 +1144,7 @@ impl Terminal { fn determine_scroll_lines(&mut self, e: &MouseScrollWheel, mouse_mode: bool) -> Option { let scroll_multiplier = if mouse_mode { 1. } else { SCROLL_MULTIPLIER }; - + let line_height = self.last_content.size.line_height; match e.phase { /* Reset scroll state on started */ Some(gpui::TouchPhase::Started) => { @@ -1153,11 +1153,11 @@ impl Terminal { } /* Calculate the appropriate scroll lines */ Some(gpui::TouchPhase::Moved) => { - let old_offset = (self.scroll_px / self.last_content.size.line_height) as i32; + let old_offset = (self.scroll_px / line_height) as i32; - self.scroll_px += e.delta.y() * scroll_multiplier; + self.scroll_px += e.delta.pixel_delta(line_height).y() * scroll_multiplier; - let new_offset = (self.scroll_px / self.last_content.size.line_height) as i32; + let new_offset = (self.scroll_px / line_height) as i32; // Whenever we hit the edges, reset our stored scroll to 0 // so we can respond to changes in direction quickly @@ -1167,7 +1167,7 @@ impl Terminal { } /* Fall back to delta / line_height */ None => Some( - ((e.delta.y() * scroll_multiplier) / self.last_content.size.line_height) as i32, + ((e.delta.pixel_delta(line_height).y() * scroll_multiplier) / line_height) as i32, ), _ => None, } From 4e4299d5004f48d3da13cca10bc25a51d46dd39c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 16 Nov 2022 14:22:18 -0800 Subject: [PATCH 32/67] v0.66.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6cb733ce6902d31fe93eb29d86cbb91f166961d..7330f6d0095a1d32259400fbc253f52a0d2bb156 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7673,7 +7673,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.65.0" +version = "0.66.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index bd2e1c0fca994001bcccd2b48a1f4b8f6c7d622d..78349fa797722b585d810934803077329d307991 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.65.0" +version = "0.66.0" [lib] name = "zed" From c613c98e376e822544cfbef5d69fc05cf672d11d Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 16 Nov 2022 17:28:50 -0500 Subject: [PATCH 33/67] Move comment to correct location --- crates/collab/src/db_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/collab/src/db_tests.rs b/crates/collab/src/db_tests.rs index c4e95f10ce5a6a184242017f70c864a271639017..b3f964b8a7b3767f1f1d2d96bc1acdd56990aa82 100644 --- a/crates/collab/src/db_tests.rs +++ b/crates/collab/src/db_tests.rs @@ -646,7 +646,6 @@ async fn test_signups() { let usernames = (0..8).map(|i| format!("person-{i}")).collect::>(); - // people sign up on the waitlist let all_signups = usernames .iter() .enumerate() @@ -661,8 +660,9 @@ async fn test_signups() { }) .collect::>(); + // people sign up on the waitlist for signup in &all_signups { - // Users can sign up multiple times without issues + // users can sign up multiple times without issues for _ in 0..2 { db.create_signup(&signup).await.unwrap(); } From 93824dd2392d7e145449bfb59be55ca024d38baf Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Wed, 16 Nov 2022 20:02:15 -0500 Subject: [PATCH 34/67] Fix top-level header in discord webhook action --- .github/workflows/release_actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml index 65866baf7f18c03486d5a3cbc808f4681dc10c16..9066dbdd814025a23b225575bee374ae66906669 100644 --- a/.github/workflows/release_actions.yml +++ b/.github/workflows/release_actions.yml @@ -17,7 +17,7 @@ jobs: Restart your Zed or head to https://zed.dev/releases to grab it. ```md - ### Changelog + # Changelog ${{ github.event.release.body }} ``` From ce0dfde8ee908d8dfd60579ac641a555f6066cf9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 17 Nov 2022 11:14:31 -0800 Subject: [PATCH 35/67] Check for wakeups correctly --- crates/terminal/src/terminal.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 8b5b20e81e7f4e74cbcbb0e85f0269d0162318dd..7e469e19fec03564140f5063f1ee8e243331d345 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -429,17 +429,18 @@ impl TerminalBuilder { } } - if events.is_empty() { + if events.is_empty() && wakeup == false { smol::future::yield_now().await; break 'outer; } else { this.upgrade(&cx)?.update(&mut cx, |this, cx| { - for event in events { - this.process_event(&event, cx); - } if wakeup { this.process_event(&AlacTermEvent::Wakeup, cx); } + + for event in events { + this.process_event(&event, cx); + } }); smol::future::yield_now().await; } From 5020c70a04fa019a461d9dfd5589241aed1773e1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 17 Nov 2022 11:44:29 -0800 Subject: [PATCH 36/67] collab 0.2.4 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7330f6d0095a1d32259400fbc253f52a0d2bb156..709be66ade028b2db82c94d693ff1323f10ba760 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1028,7 +1028,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 33a6ffa6e3c648d396da1f1fb3a5bd27d88dda45..57a57a00c1b83d675af5d6f2142a560ffbec3b2d 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.2.3" +version = "0.2.4" [[bin]] name = "collab" From 6537def97eedb726468d726d30fbb18bfee51d1e Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 17 Nov 2022 17:01:34 -0500 Subject: [PATCH 37/67] Allow having multiple mouse event handlers of the same kind Co-Authored-By: Kay Simmons --- crates/gpui/src/presenter.rs | 30 ++++--- crates/gpui/src/scene/mouse_event.rs | 22 ++--- crates/gpui/src/scene/mouse_region.rs | 123 ++++++++++++++++++-------- 3 files changed, 113 insertions(+), 62 deletions(-) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index e43e428fe62fbfd782daa82b7da87b30219ebc9a..c988bb330be665b0b2845545fc7808953a7d8284 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -475,27 +475,33 @@ impl Presenter { if let MouseEvent::Down(e) = &mouse_event { if valid_region .handlers - .contains_handler(MouseEvent::click_disc(), Some(e.button)) + .contains(MouseEvent::click_disc(), Some(e.button)) || valid_region .handlers - .contains_handler(MouseEvent::drag_disc(), Some(e.button)) + .contains(MouseEvent::drag_disc(), Some(e.button)) { event_cx.handled = true; } } - if let Some(callback) = valid_region.handlers.get(&mouse_event.handler_key()) { - event_cx.handled = true; - event_cx.with_current_view(valid_region.id().view_id(), { - let region_event = mouse_event.clone(); - |cx| callback(region_event, cx) - }); + // `event_consumed` should only be true if there are any handlers for this event. + let mut event_consumed = false; + if let Some(callbacks) = valid_region.handlers.get(&mouse_event.handler_key()) { + event_consumed = true; + for callback in callbacks { + event_cx.handled = true; + event_cx.with_current_view(valid_region.id().view_id(), { + let region_event = mouse_event.clone(); + |cx| callback(region_event, cx) + }); + event_consumed &= event_cx.handled; + any_event_handled |= event_cx.handled; + } } - any_event_handled = any_event_handled || event_cx.handled; - // For bubbling events, if the event was handled, don't continue dispatching - // This only makes sense for local events. - if event_cx.handled && mouse_event.is_capturable() { + // For bubbling events, if the event was handled, don't continue dispatching. + // This only makes sense for local events which return false from is_capturable. + if event_consumed && mouse_event.is_capturable() { break; } } diff --git a/crates/gpui/src/scene/mouse_event.rs b/crates/gpui/src/scene/mouse_event.rs index d7370ac75f8b5e332e320ad5cf6a95cc781828af..00d1ddbf8bf61ee0457186d30f449d1c3ed95ef4 100644 --- a/crates/gpui/src/scene/mouse_event.rs +++ b/crates/gpui/src/scene/mouse_event.rs @@ -5,7 +5,7 @@ use std::{ use pathfinder_geometry::{rect::RectF, vector::Vector2F}; -use crate::{MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; +use crate::{scene::mouse_region::HandlerKey, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; #[derive(Debug, Default, Clone)] pub struct MouseMove { @@ -217,17 +217,17 @@ impl MouseEvent { discriminant(&MouseEvent::ScrollWheel(Default::default())) } - pub fn handler_key(&self) -> (Discriminant, Option) { + pub fn handler_key(&self) -> HandlerKey { match self { - MouseEvent::Move(_) => (Self::move_disc(), None), - MouseEvent::Drag(e) => (Self::drag_disc(), e.pressed_button), - MouseEvent::Hover(_) => (Self::hover_disc(), None), - MouseEvent::Down(e) => (Self::down_disc(), Some(e.button)), - MouseEvent::Up(e) => (Self::up_disc(), Some(e.button)), - MouseEvent::Click(e) => (Self::click_disc(), Some(e.button)), - MouseEvent::UpOut(e) => (Self::up_out_disc(), Some(e.button)), - MouseEvent::DownOut(e) => (Self::down_out_disc(), Some(e.button)), - MouseEvent::ScrollWheel(_) => (Self::scroll_wheel_disc(), None), + MouseEvent::Move(_) => HandlerKey::new(Self::move_disc(), None), + MouseEvent::Drag(e) => HandlerKey::new(Self::drag_disc(), e.pressed_button), + MouseEvent::Hover(_) => HandlerKey::new(Self::hover_disc(), None), + MouseEvent::Down(e) => HandlerKey::new(Self::down_disc(), Some(e.button)), + MouseEvent::Up(e) => HandlerKey::new(Self::up_disc(), Some(e.button)), + MouseEvent::Click(e) => HandlerKey::new(Self::click_disc(), Some(e.button)), + MouseEvent::UpOut(e) => HandlerKey::new(Self::up_out_disc(), Some(e.button)), + MouseEvent::DownOut(e) => HandlerKey::new(Self::down_out_disc(), Some(e.button)), + MouseEvent::ScrollWheel(_) => HandlerKey::new(Self::scroll_wheel_disc(), None), } } } diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 4b5217cc2dec1355d350f921fa66233d8be39591..0fdc76ebbfefe8204983cd58e4a9f42a310d8024 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -3,6 +3,7 @@ use std::{any::TypeId, fmt::Debug, mem::Discriminant, rc::Rc}; use collections::HashMap; use pathfinder_geometry::rect::RectF; +use smallvec::SmallVec; use crate::{EventContext, MouseButton}; @@ -177,61 +178,105 @@ impl MouseRegionId { } } +pub type HandlerCallback = Rc; + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct HandlerKey { + event_kind: Discriminant, + button: Option, +} + +impl HandlerKey { + pub fn new(event_kind: Discriminant, button: Option) -> HandlerKey { + HandlerKey { event_kind, button } + } +} + #[derive(Clone, Default)] pub struct HandlerSet { - #[allow(clippy::type_complexity)] - pub set: HashMap< - (Discriminant, Option), - Rc, - >, + set: HashMap>, } impl HandlerSet { pub fn capture_all() -> Self { - #[allow(clippy::type_complexity)] - let mut set: HashMap< - (Discriminant, Option), - Rc, - > = Default::default(); - - set.insert((MouseEvent::move_disc(), None), Rc::new(|_, _| {})); - set.insert((MouseEvent::hover_disc(), None), Rc::new(|_, _| {})); + let mut set: HashMap> = HashMap::default(); + + set.insert( + HandlerKey::new(MouseEvent::move_disc(), None), + SmallVec::from_buf([Rc::new(|_, _| {})]), + ); + set.insert( + HandlerKey::new(MouseEvent::hover_disc(), None), + SmallVec::from_buf([Rc::new(|_, _| {})]), + ); for button in MouseButton::all() { - set.insert((MouseEvent::drag_disc(), Some(button)), Rc::new(|_, _| {})); - set.insert((MouseEvent::down_disc(), Some(button)), Rc::new(|_, _| {})); - set.insert((MouseEvent::up_disc(), Some(button)), Rc::new(|_, _| {})); - set.insert((MouseEvent::click_disc(), Some(button)), Rc::new(|_, _| {})); set.insert( - (MouseEvent::down_out_disc(), Some(button)), - Rc::new(|_, _| {}), + HandlerKey::new(MouseEvent::drag_disc(), Some(button)), + SmallVec::from_buf([Rc::new(|_, _| {})]), + ); + set.insert( + HandlerKey::new(MouseEvent::down_disc(), Some(button)), + SmallVec::from_buf([Rc::new(|_, _| {})]), + ); + set.insert( + HandlerKey::new(MouseEvent::up_disc(), Some(button)), + SmallVec::from_buf([Rc::new(|_, _| {})]), ); set.insert( - (MouseEvent::up_out_disc(), Some(button)), - Rc::new(|_, _| {}), + HandlerKey::new(MouseEvent::click_disc(), Some(button)), + SmallVec::from_buf([Rc::new(|_, _| {})]), + ); + set.insert( + HandlerKey::new(MouseEvent::down_out_disc(), Some(button)), + SmallVec::from_buf([Rc::new(|_, _| {})]), + ); + set.insert( + HandlerKey::new(MouseEvent::up_out_disc(), Some(button)), + SmallVec::from_buf([Rc::new(|_, _| {})]), ); } - set.insert((MouseEvent::scroll_wheel_disc(), None), Rc::new(|_, _| {})); + set.insert( + HandlerKey::new(MouseEvent::scroll_wheel_disc(), None), + SmallVec::from_buf([Rc::new(|_, _| {})]), + ); HandlerSet { set } } - pub fn get( - &self, - key: &(Discriminant, Option), - ) -> Option> { - self.set.get(key).cloned() + pub fn get(&self, key: &HandlerKey) -> Option<&[HandlerCallback]> { + self.set.get(key).map(|vec| vec.as_slice()) } - pub fn contains_handler( + pub fn contains( &self, - event: Discriminant, + discriminant: Discriminant, button: Option, ) -> bool { - self.set.contains_key(&(event, button)) + self.set + .contains_key(&HandlerKey::new(discriminant, button)) + } + + fn insert( + &mut self, + event_kind: Discriminant, + button: Option, + callback: HandlerCallback, + ) { + use std::collections::hash_map::Entry; + + match self.set.entry(HandlerKey::new(event_kind, button)) { + Entry::Occupied(mut vec) => { + vec.get_mut().push(callback); + } + + Entry::Vacant(entry) => { + entry.insert(SmallVec::from_buf([callback])); + } + } } pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self { - self.set.insert((MouseEvent::move_disc(), None), + self.insert(MouseEvent::move_disc(), None, Rc::new(move |region_event, cx| { if let MouseEvent::Move(e) = region_event { handler(e, cx); @@ -249,7 +294,7 @@ impl HandlerSet { button: MouseButton, handler: impl Fn(MouseDown, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseEvent::down_disc(), Some(button)), + self.insert(MouseEvent::down_disc(), Some(button), Rc::new(move |region_event, cx| { if let MouseEvent::Down(e) = region_event { handler(e, cx); @@ -267,7 +312,7 @@ impl HandlerSet { button: MouseButton, handler: impl Fn(MouseUp, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseEvent::up_disc(), Some(button)), + self.insert(MouseEvent::up_disc(), Some(button), Rc::new(move |region_event, cx| { if let MouseEvent::Up(e) = region_event { handler(e, cx); @@ -285,7 +330,7 @@ impl HandlerSet { button: MouseButton, handler: impl Fn(MouseClick, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseEvent::click_disc(), Some(button)), + self.insert(MouseEvent::click_disc(), Some(button), Rc::new(move |region_event, cx| { if let MouseEvent::Click(e) = region_event { handler(e, cx); @@ -303,7 +348,7 @@ impl HandlerSet { button: MouseButton, handler: impl Fn(MouseDownOut, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseEvent::down_out_disc(), Some(button)), + self.insert(MouseEvent::down_out_disc(), Some(button), Rc::new(move |region_event, cx| { if let MouseEvent::DownOut(e) = region_event { handler(e, cx); @@ -321,7 +366,7 @@ impl HandlerSet { button: MouseButton, handler: impl Fn(MouseUpOut, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseEvent::up_out_disc(), Some(button)), + self.insert(MouseEvent::up_out_disc(), Some(button), Rc::new(move |region_event, cx| { if let MouseEvent::UpOut(e) = region_event { handler(e, cx); @@ -339,7 +384,7 @@ impl HandlerSet { button: MouseButton, handler: impl Fn(MouseDrag, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseEvent::drag_disc(), Some(button)), + self.insert(MouseEvent::drag_disc(), Some(button), Rc::new(move |region_event, cx| { if let MouseEvent::Drag(e) = region_event { handler(e, cx); @@ -353,7 +398,7 @@ impl HandlerSet { } pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self { - self.set.insert((MouseEvent::hover_disc(), None), + self.insert(MouseEvent::hover_disc(), None, Rc::new(move |region_event, cx| { if let MouseEvent::Hover(e) = region_event { handler(e, cx); @@ -370,7 +415,7 @@ impl HandlerSet { mut self, handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static, ) -> Self { - self.set.insert((MouseEvent::scroll_wheel_disc(), None), + self.insert(MouseEvent::scroll_wheel_disc(), None, Rc::new(move |region_event, cx| { if let MouseEvent::ScrollWheel(e) = region_event { handler(e, cx); From bca635e5d372fb83d0977b760fbec98b215feef2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 17 Nov 2022 15:26:46 -0800 Subject: [PATCH 38/67] Add LspAdapter hook for processing completions, fix completion sorting from Pyright --- crates/language/src/language.rs | 12 +++ crates/language/src/proto.rs | 9 +- crates/project/src/project.rs | 155 +++++++++++++++-------------- crates/zed/src/languages/python.rs | 19 ++++ 4 files changed, 115 insertions(+), 80 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 5e9319b1289bb3ea0530dbade269f436706b328a..47d724866d7ff91c62ecaa55644d8d4cbf62337a 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -135,6 +135,10 @@ impl CachedLspAdapter { self.adapter.process_diagnostics(params).await } + pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) { + self.adapter.process_completion(completion_item).await + } + pub async fn label_for_completion( &self, completion_item: &lsp::CompletionItem, @@ -175,6 +179,8 @@ pub trait LspAdapter: 'static + Send + Sync { async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} + async fn process_completion(&self, _: &mut lsp::CompletionItem) {} + async fn label_for_completion( &self, _: &lsp::CompletionItem, @@ -826,6 +832,12 @@ impl Language { } } + pub async fn process_completion(self: &Arc, completion: &mut lsp::CompletionItem) { + if let Some(adapter) = self.adapter.as_ref() { + adapter.process_completion(completion).await; + } + } + pub async fn label_for_completion( self: &Arc, completion: &lsp::CompletionItem, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index f93d99f76b02a86a0267d2355a7c85cccda60b70..674ce4f50eb5988098c32c3d7557852f9e2b912a 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -426,10 +426,11 @@ pub async fn deserialize_completion( .and_then(deserialize_anchor) .ok_or_else(|| anyhow!("invalid old end"))?; let lsp_completion = serde_json::from_slice(&completion.lsp_completion)?; - let label = match language { - Some(l) => l.label_for_completion(&lsp_completion).await, - None => None, - }; + + let mut label = None; + if let Some(language) = language { + label = language.label_for_completion(&lsp_completion).await; + } Ok(Completion { old_range: old_start..old_end, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e1a0b9c00a5bc8489ed1ba817feadcfce7599227..a7124a19698dfb365463a62beced578be12783a7 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -3329,88 +3329,91 @@ impl Project { let snapshot = this.snapshot(); let clipped_position = this.clip_point_utf16(position, Bias::Left); let mut range_for_token = None; - completions.into_iter().filter_map(move |lsp_completion| { - // For now, we can only handle additional edits if they are returned - // when resolving the completion, not if they are present initially. - if lsp_completion - .additional_text_edits - .as_ref() - .map_or(false, |edits| !edits.is_empty()) - { - return None; - } + completions + .into_iter() + .filter_map(move |mut lsp_completion| { + // For now, we can only handle additional edits if they are returned + // when resolving the completion, not if they are present initially. + if lsp_completion + .additional_text_edits + .as_ref() + .map_or(false, |edits| !edits.is_empty()) + { + return None; + } - let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() { - // If the language server provides a range to overwrite, then - // check that the range is valid. - Some(lsp::CompletionTextEdit::Edit(edit)) => { - let range = range_from_lsp(edit.range); - let start = snapshot.clip_point_utf16(range.start, Bias::Left); - let end = snapshot.clip_point_utf16(range.end, Bias::Left); - if start != range.start || end != range.end { - log::info!("completion out of expected range"); - return None; + let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() + { + // If the language server provides a range to overwrite, then + // check that the range is valid. + Some(lsp::CompletionTextEdit::Edit(edit)) => { + let range = range_from_lsp(edit.range); + let start = snapshot.clip_point_utf16(range.start, Bias::Left); + let end = snapshot.clip_point_utf16(range.end, Bias::Left); + if start != range.start || end != range.end { + log::info!("completion out of expected range"); + return None; + } + ( + snapshot.anchor_before(start)..snapshot.anchor_after(end), + edit.new_text.clone(), + ) } - ( - snapshot.anchor_before(start)..snapshot.anchor_after(end), - edit.new_text.clone(), - ) - } - // If the language server does not provide a range, then infer - // the range based on the syntax tree. - None => { - if position != clipped_position { - log::info!("completion out of expected range"); + // If the language server does not provide a range, then infer + // the range based on the syntax tree. + None => { + if position != clipped_position { + log::info!("completion out of expected range"); + return None; + } + let Range { start, end } = range_for_token + .get_or_insert_with(|| { + let offset = position.to_offset(&snapshot); + let (range, kind) = snapshot.surrounding_word(offset); + if kind == Some(CharKind::Word) { + range + } else { + offset..offset + } + }) + .clone(); + let text = lsp_completion + .insert_text + .as_ref() + .unwrap_or(&lsp_completion.label) + .clone(); + ( + snapshot.anchor_before(start)..snapshot.anchor_after(end), + text, + ) + } + Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => { + log::info!("unsupported insert/replace completion"); return None; } - let Range { start, end } = range_for_token - .get_or_insert_with(|| { - let offset = position.to_offset(&snapshot); - let (range, kind) = snapshot.surrounding_word(offset); - if kind == Some(CharKind::Word) { - range - } else { - offset..offset - } - }) - .clone(); - let text = lsp_completion - .insert_text - .as_ref() - .unwrap_or(&lsp_completion.label) - .clone(); - ( - snapshot.anchor_before(start)..snapshot.anchor_after(end), - text, - ) - } - Some(lsp::CompletionTextEdit::InsertAndReplace(_)) => { - log::info!("unsupported insert/replace completion"); - return None; - } - }; - - LineEnding::normalize(&mut new_text); - let language = language.clone(); - Some(async move { - let label = if let Some(language) = language { - language.label_for_completion(&lsp_completion).await - } else { - None }; - Completion { - old_range, - new_text, - label: label.unwrap_or_else(|| { - CodeLabel::plain( - lsp_completion.label.clone(), - lsp_completion.filter_text.as_deref(), - ) - }), - lsp_completion, - } + + LineEnding::normalize(&mut new_text); + let language = language.clone(); + Some(async move { + let mut label = None; + if let Some(language) = language { + language.process_completion(&mut lsp_completion).await; + label = language.label_for_completion(&lsp_completion).await; + } + Completion { + old_range, + new_text, + label: label.unwrap_or_else(|| { + CodeLabel::plain( + lsp_completion.label.clone(), + lsp_completion.filter_text.as_deref(), + ) + }), + lsp_completion, + } + }) }) - }) }); Ok(futures::future::join_all(completions).await) diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index e6e55eeac464ec127e21620a2b3c37fe06313b4a..ba6ccf7bf01b544bde4e91737cf2a89e4e9e4b36 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -87,6 +87,25 @@ impl LspAdapter for PythonLspAdapter { .log_err() } + async fn process_completion(&self, item: &mut lsp::CompletionItem) { + // Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`. + // Where `XX` is the sorting category, `YYYY` is based on most recent usage, + // and `name` is the symbol name itself. + // + // Because the the symbol name is included, there generally are not ties when + // sorting by the `sortText`, so the symbol's fuzzy match score is not taken + // into account. Here, we remove the symbol name from the sortText in order + // to allow our own fuzzy score to be used to break ties. + // + // see https://github.com/microsoft/pyright/blob/95ef4e103b9b2f129c9320427e51b73ea7cf78bd/packages/pyright-internal/src/languageService/completionProvider.ts#LL2873 + let Some(sort_text) = &mut item.sort_text else { return }; + let mut parts = sort_text.split('.'); + let Some(first) = parts.next() else { return }; + let Some(second) = parts.next() else { return }; + let Some(_) = parts.next() else { return }; + sort_text.replace_range(first.len() + second.len() + 1.., ""); + } + async fn label_for_completion( &self, item: &lsp::CompletionItem, From 75b8a12ab37a21189aa28adbae2cec3412d07134 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 18 Nov 2022 13:04:27 -0800 Subject: [PATCH 39/67] address issue where mouse down events weren't getting captured after the multiple handlers change --- crates/gpui/src/presenter.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index c988bb330be665b0b2845545fc7808953a7d8284..d15051ef126677d516a9986de5e64ee5237b5dcc 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -485,7 +485,7 @@ impl Presenter { } // `event_consumed` should only be true if there are any handlers for this event. - let mut event_consumed = false; + let mut event_consumed = event_cx.handled; if let Some(callbacks) = valid_region.handlers.get(&mouse_event.handler_key()) { event_consumed = true; for callback in callbacks { @@ -499,6 +499,8 @@ impl Presenter { } } + any_event_handled |= event_cx.handled; + // For bubbling events, if the event was handled, don't continue dispatching. // This only makes sense for local events which return false from is_capturable. if event_consumed && mouse_event.is_capturable() { From 0078bea8772e16952df97d5f19c9b92534d425a3 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Fri, 18 Nov 2022 13:42:46 -0800 Subject: [PATCH 40/67] change bump-version to install jq if its not already installed --- script/lib/bump-version.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/script/lib/bump-version.sh b/script/lib/bump-version.sh index 7800cc4408952e0d3abe9cd18bce64dc99af7d0e..ce955369505dbfd1e9fdbacd64321c0c7bc6d405 100755 --- a/script/lib/bump-version.sh +++ b/script/lib/bump-version.sh @@ -13,6 +13,7 @@ if [[ -n $(git status --short --untracked-files=no) ]]; then fi which cargo-set-version > /dev/null || cargo install cargo-edit +which jq > /dev/null || brew install jq cargo set-version --package $package --bump $version_increment cargo check --quiet From f9cbed5a1f6566d17ccdead161171f831c682e9e Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 15 Nov 2022 14:11:52 -0500 Subject: [PATCH 41/67] Clamp UTF-16 coordinate while performing LSP edits rather than panicing --- crates/project/src/project.rs | 2 +- crates/rope/src/rope.rs | 36 +++++++++++++++++++++++------------ crates/text/src/text.rs | 22 ++++++++++++++++++++- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a7124a19698dfb365463a62beced578be12783a7..c4e920db84cd35585be6643bd68f67daaa612ebb 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -5743,7 +5743,7 @@ impl Project { // of any anchors positioned in the unchanged regions. if range.end.row > range.start.row { let mut offset = range.start.to_offset(&snapshot); - let old_text = snapshot.text_for_range(range).collect::(); + let old_text = snapshot.text_for_clamped_range(range).collect::(); let diff = TextDiff::from_lines(old_text.as_str(), &new_text); let mut moved_since_edit = true; diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 8c357801e321034d68eda79bf54188200631ec8e..b6a4930f0b66355057d1e2538a9abb81790fa5de 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -259,7 +259,7 @@ impl Rope { .map_or(0, |chunk| chunk.point_to_offset(overshoot)) } - pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { + pub fn point_utf16_to_offset(&self, point: PointUtf16, clamp: bool) -> usize { if point >= self.summary().lines_utf16() { return self.summary().len; } @@ -269,7 +269,7 @@ impl Rope { cursor.start().1 + cursor .item() - .map_or(0, |chunk| chunk.point_utf16_to_offset(overshoot)) + .map_or(0, |chunk| chunk.point_utf16_to_offset(overshoot, clamp)) } pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point { @@ -711,29 +711,41 @@ impl Chunk { point_utf16 } - fn point_utf16_to_offset(&self, target: PointUtf16) -> usize { + fn point_utf16_to_offset(&self, target: PointUtf16, clamp: bool) -> usize { let mut offset = 0; let mut point = PointUtf16::new(0, 0); for ch in self.0.chars() { - if point >= target { - if point > target { - panic!("point {:?} is inside of character {:?}", target, ch); - } + if point == target { break; } if ch == '\n' { point.row += 1; + point.column = 0; + if point.row > target.row { + if clamp { + //Return the offset up to but not including the newline + return offset; + } panic!( "point {:?} is beyond the end of a line with length {}", target, point.column ); } - point.column = 0; } else { point.column += ch.len_utf16() as u32; } + + if point > target { + if clamp { + //Return the offset before adding the len of the codepoint which + //we have landed within, bias left + return offset; + } + panic!("point {:?} is inside of character {:?}", target, ch); + } + offset += ch.len_utf8(); } offset @@ -1210,7 +1222,7 @@ mod tests { point ); assert_eq!( - actual.point_utf16_to_offset(point_utf16), + actual.point_utf16_to_offset(point_utf16, false), ix, "point_utf16_to_offset({:?})", point_utf16 @@ -1250,9 +1262,9 @@ mod tests { let left_point = actual.clip_point_utf16(point_utf16, Bias::Left); let right_point = actual.clip_point_utf16(point_utf16, Bias::Right); assert!(right_point >= left_point); - // Ensure translating UTF-16 points to offsets doesn't panic. - actual.point_utf16_to_offset(left_point); - actual.point_utf16_to_offset(right_point); + // Ensure translating valid UTF-16 points to offsets doesn't panic. + actual.point_utf16_to_offset(left_point, false); + actual.point_utf16_to_offset(right_point, false); offset_utf16.0 += 1; if unit == b'\n' as u16 { diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 72ae018a16a66848f6bed4435f5e809ee4c39a58..272e425651cd7dca74c57206d3de04f68d9a6d89 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1591,7 +1591,11 @@ impl BufferSnapshot { } pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { - self.visible_text.point_utf16_to_offset(point) + self.visible_text.point_utf16_to_offset(point, false) + } + + pub fn point_utf16_to_offset_clamped(&self, point: PointUtf16) -> usize { + self.visible_text.point_utf16_to_offset(point, true) } pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point { @@ -1649,6 +1653,12 @@ impl BufferSnapshot { self.visible_text.chunks_in_range(start..end) } + pub fn text_for_clamped_range(&self, range: Range) -> Chunks<'_> { + let start = range.start.to_offset_clamped(self); + let end = range.end.to_offset_clamped(self); + self.visible_text.chunks_in_range(start..end) + } + pub fn line_len(&self, row: u32) -> u32 { let row_start_offset = Point::new(row, 0).to_offset(self); let row_end_offset = if row >= self.max_point().row { @@ -2390,6 +2400,16 @@ impl<'a, T: ToOffset> ToOffset for &'a T { } } +pub trait ToOffsetClamped { + fn to_offset_clamped(&self, snapshot: &BufferSnapshot) -> usize; +} + +impl ToOffsetClamped for PointUtf16 { + fn to_offset_clamped<'a>(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.point_utf16_to_offset_clamped(*self) + } +} + pub trait ToPoint { fn to_point(&self, snapshot: &BufferSnapshot) -> Point; } From bb32599dedea130074c358dcce95f5458bd746be Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 15 Nov 2022 15:16:52 -0500 Subject: [PATCH 42/67] Clamp for all UTF-16 to offset conversions which used to use `ToOffset` --- crates/language/src/diagnostic_set.rs | 4 +-- crates/project/src/lsp_command.rs | 36 +++++++++++++++------------ crates/project/src/project.rs | 17 +++++++------ crates/text/src/text.rs | 25 +++++++++---------- 4 files changed, 43 insertions(+), 39 deletions(-) diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index b52327cac0e81b4ddbf7f224ebef170ac92d3f15..fe2e4c9c212a642c9f8b24e4d5347d5d46e994c2 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -70,8 +70,8 @@ impl DiagnosticSet { Self { diagnostics: SumTree::from_iter( entries.into_iter().map(|entry| DiagnosticEntry { - range: buffer.anchor_before(entry.range.start) - ..buffer.anchor_after(entry.range.end), + range: buffer.clamped_anchor_before(entry.range.start) + ..buffer.clamped_anchor_after(entry.range.end), diagnostic: entry.diagnostic, }), buffer, diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 37f6e76340ebdc59f623a61bd1cbb9ef4e168b04..78c6b50003605bf65c1e2b3f05b75d2d00ca9e89 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -131,7 +131,9 @@ impl LspCommand for PrepareRename { if buffer.clip_point_utf16(start, Bias::Left) == start && buffer.clip_point_utf16(end, Bias::Left) == end { - return Ok(Some(buffer.anchor_after(start)..buffer.anchor_before(end))); + return Ok(Some( + buffer.clamped_anchor_after(start)..buffer.clamped_anchor_before(end), + )); } } Ok(None) @@ -143,7 +145,7 @@ impl LspCommand for PrepareRename { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.anchor_before(self.position), + &buffer.clamped_anchor_before(self.position), )), version: serialize_version(&buffer.version()), } @@ -262,7 +264,7 @@ impl LspCommand for PerformRename { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.anchor_before(self.position), + &buffer.clamped_anchor_before(self.position), )), new_name: self.new_name.clone(), version: serialize_version(&buffer.version()), @@ -360,7 +362,7 @@ impl LspCommand for GetDefinition { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.anchor_before(self.position), + &buffer.clamped_anchor_before(self.position), )), version: serialize_version(&buffer.version()), } @@ -446,7 +448,7 @@ impl LspCommand for GetTypeDefinition { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.anchor_before(self.position), + &buffer.clamped_anchor_before(self.position), )), version: serialize_version(&buffer.version()), } @@ -629,8 +631,8 @@ async fn location_links_from_lsp( origin_buffer.clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left); Location { buffer: buffer.clone(), - range: origin_buffer.anchor_after(origin_start) - ..origin_buffer.anchor_before(origin_end), + range: origin_buffer.clamped_anchor_after(origin_start) + ..origin_buffer.clamped_anchor_before(origin_end), } }); @@ -641,8 +643,8 @@ async fn location_links_from_lsp( target_buffer.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left); let target_location = Location { buffer: target_buffer_handle, - range: target_buffer.anchor_after(target_start) - ..target_buffer.anchor_before(target_end), + range: target_buffer.clamped_anchor_after(target_start) + ..target_buffer.clamped_anchor_before(target_end), }; definitions.push(LocationLink { @@ -741,8 +743,8 @@ impl LspCommand for GetReferences { .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left); references.push(Location { buffer: target_buffer_handle, - range: target_buffer.anchor_after(target_start) - ..target_buffer.anchor_before(target_end), + range: target_buffer.clamped_anchor_after(target_start) + ..target_buffer.clamped_anchor_before(target_end), }); }); } @@ -756,7 +758,7 @@ impl LspCommand for GetReferences { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.anchor_before(self.position), + &buffer.clamped_anchor_before(self.position), )), version: serialize_version(&buffer.version()), } @@ -882,7 +884,8 @@ impl LspCommand for GetDocumentHighlights { let end = buffer .clip_point_utf16(point_from_lsp(lsp_highlight.range.end), Bias::Left); DocumentHighlight { - range: buffer.anchor_after(start)..buffer.anchor_before(end), + range: buffer.clamped_anchor_after(start) + ..buffer.clamped_anchor_before(end), kind: lsp_highlight .kind .unwrap_or(lsp::DocumentHighlightKind::READ), @@ -897,7 +900,7 @@ impl LspCommand for GetDocumentHighlights { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.anchor_before(self.position), + &buffer.clamped_anchor_before(self.position), )), version: serialize_version(&buffer.version()), } @@ -1017,7 +1020,8 @@ impl LspCommand for GetHover { let token_start = buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left); let token_end = buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left); - buffer.anchor_after(token_start)..buffer.anchor_before(token_end) + buffer.clamped_anchor_after(token_start) + ..buffer.clamped_anchor_before(token_end) }) }); @@ -1099,7 +1103,7 @@ impl LspCommand for GetHover { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.anchor_before(self.position), + &buffer.clamped_anchor_before(self.position), )), version: serialize_version(&buffer.version), } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index c4e920db84cd35585be6643bd68f67daaa612ebb..2036ef3cd8bc3729aa95da920a99bc7c6852e8c0 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -25,7 +25,8 @@ use language::{ range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt, - Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, + Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToOffsetClamped, ToPointUtf16, + Transaction, }; use lsp::{ DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, LanguageString, @@ -3289,7 +3290,7 @@ impl Project { }; let position = position.to_point_utf16(source_buffer); - let anchor = source_buffer.anchor_after(position); + let anchor = source_buffer.clamped_anchor_after(position); if worktree.read(cx).as_local().is_some() { let buffer_abs_path = buffer_abs_path.unwrap(); @@ -3355,7 +3356,7 @@ impl Project { return None; } ( - snapshot.anchor_before(start)..snapshot.anchor_after(end), + snapshot.clamped_anchor_before(start)..snapshot.clamped_anchor_after(end), edit.new_text.clone(), ) } @@ -3368,7 +3369,7 @@ impl Project { } let Range { start, end } = range_for_token .get_or_insert_with(|| { - let offset = position.to_offset(&snapshot); + let offset = position.to_offset_clamped(&snapshot); let (range, kind) = snapshot.surrounding_word(offset); if kind == Some(CharKind::Word) { range @@ -5742,7 +5743,7 @@ impl Project { // we can identify the changes more precisely, preserving the locations // of any anchors positioned in the unchanged regions. if range.end.row > range.start.row { - let mut offset = range.start.to_offset(&snapshot); + let mut offset = range.start.to_offset_clamped(&snapshot); let old_text = snapshot.text_for_clamped_range(range).collect::(); let diff = TextDiff::from_lines(old_text.as_str(), &new_text); @@ -5778,11 +5779,11 @@ impl Project { } } } else if range.end == range.start { - let anchor = snapshot.anchor_after(range.start); + let anchor = snapshot.clamped_anchor_after(range.start); edits.push((anchor..anchor, new_text)); } else { - let edit_start = snapshot.anchor_after(range.start); - let edit_end = snapshot.anchor_before(range.end); + let edit_start = snapshot.clamped_anchor_after(range.start); + let edit_end = snapshot.clamped_anchor_before(range.end); edits.push((edit_start..edit_end, new_text)); } } diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 272e425651cd7dca74c57206d3de04f68d9a6d89..42ffe5c6edc266283f0e54590f5c7dc6b1c877fa 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1808,12 +1808,23 @@ impl BufferSnapshot { self.anchor_at(position, Bias::Left) } + pub fn clamped_anchor_before(&self, position: T) -> Anchor { + self.anchor_at_offset(position.to_offset_clamped(self), Bias::Left) + } + pub fn anchor_after(&self, position: T) -> Anchor { self.anchor_at(position, Bias::Right) } + pub fn clamped_anchor_after(&self, position: T) -> Anchor { + self.anchor_at_offset(position.to_offset_clamped(self), Bias::Right) + } + pub fn anchor_at(&self, position: T, bias: Bias) -> Anchor { - let offset = position.to_offset(self); + self.anchor_at_offset(position.to_offset(self), bias) + } + + fn anchor_at_offset(&self, offset: usize, bias: Bias) -> Anchor { if bias == Bias::Left && offset == 0 { Anchor::MIN } else if bias == Bias::Right && offset == self.len() { @@ -2369,12 +2380,6 @@ impl ToOffset for Point { } } -impl ToOffset for PointUtf16 { - fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize { - snapshot.point_utf16_to_offset(*self) - } -} - impl ToOffset for usize { fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize { assert!(*self <= snapshot.len(), "offset {self} is out of range"); @@ -2382,12 +2387,6 @@ impl ToOffset for usize { } } -impl ToOffset for OffsetUtf16 { - fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize { - snapshot.offset_utf16_to_offset(*self) - } -} - impl ToOffset for Anchor { fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize { snapshot.summary_for_anchor(self) From 074e3cfbd68a5429e244b204773486959845a90d Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 16 Nov 2022 13:29:26 -0500 Subject: [PATCH 43/67] Clamp UTF-16 to point conversions --- crates/editor/src/editor.rs | 2 +- crates/editor/src/multi_buffer.rs | 22 +++++++----- crates/editor/src/selections_collection.rs | 27 ++++++++++++-- crates/project_symbols/src/project_symbols.rs | 2 +- crates/rope/src/rope.rs | 35 ++++++++----------- crates/text/src/text.rs | 22 ++++++------ 6 files changed, 65 insertions(+), 45 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index dd5934f9794c4d65300db90bd3aedc7e69e9429f..7206dbb199f9123503a69bda3202357b9208bb78 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -55,7 +55,7 @@ use link_go_to_definition::{ }; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, - ToPoint, + ToOffsetClamped, ToPoint, }; use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 38475daf28f75a74278d36f5e4d2c16e006a1b05..70645bbfe00fd68f2d8b19e4b9e19d40b904e1ae 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -72,6 +72,10 @@ pub trait ToOffset: 'static + fmt::Debug { fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize; } +pub trait ToOffsetClamped: 'static + fmt::Debug { + fn to_offset_clamped(&self, snapshot: &MultiBufferSnapshot) -> usize; +} + pub trait ToOffsetUtf16: 'static + fmt::Debug { fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16; } @@ -1945,9 +1949,9 @@ impl MultiBufferSnapshot { } } - pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { + pub fn point_utf16_to_offset_clamped(&self, point: PointUtf16) -> usize { if let Some((_, _, buffer)) = self.as_singleton() { - return buffer.point_utf16_to_offset(point); + return buffer.point_utf16_to_offset_clamped(point); } let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>(); @@ -1961,7 +1965,7 @@ impl MultiBufferSnapshot { .offset_to_point_utf16(excerpt.range.context.start.to_offset(&excerpt.buffer)); let buffer_offset = excerpt .buffer - .point_utf16_to_offset(excerpt_start_point + overshoot); + .point_utf16_to_offset_clamped(excerpt_start_point + overshoot); *start_offset + (buffer_offset - excerpt_start_offset) } else { self.excerpts.summary().text.len @@ -3274,12 +3278,6 @@ impl ToOffset for Point { } } -impl ToOffset for PointUtf16 { - fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { - snapshot.point_utf16_to_offset(*self) - } -} - impl ToOffset for usize { fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { assert!(*self <= snapshot.len(), "offset is out of range"); @@ -3293,6 +3291,12 @@ impl ToOffset for OffsetUtf16 { } } +impl ToOffsetClamped for PointUtf16 { + fn to_offset_clamped<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + snapshot.point_utf16_to_offset_clamped(*self) + } +} + impl ToOffsetUtf16 for OffsetUtf16 { fn to_offset_utf16(&self, _snapshot: &MultiBufferSnapshot) -> OffsetUtf16 { *self diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 256405f20eeeecb59a34fa581e6a5bd8f33854e9..01e38d269eeeb88d35fd07ce1fad110991fb1d2f 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -14,6 +14,7 @@ use util::post_inc; use crate::{ display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint}, Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset, + ToOffsetClamped, }; #[derive(Clone)] @@ -544,11 +545,33 @@ impl<'a> MutableSelectionsCollection<'a> { T: ToOffset, { let buffer = self.buffer.read(self.cx).snapshot(self.cx); + let ranges = ranges + .into_iter() + .map(|range| range.start.to_offset(&buffer)..range.end.to_offset(&buffer)); + self.select_offset_ranges(ranges); + } + + pub fn select_clamped_ranges(&mut self, ranges: I) + where + I: IntoIterator>, + T: ToOffsetClamped, + { + let buffer = self.buffer.read(self.cx).snapshot(self.cx); + let ranges = ranges.into_iter().map(|range| { + range.start.to_offset_clamped(&buffer)..range.end.to_offset_clamped(&buffer) + }); + self.select_offset_ranges(ranges); + } + + fn select_offset_ranges(&mut self, ranges: I) + where + I: IntoIterator>, + { let selections = ranges .into_iter() .map(|range| { - let mut start = range.start.to_offset(&buffer); - let mut end = range.end.to_offset(&buffer); + let mut start = range.start; + let mut end = range.end; let reversed = if start > end { mem::swap(&mut start, &mut end); true diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 273230fe26feb5f01cc1cdcbacd7c321d689a446..60623e99caf8bc95f6488915586e288dae3b8c99 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -151,7 +151,7 @@ impl ProjectSymbolsView { let editor = workspace.open_project_item::(buffer, cx); editor.update(cx, |editor, cx| { editor.change_selections(Some(Autoscroll::center()), cx, |s| { - s.select_ranges([position..position]) + s.select_clamped_ranges([position..position]) }); }); }); diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index b6a4930f0b66355057d1e2538a9abb81790fa5de..83e9c96ca968b273a1ac749c611ac897c427f3d2 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -259,7 +259,7 @@ impl Rope { .map_or(0, |chunk| chunk.point_to_offset(overshoot)) } - pub fn point_utf16_to_offset(&self, point: PointUtf16, clamp: bool) -> usize { + pub fn point_utf16_to_offset_clamped(&self, point: PointUtf16) -> usize { if point >= self.summary().lines_utf16() { return self.summary().len; } @@ -269,7 +269,7 @@ impl Rope { cursor.start().1 + cursor .item() - .map_or(0, |chunk| chunk.point_utf16_to_offset(overshoot, clamp)) + .map_or(0, |chunk| chunk.point_utf16_to_offset_clamped(overshoot)) } pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point { @@ -711,7 +711,7 @@ impl Chunk { point_utf16 } - fn point_utf16_to_offset(&self, target: PointUtf16, clamp: bool) -> usize { + fn point_utf16_to_offset_clamped(&self, target: PointUtf16) -> usize { let mut offset = 0; let mut point = PointUtf16::new(0, 0); for ch in self.0.chars() { @@ -724,26 +724,19 @@ impl Chunk { point.column = 0; if point.row > target.row { - if clamp { - //Return the offset up to but not including the newline - return offset; - } - panic!( - "point {:?} is beyond the end of a line with length {}", - target, point.column - ); + //point is beyond the end of the line + //Return the offset up to but not including the newline + return offset; } } else { point.column += ch.len_utf16() as u32; } if point > target { - if clamp { - //Return the offset before adding the len of the codepoint which - //we have landed within, bias left - return offset; - } - panic!("point {:?} is inside of character {:?}", target, ch); + //point is inside of a codepoint + //Return the offset before adding the len of the codepoint which + //we have landed within, bias left + return offset; } offset += ch.len_utf8(); @@ -1222,7 +1215,7 @@ mod tests { point ); assert_eq!( - actual.point_utf16_to_offset(point_utf16, false), + actual.point_utf16_to_offset_clamped(point_utf16), ix, "point_utf16_to_offset({:?})", point_utf16 @@ -1262,9 +1255,9 @@ mod tests { let left_point = actual.clip_point_utf16(point_utf16, Bias::Left); let right_point = actual.clip_point_utf16(point_utf16, Bias::Right); assert!(right_point >= left_point); - // Ensure translating valid UTF-16 points to offsets doesn't panic. - actual.point_utf16_to_offset(left_point, false); - actual.point_utf16_to_offset(right_point, false); + // Ensure translating UTF-16 points to offsets doesn't panic. + actual.point_utf16_to_offset_clamped(left_point); + actual.point_utf16_to_offset_clamped(right_point); offset_utf16.0 += 1; if unit == b'\n' as u16 { diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 42ffe5c6edc266283f0e54590f5c7dc6b1c877fa..aec46b03cfc152a70903654fd06ffb40112356d7 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1590,12 +1590,8 @@ impl BufferSnapshot { self.visible_text.point_to_offset(point) } - pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { - self.visible_text.point_utf16_to_offset(point, false) - } - pub fn point_utf16_to_offset_clamped(&self, point: PointUtf16) -> usize { - self.visible_text.point_utf16_to_offset(point, true) + self.visible_text.point_utf16_to_offset_clamped(point) } pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point { @@ -2425,18 +2421,22 @@ impl ToPoint for usize { } } -impl ToPoint for PointUtf16 { - fn to_point<'a>(&self, snapshot: &BufferSnapshot) -> Point { - snapshot.point_utf16_to_point(*self) - } -} - impl ToPoint for Point { fn to_point<'a>(&self, _: &BufferSnapshot) -> Point { *self } } +pub trait ToPointClamped { + fn to_point_clamped(&self, snapshot: &BufferSnapshot) -> Point; +} + +impl ToPointClamped for PointUtf16 { + fn to_point_clamped<'a>(&self, snapshot: &BufferSnapshot) -> Point { + snapshot.point_utf16_to_point(*self) + } +} + pub trait ToPointUtf16 { fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16; } From 4ead1ecbbffcabef97ddba9327818ee0cd27b960 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 16 Nov 2022 14:27:19 -0500 Subject: [PATCH 44/67] Simply logic of this method Co-Authored-By: Max Brunsfeld --- crates/rope/src/rope.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 83e9c96ca968b273a1ac749c611ac897c427f3d2..ca1f35bb13ec47d9208363595c38b812b405332a 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -714,6 +714,7 @@ impl Chunk { fn point_utf16_to_offset_clamped(&self, target: PointUtf16) -> usize { let mut offset = 0; let mut point = PointUtf16::new(0, 0); + for ch in self.0.chars() { if point == target { break; @@ -722,25 +723,19 @@ impl Chunk { if ch == '\n' { point.row += 1; point.column = 0; - - if point.row > target.row { - //point is beyond the end of the line - //Return the offset up to but not including the newline - return offset; - } } else { point.column += ch.len_utf16() as u32; } if point > target { - //point is inside of a codepoint - //Return the offset before adding the len of the codepoint which - //we have landed within, bias left + // If the point is past the end of a line or inside of a code point, + // return the last valid offset before the point. return offset; } offset += ch.len_utf8(); } + offset } From 436c89650a14ce2063dffecefafc74c6675d1e29 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 16 Nov 2022 15:12:52 -0500 Subject: [PATCH 45/67] Rename clamped -> clipped --- crates/editor/src/editor.rs | 2 +- crates/editor/src/multi_buffer.rs | 16 +++++------ crates/editor/src/selections_collection.rs | 8 +++--- crates/project/src/project.rs | 6 ++-- crates/project_symbols/src/project_symbols.rs | 2 +- crates/rope/src/rope.rs | 12 ++++---- crates/text/src/text.rs | 28 +++++++++---------- 7 files changed, 37 insertions(+), 37 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7206dbb199f9123503a69bda3202357b9208bb78..4575e9ce5e9c1e0bdf603bc0fae2429af56ffa93 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -55,7 +55,7 @@ use link_go_to_definition::{ }; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, - ToOffsetClamped, ToPoint, + ToOffsetClipped, ToPoint, }; use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 70645bbfe00fd68f2d8b19e4b9e19d40b904e1ae..aa25b476803418f6c0ad8b5a2ae4bb04c96a4d3e 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -72,8 +72,8 @@ pub trait ToOffset: 'static + fmt::Debug { fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize; } -pub trait ToOffsetClamped: 'static + fmt::Debug { - fn to_offset_clamped(&self, snapshot: &MultiBufferSnapshot) -> usize; +pub trait ToOffsetClipped: 'static + fmt::Debug { + fn to_offset_clipped(&self, snapshot: &MultiBufferSnapshot) -> usize; } pub trait ToOffsetUtf16: 'static + fmt::Debug { @@ -1949,9 +1949,9 @@ impl MultiBufferSnapshot { } } - pub fn point_utf16_to_offset_clamped(&self, point: PointUtf16) -> usize { + pub fn point_utf16_to_offset_clipped(&self, point: PointUtf16) -> usize { if let Some((_, _, buffer)) = self.as_singleton() { - return buffer.point_utf16_to_offset_clamped(point); + return buffer.point_utf16_to_offset_clipped(point); } let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>(); @@ -1965,7 +1965,7 @@ impl MultiBufferSnapshot { .offset_to_point_utf16(excerpt.range.context.start.to_offset(&excerpt.buffer)); let buffer_offset = excerpt .buffer - .point_utf16_to_offset_clamped(excerpt_start_point + overshoot); + .point_utf16_to_offset_clipped(excerpt_start_point + overshoot); *start_offset + (buffer_offset - excerpt_start_offset) } else { self.excerpts.summary().text.len @@ -3291,9 +3291,9 @@ impl ToOffset for OffsetUtf16 { } } -impl ToOffsetClamped for PointUtf16 { - fn to_offset_clamped<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { - snapshot.point_utf16_to_offset_clamped(*self) +impl ToOffsetClipped for PointUtf16 { + fn to_offset_clipped<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + snapshot.point_utf16_to_offset_clipped(*self) } } diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 01e38d269eeeb88d35fd07ce1fad110991fb1d2f..14026d9f4e86ce93ac3c40c710a614420d55ca37 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -14,7 +14,7 @@ use util::post_inc; use crate::{ display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint}, Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset, - ToOffsetClamped, + ToOffsetClipped, }; #[derive(Clone)] @@ -551,14 +551,14 @@ impl<'a> MutableSelectionsCollection<'a> { self.select_offset_ranges(ranges); } - pub fn select_clamped_ranges(&mut self, ranges: I) + pub fn select_clipped_ranges(&mut self, ranges: I) where I: IntoIterator>, - T: ToOffsetClamped, + T: ToOffsetClipped, { let buffer = self.buffer.read(self.cx).snapshot(self.cx); let ranges = ranges.into_iter().map(|range| { - range.start.to_offset_clamped(&buffer)..range.end.to_offset_clamped(&buffer) + range.start.to_offset_clipped(&buffer)..range.end.to_offset_clipped(&buffer) }); self.select_offset_ranges(ranges); } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2036ef3cd8bc3729aa95da920a99bc7c6852e8c0..e1339ba434c3a80d44da7b97c85693dc25249e23 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -25,7 +25,7 @@ use language::{ range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt, - Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToOffsetClamped, ToPointUtf16, + Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToOffsetClipped, ToPointUtf16, Transaction, }; use lsp::{ @@ -3369,7 +3369,7 @@ impl Project { } let Range { start, end } = range_for_token .get_or_insert_with(|| { - let offset = position.to_offset_clamped(&snapshot); + let offset = position.to_offset_clipped(&snapshot); let (range, kind) = snapshot.surrounding_word(offset); if kind == Some(CharKind::Word) { range @@ -5743,7 +5743,7 @@ impl Project { // we can identify the changes more precisely, preserving the locations // of any anchors positioned in the unchanged regions. if range.end.row > range.start.row { - let mut offset = range.start.to_offset_clamped(&snapshot); + let mut offset = range.start.to_offset_clipped(&snapshot); let old_text = snapshot.text_for_clamped_range(range).collect::(); let diff = TextDiff::from_lines(old_text.as_str(), &new_text); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 60623e99caf8bc95f6488915586e288dae3b8c99..eb755d2d2f360a2691a447c4549c4d83de4150ba 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -151,7 +151,7 @@ impl ProjectSymbolsView { let editor = workspace.open_project_item::(buffer, cx); editor.update(cx, |editor, cx| { editor.change_selections(Some(Autoscroll::center()), cx, |s| { - s.select_clamped_ranges([position..position]) + s.select_clipped_ranges([position..position]) }); }); }); diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index ca1f35bb13ec47d9208363595c38b812b405332a..9b52815ae34db77934952c5717d8645194977f3f 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -259,7 +259,7 @@ impl Rope { .map_or(0, |chunk| chunk.point_to_offset(overshoot)) } - pub fn point_utf16_to_offset_clamped(&self, point: PointUtf16) -> usize { + pub fn point_utf16_to_offset_clipped(&self, point: PointUtf16) -> usize { if point >= self.summary().lines_utf16() { return self.summary().len; } @@ -269,7 +269,7 @@ impl Rope { cursor.start().1 + cursor .item() - .map_or(0, |chunk| chunk.point_utf16_to_offset_clamped(overshoot)) + .map_or(0, |chunk| chunk.point_utf16_to_offset_clipped(overshoot)) } pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point { @@ -711,7 +711,7 @@ impl Chunk { point_utf16 } - fn point_utf16_to_offset_clamped(&self, target: PointUtf16) -> usize { + fn point_utf16_to_offset_clipped(&self, target: PointUtf16) -> usize { let mut offset = 0; let mut point = PointUtf16::new(0, 0); @@ -1210,7 +1210,7 @@ mod tests { point ); assert_eq!( - actual.point_utf16_to_offset_clamped(point_utf16), + actual.point_utf16_to_offset_clipped(point_utf16), ix, "point_utf16_to_offset({:?})", point_utf16 @@ -1251,8 +1251,8 @@ mod tests { let right_point = actual.clip_point_utf16(point_utf16, Bias::Right); assert!(right_point >= left_point); // Ensure translating UTF-16 points to offsets doesn't panic. - actual.point_utf16_to_offset_clamped(left_point); - actual.point_utf16_to_offset_clamped(right_point); + actual.point_utf16_to_offset_clipped(left_point); + actual.point_utf16_to_offset_clipped(right_point); offset_utf16.0 += 1; if unit == b'\n' as u16 { diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index aec46b03cfc152a70903654fd06ffb40112356d7..8cdf087b53c538aabafa0581a2d31d63d4a56846 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1590,8 +1590,8 @@ impl BufferSnapshot { self.visible_text.point_to_offset(point) } - pub fn point_utf16_to_offset_clamped(&self, point: PointUtf16) -> usize { - self.visible_text.point_utf16_to_offset_clamped(point) + pub fn point_utf16_to_offset_clipped(&self, point: PointUtf16) -> usize { + self.visible_text.point_utf16_to_offset_clipped(point) } pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point { @@ -1649,9 +1649,9 @@ impl BufferSnapshot { self.visible_text.chunks_in_range(start..end) } - pub fn text_for_clamped_range(&self, range: Range) -> Chunks<'_> { - let start = range.start.to_offset_clamped(self); - let end = range.end.to_offset_clamped(self); + pub fn text_for_clamped_range(&self, range: Range) -> Chunks<'_> { + let start = range.start.to_offset_clipped(self); + let end = range.end.to_offset_clipped(self); self.visible_text.chunks_in_range(start..end) } @@ -1804,16 +1804,16 @@ impl BufferSnapshot { self.anchor_at(position, Bias::Left) } - pub fn clamped_anchor_before(&self, position: T) -> Anchor { - self.anchor_at_offset(position.to_offset_clamped(self), Bias::Left) + pub fn clamped_anchor_before(&self, position: T) -> Anchor { + self.anchor_at_offset(position.to_offset_clipped(self), Bias::Left) } pub fn anchor_after(&self, position: T) -> Anchor { self.anchor_at(position, Bias::Right) } - pub fn clamped_anchor_after(&self, position: T) -> Anchor { - self.anchor_at_offset(position.to_offset_clamped(self), Bias::Right) + pub fn clamped_anchor_after(&self, position: T) -> Anchor { + self.anchor_at_offset(position.to_offset_clipped(self), Bias::Right) } pub fn anchor_at(&self, position: T, bias: Bias) -> Anchor { @@ -2395,13 +2395,13 @@ impl<'a, T: ToOffset> ToOffset for &'a T { } } -pub trait ToOffsetClamped { - fn to_offset_clamped(&self, snapshot: &BufferSnapshot) -> usize; +pub trait ToOffsetClipped { + fn to_offset_clipped(&self, snapshot: &BufferSnapshot) -> usize; } -impl ToOffsetClamped for PointUtf16 { - fn to_offset_clamped<'a>(&self, snapshot: &BufferSnapshot) -> usize { - snapshot.point_utf16_to_offset_clamped(*self) +impl ToOffsetClipped for PointUtf16 { + fn to_offset_clipped<'a>(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.point_utf16_to_offset_clipped(*self) } } From 55d3c09b6b51e9557c33cde9b3a964a8cb7f39e9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 21 Nov 2022 12:34:36 -0800 Subject: [PATCH 46/67] Fix file extension retrieval for single-file worktrees Previously, we used the file's 'path' method, which only returns the relative path from the worktree root. --- crates/editor/src/editor.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index dd5934f9794c4d65300db90bd3aedc7e69e9429f..426215eb15aed4ce571bec8f16f22ec63091af17 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -73,6 +73,7 @@ use std::{ mem, num::NonZeroU32, ops::{Deref, DerefMut, Range, RangeInclusive}, + path::Path, sync::Arc, time::{Duration, Instant}, }; @@ -6536,15 +6537,13 @@ impl Editor { .as_singleton() .and_then(|b| b.read(cx).file()), ) { - project.read(cx).client().report_event( - name, - json!({ - "File Extension": file - .path() - .extension() - .and_then(|e| e.to_str()) - }), - ); + let extension = Path::new(file.file_name(cx)) + .extension() + .and_then(|e| e.to_str()); + project + .read(cx) + .client() + .report_event(name, json!({ "File Extension": extension })); } } } From 1c84e77c3773dd5c07b2d8e77d3278a9b4ba0916 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 16 Nov 2022 22:20:16 -0500 Subject: [PATCH 47/67] Start adding concept of `Unclipped` text coordinates Co-Authored-By: Max Brunsfeld --- crates/language/src/diagnostic_set.rs | 8 +-- crates/language/src/language.rs | 6 +- crates/project/src/lsp_command.rs | 40 ++++++------ crates/project/src/project.rs | 26 ++++---- crates/project/src/worktree.rs | 7 ++- crates/rope/src/rope.rs | 90 ++++++++++++++++++--------- crates/text/src/text.rs | 86 +++++++++++-------------- 7 files changed, 139 insertions(+), 124 deletions(-) diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index fe2e4c9c212a642c9f8b24e4d5347d5d46e994c2..a4d6dc12c731ec44a60f2fb8078e86355f283d18 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -6,7 +6,7 @@ use std::{ ops::Range, }; use sum_tree::{self, Bias, SumTree}; -use text::{Anchor, FromAnchor, PointUtf16, ToOffset}; +use text::{Anchor, FromAnchor, PointUtf16, ToOffset, Unclipped}; #[derive(Clone, Debug, Default)] pub struct DiagnosticSet { @@ -63,15 +63,15 @@ impl DiagnosticSet { pub fn new(iter: I, buffer: &text::BufferSnapshot) -> Self where - I: IntoIterator>, + I: IntoIterator>>, { let mut entries = iter.into_iter().collect::>(); entries.sort_unstable_by_key(|entry| (entry.range.start, Reverse(entry.range.end))); Self { diagnostics: SumTree::from_iter( entries.into_iter().map(|entry| DiagnosticEntry { - range: buffer.clamped_anchor_before(entry.range.start) - ..buffer.clamped_anchor_after(entry.range.end), + range: buffer.anchor_before(entry.range.start) + ..buffer.anchor_before(entry.range.end), diagnostic: entry.diagnostic, }), buffer, diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 47d724866d7ff91c62ecaa55644d8d4cbf62337a..c3f2c3716b43c208408f67218f191354fcbe0c7b 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -1053,8 +1053,8 @@ pub fn point_to_lsp(point: PointUtf16) -> lsp::Position { lsp::Position::new(point.row, point.column) } -pub fn point_from_lsp(point: lsp::Position) -> PointUtf16 { - PointUtf16::new(point.line, point.character) +pub fn point_from_lsp(point: lsp::Position) -> Unclipped { + Unclipped(PointUtf16::new(point.line, point.character)) } pub fn range_to_lsp(range: Range) -> lsp::Range { @@ -1064,7 +1064,7 @@ pub fn range_to_lsp(range: Range) -> lsp::Range { } } -pub fn range_from_lsp(range: lsp::Range) -> Range { +pub fn range_from_lsp(range: lsp::Range) -> Range> { let mut start = point_from_lsp(range.start); let mut end = point_from_lsp(range.end); if start > end { diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 78c6b50003605bf65c1e2b3f05b75d2d00ca9e89..ae6d18a9a90f6fc34cafc4979de42e7bec293213 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -128,11 +128,11 @@ impl LspCommand for PrepareRename { ) = message { let Range { start, end } = range_from_lsp(range); - if buffer.clip_point_utf16(start, Bias::Left) == start - && buffer.clip_point_utf16(end, Bias::Left) == end + if buffer.clip_point_utf16(start, Bias::Left) == start.0 + && buffer.clip_point_utf16(end, Bias::Left) == end.0 { return Ok(Some( - buffer.clamped_anchor_after(start)..buffer.clamped_anchor_before(end), + buffer.anchor_after(start)..buffer.anchor_before(end), )); } } @@ -145,7 +145,7 @@ impl LspCommand for PrepareRename { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.clamped_anchor_before(self.position), + &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), } @@ -264,7 +264,7 @@ impl LspCommand for PerformRename { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.clamped_anchor_before(self.position), + &buffer.anchor_before(self.position), )), new_name: self.new_name.clone(), version: serialize_version(&buffer.version()), @@ -362,7 +362,7 @@ impl LspCommand for GetDefinition { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.clamped_anchor_before(self.position), + &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), } @@ -448,7 +448,7 @@ impl LspCommand for GetTypeDefinition { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.clamped_anchor_before(self.position), + &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), } @@ -631,8 +631,8 @@ async fn location_links_from_lsp( origin_buffer.clip_point_utf16(point_from_lsp(origin_range.end), Bias::Left); Location { buffer: buffer.clone(), - range: origin_buffer.clamped_anchor_after(origin_start) - ..origin_buffer.clamped_anchor_before(origin_end), + range: origin_buffer.anchor_after(origin_start) + ..origin_buffer.anchor_before(origin_end), } }); @@ -643,8 +643,8 @@ async fn location_links_from_lsp( target_buffer.clip_point_utf16(point_from_lsp(target_range.end), Bias::Left); let target_location = Location { buffer: target_buffer_handle, - range: target_buffer.clamped_anchor_after(target_start) - ..target_buffer.clamped_anchor_before(target_end), + range: target_buffer.anchor_after(target_start) + ..target_buffer.anchor_before(target_end), }; definitions.push(LocationLink { @@ -743,8 +743,8 @@ impl LspCommand for GetReferences { .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left); references.push(Location { buffer: target_buffer_handle, - range: target_buffer.clamped_anchor_after(target_start) - ..target_buffer.clamped_anchor_before(target_end), + range: target_buffer.anchor_after(target_start) + ..target_buffer.anchor_before(target_end), }); }); } @@ -758,7 +758,7 @@ impl LspCommand for GetReferences { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.clamped_anchor_before(self.position), + &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), } @@ -884,8 +884,8 @@ impl LspCommand for GetDocumentHighlights { let end = buffer .clip_point_utf16(point_from_lsp(lsp_highlight.range.end), Bias::Left); DocumentHighlight { - range: buffer.clamped_anchor_after(start) - ..buffer.clamped_anchor_before(end), + range: buffer.anchor_after(start) + ..buffer.anchor_before(end), kind: lsp_highlight .kind .unwrap_or(lsp::DocumentHighlightKind::READ), @@ -900,7 +900,7 @@ impl LspCommand for GetDocumentHighlights { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.clamped_anchor_before(self.position), + &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version()), } @@ -1020,8 +1020,8 @@ impl LspCommand for GetHover { let token_start = buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left); let token_end = buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left); - buffer.clamped_anchor_after(token_start) - ..buffer.clamped_anchor_before(token_end) + buffer.anchor_after(token_start) + ..buffer.anchor_before(token_end) }) }); @@ -1103,7 +1103,7 @@ impl LspCommand for GetHover { project_id, buffer_id: buffer.remote_id(), position: Some(language::proto::serialize_anchor( - &buffer.clamped_anchor_before(self.position), + &buffer.anchor_before(self.position), )), version: serialize_version(&buffer.version), } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e1339ba434c3a80d44da7b97c85693dc25249e23..e1e839db1fc851518604649dfd737723c1720a41 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -25,8 +25,8 @@ use language::{ range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt, - Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToOffsetClipped, ToPointUtf16, - Transaction, + Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, + Transaction, Unclipped, }; use lsp::{ DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, LanguageString, @@ -2598,7 +2598,7 @@ impl Project { language_server_id: usize, abs_path: PathBuf, version: Option, - diagnostics: Vec>, + diagnostics: Vec>>, cx: &mut ModelContext, ) -> Result<(), anyhow::Error> { let (worktree, relative_path) = self @@ -2636,7 +2636,7 @@ impl Project { fn update_buffer_diagnostics( &mut self, buffer: &ModelHandle, - mut diagnostics: Vec>, + mut diagnostics: Vec>>, version: Option, cx: &mut ModelContext, ) -> Result<()> { @@ -2677,16 +2677,14 @@ impl Project { end = entry.range.end; } - let mut range = snapshot.clip_point_utf16(start, Bias::Left) - ..snapshot.clip_point_utf16(end, Bias::Right); + let mut range = start..end; - // Expand empty ranges by one character + // Expand empty ranges by one codepoint if range.start == range.end { + // This will be go to the next boundary when being clipped range.end.column += 1; - range.end = snapshot.clip_point_utf16(range.end, Bias::Right); if range.start == range.end && range.end.column > 0 { range.start.column -= 1; - range.start = snapshot.clip_point_utf16(range.start, Bias::Left); } } @@ -3290,7 +3288,7 @@ impl Project { }; let position = position.to_point_utf16(source_buffer); - let anchor = source_buffer.clamped_anchor_after(position); + let anchor = source_buffer.anchor_after(position); if worktree.read(cx).as_local().is_some() { let buffer_abs_path = buffer_abs_path.unwrap(); @@ -3356,7 +3354,7 @@ impl Project { return None; } ( - snapshot.clamped_anchor_before(start)..snapshot.clamped_anchor_after(end), + snapshot.anchor_before(start)..snapshot.anchor_after(end), edit.new_text.clone(), ) } @@ -5779,11 +5777,11 @@ impl Project { } } } else if range.end == range.start { - let anchor = snapshot.clamped_anchor_after(range.start); + let anchor = snapshot.anchor_after(range.start); edits.push((anchor..anchor, new_text)); } else { - let edit_start = snapshot.clamped_anchor_after(range.start); - let edit_end = snapshot.clamped_anchor_before(range.end); + let edit_start = snapshot.anchor_after(range.start); + let edit_end = snapshot.anchor_before(range.end); edits.push((edit_start..edit_end, new_text)); } } diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 055aa0670631f04caf269eccb7c343b31114f92e..795143c3e09f747726a8d5df9a36223f9a662a52 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -20,6 +20,7 @@ use gpui::{ executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, }; +use language::Unclipped; use language::{ proto::{deserialize_version, serialize_line_ending, serialize_version}, Buffer, DiagnosticEntry, PointUtf16, Rope, @@ -65,7 +66,7 @@ pub struct LocalWorktree { _background_scanner_task: Option>, poll_task: Option>, share: Option, - diagnostics: HashMap, Vec>>, + diagnostics: HashMap, Vec>>>, diagnostic_summaries: TreeMap, client: Arc, fs: Arc, @@ -499,7 +500,7 @@ impl LocalWorktree { }) } - pub fn diagnostics_for_path(&self, path: &Path) -> Option>> { + pub fn diagnostics_for_path(&self, path: &Path) -> Option>>> { self.diagnostics.get(path).cloned() } @@ -507,7 +508,7 @@ impl LocalWorktree { &mut self, language_server_id: usize, worktree_path: Arc, - diagnostics: Vec>, + diagnostics: Vec>>, _: &mut ModelContext, ) -> Result { self.diagnostics.remove(&worktree_path); diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 9b52815ae34db77934952c5717d8645194977f3f..27f0b8cdb414413b140067d26ec992977edaa79c 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -12,6 +12,9 @@ pub use offset_utf16::OffsetUtf16; pub use point::Point; pub use point_utf16::PointUtf16; +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Unclipped(pub T); + #[cfg(test)] const CHUNK_BASE: usize = 6; @@ -259,7 +262,15 @@ impl Rope { .map_or(0, |chunk| chunk.point_to_offset(overshoot)) } - pub fn point_utf16_to_offset_clipped(&self, point: PointUtf16) -> usize { + pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { + self.point_utf16_to_offset_impl(point, false) + } + + pub fn unclipped_point_utf16_to_offset(&self, point: Unclipped) -> usize { + self.point_utf16_to_offset_impl(point.0, true) + } + + fn point_utf16_to_offset_impl(&self, point: PointUtf16, clip: bool) -> usize { if point >= self.summary().lines_utf16() { return self.summary().len; } @@ -269,10 +280,10 @@ impl Rope { cursor.start().1 + cursor .item() - .map_or(0, |chunk| chunk.point_utf16_to_offset_clipped(overshoot)) + .map_or(0, |chunk| chunk.point_utf16_to_offset(overshoot, clip)) } - pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point { + pub fn point_utf16_to_point_clipped(&self, point: PointUtf16) -> Point { if point >= self.summary().lines_utf16() { return self.summary().lines; } @@ -280,9 +291,9 @@ impl Rope { cursor.seek(&point, Bias::Left, &()); let overshoot = point - cursor.start().0; cursor.start().1 - + cursor - .item() - .map_or(Point::zero(), |chunk| chunk.point_utf16_to_point(overshoot)) + + cursor.item().map_or(Point::zero(), |chunk| { + chunk.point_utf16_to_point_clipped(overshoot) + }) } pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize { @@ -330,11 +341,11 @@ impl Rope { } } - pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 { + pub fn clip_point_utf16(&self, point: Unclipped, bias: Bias) -> PointUtf16 { let mut cursor = self.chunks.cursor::(); - cursor.seek(&point, Bias::Right, &()); + cursor.seek(&point.0, Bias::Right, &()); if let Some(chunk) = cursor.item() { - let overshoot = point - cursor.start(); + let overshoot = Unclipped(point.0 - cursor.start()); *cursor.start() + chunk.clip_point_utf16(overshoot, bias) } else { self.summary().lines_utf16() @@ -711,7 +722,7 @@ impl Chunk { point_utf16 } - fn point_utf16_to_offset_clipped(&self, target: PointUtf16) -> usize { + fn point_utf16_to_offset(&self, target: PointUtf16, clip: bool) -> usize { let mut offset = 0; let mut point = PointUtf16::new(0, 0); @@ -723,14 +734,26 @@ impl Chunk { if ch == '\n' { point.row += 1; point.column = 0; + if point.row > target.row { + if clip { + // Return the offset of the newline + return offset; + } + panic!( + "point {:?} is beyond the end of a line with length {}", + target, point.column + ); + } } else { point.column += ch.len_utf16() as u32; } if point > target { - // If the point is past the end of a line or inside of a code point, - // return the last valid offset before the point. - return offset; + if clip { + // Return the offset of the codepoint which we have landed within, bias left + return offset; + } + panic!("point {:?} is inside of codepoint {:?}", target, ch); } offset += ch.len_utf8(); @@ -739,17 +762,21 @@ impl Chunk { offset } - fn point_utf16_to_point(&self, target: PointUtf16) -> Point { + fn point_utf16_to_point_clipped(&self, target: PointUtf16) -> Point { let mut point = Point::zero(); let mut point_utf16 = PointUtf16::zero(); + for ch in self.0.chars() { - if point_utf16 >= target { - if point_utf16 > target { - panic!("point {:?} is inside of character {:?}", target, ch); - } + if point_utf16 == target { break; } + if point_utf16 > target { + // If the point is past the end of a line or inside of a code point, + // return the last valid point before the target. + return point; + } + if ch == '\n' { point_utf16 += PointUtf16::new(1, 0); point += Point::new(1, 0); @@ -758,6 +785,7 @@ impl Chunk { point += Point::new(0, ch.len_utf8() as u32); } } + point } @@ -777,11 +805,11 @@ impl Chunk { unreachable!() } - fn clip_point_utf16(&self, target: PointUtf16, bias: Bias) -> PointUtf16 { + fn clip_point_utf16(&self, target: Unclipped, bias: Bias) -> PointUtf16 { for (row, line) in self.0.split('\n').enumerate() { - if row == target.row as usize { + if row == target.0.row as usize { let mut code_units = line.encode_utf16(); - let mut column = code_units.by_ref().take(target.column as usize).count(); + let mut column = code_units.by_ref().take(target.0.column as usize).count(); if char::decode_utf16(code_units).next().transpose().is_err() { match bias { Bias::Left => column -= 1, @@ -1114,15 +1142,15 @@ mod tests { ); assert_eq!( - rope.clip_point_utf16(PointUtf16::new(0, 1), Bias::Left), + rope.clip_point_utf16(Unclipped(PointUtf16::new(0, 1)), Bias::Left), PointUtf16::new(0, 0) ); assert_eq!( - rope.clip_point_utf16(PointUtf16::new(0, 1), Bias::Right), + rope.clip_point_utf16(Unclipped(PointUtf16::new(0, 1)), Bias::Right), PointUtf16::new(0, 2) ); assert_eq!( - rope.clip_point_utf16(PointUtf16::new(0, 3), Bias::Right), + rope.clip_point_utf16(Unclipped(PointUtf16::new(0, 3)), Bias::Right), PointUtf16::new(0, 2) ); @@ -1210,7 +1238,7 @@ mod tests { point ); assert_eq!( - actual.point_utf16_to_offset_clipped(point_utf16), + actual.point_utf16_to_offset(point_utf16), ix, "point_utf16_to_offset({:?})", point_utf16 @@ -1238,7 +1266,7 @@ mod tests { } let mut offset_utf16 = OffsetUtf16(0); - let mut point_utf16 = PointUtf16::zero(); + let mut point_utf16 = Unclipped(PointUtf16::zero()); for unit in expected.encode_utf16() { let left_offset = actual.clip_offset_utf16(offset_utf16, Bias::Left); let right_offset = actual.clip_offset_utf16(offset_utf16, Bias::Right); @@ -1250,15 +1278,15 @@ mod tests { let left_point = actual.clip_point_utf16(point_utf16, Bias::Left); let right_point = actual.clip_point_utf16(point_utf16, Bias::Right); assert!(right_point >= left_point); - // Ensure translating UTF-16 points to offsets doesn't panic. - actual.point_utf16_to_offset_clipped(left_point); - actual.point_utf16_to_offset_clipped(right_point); + // Ensure translating valid UTF-16 points to offsets doesn't panic. + actual.point_utf16_to_offset(left_point); + actual.point_utf16_to_offset(right_point); offset_utf16.0 += 1; if unit == b'\n' as u16 { - point_utf16 += PointUtf16::new(1, 0); + point_utf16.0 += PointUtf16::new(1, 0); } else { - point_utf16 += PointUtf16::new(0, 1); + point_utf16.0 += PointUtf16::new(0, 1); } } diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 8cdf087b53c538aabafa0581a2d31d63d4a56846..e4469ca141ff2300a58329ab7e4db072063665c9 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1590,12 +1590,16 @@ impl BufferSnapshot { self.visible_text.point_to_offset(point) } - pub fn point_utf16_to_offset_clipped(&self, point: PointUtf16) -> usize { - self.visible_text.point_utf16_to_offset_clipped(point) + pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { + self.visible_text.point_utf16_to_offset(point) } - pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point { - self.visible_text.point_utf16_to_point(point) + pub fn unclipped_point_utf16_to_offset(&self, point: Unclipped) -> usize { + self.visible_text.unclipped_point_utf16_to_offset(point) + } + + pub fn point_utf16_to_point_clipped(&self, point: PointUtf16) -> Point { + self.visible_text.point_utf16_to_point_clipped(point) } pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize { @@ -1649,12 +1653,6 @@ impl BufferSnapshot { self.visible_text.chunks_in_range(start..end) } - pub fn text_for_clamped_range(&self, range: Range) -> Chunks<'_> { - let start = range.start.to_offset_clipped(self); - let end = range.end.to_offset_clipped(self); - self.visible_text.chunks_in_range(start..end) - } - pub fn line_len(&self, row: u32) -> u32 { let row_start_offset = Point::new(row, 0).to_offset(self); let row_end_offset = if row >= self.max_point().row { @@ -1804,18 +1802,10 @@ impl BufferSnapshot { self.anchor_at(position, Bias::Left) } - pub fn clamped_anchor_before(&self, position: T) -> Anchor { - self.anchor_at_offset(position.to_offset_clipped(self), Bias::Left) - } - pub fn anchor_after(&self, position: T) -> Anchor { self.anchor_at(position, Bias::Right) } - pub fn clamped_anchor_after(&self, position: T) -> Anchor { - self.anchor_at_offset(position.to_offset_clipped(self), Bias::Right) - } - pub fn anchor_at(&self, position: T, bias: Bias) -> Anchor { self.anchor_at_offset(position.to_offset(self), bias) } @@ -1857,7 +1847,7 @@ impl BufferSnapshot { self.visible_text.clip_offset_utf16(offset, bias) } - pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 { + pub fn clip_point_utf16(&self, point: Unclipped, bias: Bias) -> PointUtf16 { self.visible_text.clip_point_utf16(point, bias) } @@ -2395,13 +2385,15 @@ impl<'a, T: ToOffset> ToOffset for &'a T { } } -pub trait ToOffsetClipped { - fn to_offset_clipped(&self, snapshot: &BufferSnapshot) -> usize; +impl ToOffset for PointUtf16 { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.point_utf16_to_offset(*self) + } } -impl ToOffsetClipped for PointUtf16 { - fn to_offset_clipped<'a>(&self, snapshot: &BufferSnapshot) -> usize { - snapshot.point_utf16_to_offset_clipped(*self) +impl ToOffset for Unclipped { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.unclipped_point_utf16_to_offset(*self) } } @@ -2427,13 +2419,9 @@ impl ToPoint for Point { } } -pub trait ToPointClamped { - fn to_point_clamped(&self, snapshot: &BufferSnapshot) -> Point; -} - -impl ToPointClamped for PointUtf16 { - fn to_point_clamped<'a>(&self, snapshot: &BufferSnapshot) -> Point { - snapshot.point_utf16_to_point(*self) +impl ToPoint for Unclipped { + fn to_point<'a>(&self, snapshot: &BufferSnapshot) -> Point { + snapshot.point_utf16_to_point_clipped(self.0) } } @@ -2487,27 +2475,27 @@ impl ToOffsetUtf16 for OffsetUtf16 { } } -pub trait Clip { - fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self; -} +// pub trait Clip { +// fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self; +// } -impl Clip for usize { - fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self { - snapshot.clip_offset(*self, bias) - } -} +// impl Clip for usize { +// fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self { +// snapshot.clip_offset(*self, bias) +// } +// } -impl Clip for Point { - fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self { - snapshot.clip_point(*self, bias) - } -} +// impl Clip for Point { +// fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self { +// snapshot.clip_point(*self, bias) +// } +// } -impl Clip for PointUtf16 { - fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self { - snapshot.clip_point_utf16(*self, bias) - } -} +// impl Clip for Unclipped { +// fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self { +// snapshot.clip_point_utf16(self.0, bias) +// } +// } pub trait FromAnchor { fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self; From 8c75df30cb41fdfcdc6450a03dc47fb1957c787f Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 21 Nov 2022 11:47:46 -0500 Subject: [PATCH 48/67] Wrap a bunch of traits for Unclipped --- crates/language/src/buffer_tests.rs | 1 + crates/project/src/lsp_command.rs | 10 ++-- crates/project/src/project.rs | 6 +-- crates/project/src/worktree.rs | 5 +- crates/rope/src/rope.rs | 72 ++++++++++++++++++++++++++++- 5 files changed, 81 insertions(+), 13 deletions(-) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 6043127dd526c2d44d0bb66050c83169c73efbe5..68fe8a294824a0be4a2853f1b41221a951352ae8 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1337,6 +1337,7 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) { (0..entry_count).map(|_| { let range = buffer.random_byte_range(0, &mut rng); let range = range.to_point_utf16(buffer); + let range = Unclipped(range.start)..Unclipped(range.end); DiagnosticEntry { range, diagnostic: Diagnostic { diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index ae6d18a9a90f6fc34cafc4979de42e7bec293213..3ea12617351ecc1708741ad1a60aef6e73702740 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -131,9 +131,7 @@ impl LspCommand for PrepareRename { if buffer.clip_point_utf16(start, Bias::Left) == start.0 && buffer.clip_point_utf16(end, Bias::Left) == end.0 { - return Ok(Some( - buffer.anchor_after(start)..buffer.anchor_before(end), - )); + return Ok(Some(buffer.anchor_after(start)..buffer.anchor_before(end))); } } Ok(None) @@ -884,8 +882,7 @@ impl LspCommand for GetDocumentHighlights { let end = buffer .clip_point_utf16(point_from_lsp(lsp_highlight.range.end), Bias::Left); DocumentHighlight { - range: buffer.anchor_after(start) - ..buffer.anchor_before(end), + range: buffer.anchor_after(start)..buffer.anchor_before(end), kind: lsp_highlight .kind .unwrap_or(lsp::DocumentHighlightKind::READ), @@ -1020,8 +1017,7 @@ impl LspCommand for GetHover { let token_start = buffer.clip_point_utf16(point_from_lsp(range.start), Bias::Left); let token_end = buffer.clip_point_utf16(point_from_lsp(range.end), Bias::Left); - buffer.anchor_after(token_start) - ..buffer.anchor_before(token_end) + buffer.anchor_after(token_start)..buffer.anchor_before(token_end) }) }); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index e1e839db1fc851518604649dfd737723c1720a41..cb8e01d562b39ca102037ad3b16ecf8653d58e7e 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -25,8 +25,8 @@ use language::{ range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt, - Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, - Transaction, Unclipped, + Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, + Unclipped, }; use lsp::{ DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, LanguageString, @@ -2660,7 +2660,7 @@ impl Project { let mut sanitized_diagnostics = Vec::new(); let edits_since_save = Patch::new( snapshot - .edits_since::(buffer.read(cx).saved_version()) + .edits_since::>(buffer.read(cx).saved_version()) .collect(), ); for entry in diagnostics { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 795143c3e09f747726a8d5df9a36223f9a662a52..3bab90d5e34f188b923337d84cadd6397e0e8017 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -500,7 +500,10 @@ impl LocalWorktree { }) } - pub fn diagnostics_for_path(&self, path: &Path) -> Option>>> { + pub fn diagnostics_for_path( + &self, + path: &Path, + ) -> Option>>> { self.diagnostics.get(path).cloned() } diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 27f0b8cdb414413b140067d26ec992977edaa79c..af74b08743f935f1496936bcc8664442504a96f8 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -5,7 +5,11 @@ mod point_utf16; use arrayvec::ArrayString; use bromberg_sl2::{DigestString, HashMatrix}; use smallvec::SmallVec; -use std::{cmp, fmt, io, mem, ops::Range, str}; +use std::{ + cmp, fmt, io, mem, + ops::{Add, AddAssign, Range, Sub, SubAssign}, + str, +}; use sum_tree::{Bias, Dimension, SumTree}; pub use offset_utf16::OffsetUtf16; @@ -15,6 +19,70 @@ pub use point_utf16::PointUtf16; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Unclipped(pub T); +impl std::fmt::Debug for Unclipped { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Unclipped").field(&self.0).finish() + } +} + +impl Default for Unclipped { + fn default() -> Self { + Unclipped(T::default()) + } +} + +impl From for Unclipped { + fn from(value: T) -> Self { + Unclipped(value) + } +} + +impl<'a, T: sum_tree::Dimension<'a, ChunkSummary>> sum_tree::Dimension<'a, ChunkSummary> + for Unclipped +{ + fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) { + self.0.add_summary(summary, &()); + } +} + +impl TextDimension for Unclipped { + fn from_text_summary(summary: &TextSummary) -> Self { + Unclipped(T::from_text_summary(summary)) + } + + fn add_assign(&mut self, other: &Self) { + TextDimension::add_assign(&mut self.0, &other.0); + } +} + +impl> Add> for Unclipped { + type Output = Unclipped; + + fn add(self, rhs: Unclipped) -> Self::Output { + Unclipped(self.0 + rhs.0) + } +} + +impl> Sub> for Unclipped { + type Output = Unclipped; + + fn sub(self, rhs: Unclipped) -> Self::Output { + Unclipped(self.0 - rhs.0) + } +} + +impl> AddAssign> for Unclipped { + fn add_assign(&mut self, rhs: Unclipped) { + self.0 += rhs.0; + } +} + +impl> SubAssign> for Unclipped { + fn sub_assign(&mut self, rhs: Unclipped) { + self.0 -= rhs.0; + } +} + #[cfg(test)] const CHUNK_BASE: usize = 6; @@ -945,7 +1013,7 @@ impl std::ops::Add for TextSummary { type Output = Self; fn add(mut self, rhs: Self) -> Self::Output { - self.add_assign(&rhs); + AddAssign::add_assign(&mut self, &rhs); self } } From e51cbf67ab4bf910963859b6a3eb7339153d929c Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 22 Nov 2022 02:49:47 -0500 Subject: [PATCH 49/67] Fixup compile errors --- crates/diagnostics/src/diagnostics.rs | 22 +++--- crates/editor/src/editor.rs | 2 +- crates/editor/src/multi_buffer.rs | 35 +++++---- crates/editor/src/selections_collection.rs | 13 ---- crates/language/src/proto.rs | 1 + crates/project/src/project.rs | 74 ++++++++++--------- crates/project/src/project_tests.rs | 4 +- crates/project_symbols/src/project_symbols.rs | 2 +- crates/rpc/proto/zed.proto | 9 ++- crates/rpc/src/rpc.rs | 2 +- crates/text/src/text.rs | 22 ------ 11 files changed, 83 insertions(+), 103 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index b4fb6a503c1a58dfa036a2087ed21e55901477f7..bf237b9ad9ecd1d0eae0a13c50e06dfee6a219ac 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -738,7 +738,7 @@ mod tests { DisplayPoint, }; use gpui::TestAppContext; - use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16}; + use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped}; use serde_json::json; use unindent::Unindent as _; use workspace::AppState; @@ -788,7 +788,7 @@ mod tests { None, vec![ DiagnosticEntry { - range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9), + range: Unclipped(PointUtf16::new(1, 8))..Unclipped(PointUtf16::new(1, 9)), diagnostic: Diagnostic { message: "move occurs because `x` has type `Vec`, which does not implement the `Copy` trait" @@ -801,7 +801,7 @@ mod tests { }, }, DiagnosticEntry { - range: PointUtf16::new(2, 8)..PointUtf16::new(2, 9), + range: Unclipped(PointUtf16::new(2, 8))..Unclipped(PointUtf16::new(2, 9)), diagnostic: Diagnostic { message: "move occurs because `y` has type `Vec`, which does not implement the `Copy` trait" @@ -814,7 +814,7 @@ mod tests { }, }, DiagnosticEntry { - range: PointUtf16::new(3, 6)..PointUtf16::new(3, 7), + range: Unclipped(PointUtf16::new(3, 6))..Unclipped(PointUtf16::new(3, 7)), diagnostic: Diagnostic { message: "value moved here".to_string(), severity: DiagnosticSeverity::INFORMATION, @@ -825,7 +825,7 @@ mod tests { }, }, DiagnosticEntry { - range: PointUtf16::new(4, 6)..PointUtf16::new(4, 7), + range: Unclipped(PointUtf16::new(4, 6))..Unclipped(PointUtf16::new(4, 7)), diagnostic: Diagnostic { message: "value moved here".to_string(), severity: DiagnosticSeverity::INFORMATION, @@ -836,7 +836,7 @@ mod tests { }, }, DiagnosticEntry { - range: PointUtf16::new(7, 6)..PointUtf16::new(7, 7), + range: Unclipped(PointUtf16::new(7, 6))..Unclipped(PointUtf16::new(7, 7)), diagnostic: Diagnostic { message: "use of moved value\nvalue used here after move".to_string(), severity: DiagnosticSeverity::ERROR, @@ -847,7 +847,7 @@ mod tests { }, }, DiagnosticEntry { - range: PointUtf16::new(8, 6)..PointUtf16::new(8, 7), + range: Unclipped(PointUtf16::new(8, 6))..Unclipped(PointUtf16::new(8, 7)), diagnostic: Diagnostic { message: "use of moved value\nvalue used here after move".to_string(), severity: DiagnosticSeverity::ERROR, @@ -939,7 +939,7 @@ mod tests { PathBuf::from("/test/consts.rs"), None, vec![DiagnosticEntry { - range: PointUtf16::new(0, 15)..PointUtf16::new(0, 15), + range: Unclipped(PointUtf16::new(0, 15))..Unclipped(PointUtf16::new(0, 15)), diagnostic: Diagnostic { message: "mismatched types\nexpected `usize`, found `char`".to_string(), severity: DiagnosticSeverity::ERROR, @@ -1040,7 +1040,8 @@ mod tests { None, vec![ DiagnosticEntry { - range: PointUtf16::new(0, 15)..PointUtf16::new(0, 15), + range: Unclipped(PointUtf16::new(0, 15)) + ..Unclipped(PointUtf16::new(0, 15)), diagnostic: Diagnostic { message: "mismatched types\nexpected `usize`, found `char`" .to_string(), @@ -1052,7 +1053,8 @@ mod tests { }, }, DiagnosticEntry { - range: PointUtf16::new(1, 15)..PointUtf16::new(1, 15), + range: Unclipped(PointUtf16::new(1, 15)) + ..Unclipped(PointUtf16::new(1, 15)), diagnostic: Diagnostic { message: "unresolved name `c`".to_string(), severity: DiagnosticSeverity::ERROR, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4575e9ce5e9c1e0bdf603bc0fae2429af56ffa93..dd5934f9794c4d65300db90bd3aedc7e69e9429f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -55,7 +55,7 @@ use link_go_to_definition::{ }; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, - ToOffsetClipped, ToPoint, + ToPoint, }; use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index aa25b476803418f6c0ad8b5a2ae4bb04c96a4d3e..969a9702995fe0258e1e7bfeaa3a410b4dea6647 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -11,7 +11,7 @@ use language::{ char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, DiagnosticEntry, Event, File, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _, - ToPoint as _, ToPointUtf16 as _, TransactionId, + ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, }; use smallvec::SmallVec; use std::{ @@ -72,10 +72,6 @@ pub trait ToOffset: 'static + fmt::Debug { fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize; } -pub trait ToOffsetClipped: 'static + fmt::Debug { - fn to_offset_clipped(&self, snapshot: &MultiBufferSnapshot) -> usize; -} - pub trait ToOffsetUtf16: 'static + fmt::Debug { fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16; } @@ -1753,20 +1749,21 @@ impl MultiBufferSnapshot { *cursor.start() + overshoot } - pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 { + pub fn clip_point_utf16(&self, point: Unclipped, bias: Bias) -> PointUtf16 { if let Some((_, _, buffer)) = self.as_singleton() { return buffer.clip_point_utf16(point, bias); } let mut cursor = self.excerpts.cursor::(); - cursor.seek(&point, Bias::Right, &()); + //Cannot not panic if out of bounds as it will just not reach the target position + cursor.seek(&point.0, Bias::Right, &()); let overshoot = if let Some(excerpt) = cursor.item() { let excerpt_start = excerpt .buffer .offset_to_point_utf16(excerpt.range.context.start.to_offset(&excerpt.buffer)); let buffer_point = excerpt .buffer - .clip_point_utf16(excerpt_start + (point - cursor.start()), bias); + .clip_point_utf16(Unclipped(excerpt_start + (point.0 - cursor.start())), bias); buffer_point.saturating_sub(excerpt_start) } else { PointUtf16::zero() @@ -1949,9 +1946,9 @@ impl MultiBufferSnapshot { } } - pub fn point_utf16_to_offset_clipped(&self, point: PointUtf16) -> usize { + pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { if let Some((_, _, buffer)) = self.as_singleton() { - return buffer.point_utf16_to_offset_clipped(point); + return buffer.point_utf16_to_offset(point); } let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>(); @@ -1965,7 +1962,7 @@ impl MultiBufferSnapshot { .offset_to_point_utf16(excerpt.range.context.start.to_offset(&excerpt.buffer)); let buffer_offset = excerpt .buffer - .point_utf16_to_offset_clipped(excerpt_start_point + overshoot); + .point_utf16_to_offset(excerpt_start_point + overshoot); *start_offset + (buffer_offset - excerpt_start_offset) } else { self.excerpts.summary().text.len @@ -3291,9 +3288,9 @@ impl ToOffset for OffsetUtf16 { } } -impl ToOffsetClipped for PointUtf16 { - fn to_offset_clipped<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { - snapshot.point_utf16_to_offset_clipped(*self) +impl ToOffset for PointUtf16 { + fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + snapshot.point_utf16_to_offset(*self) } } @@ -4162,12 +4159,14 @@ mod tests { } for _ in 0..ch.len_utf16() { - let left_point_utf16 = snapshot.clip_point_utf16(point_utf16, Bias::Left); - let right_point_utf16 = snapshot.clip_point_utf16(point_utf16, Bias::Right); + let left_point_utf16 = + snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Left); + let right_point_utf16 = + snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Right); let buffer_left_point_utf16 = - buffer.clip_point_utf16(buffer_point_utf16, Bias::Left); + buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Left); let buffer_right_point_utf16 = - buffer.clip_point_utf16(buffer_point_utf16, Bias::Right); + buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Right); assert_eq!( left_point_utf16, excerpt_start.lines_utf16() diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 14026d9f4e86ce93ac3c40c710a614420d55ca37..facc1b04917cd289645b599cb8060f9ff8dd08d4 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -14,7 +14,6 @@ use util::post_inc; use crate::{ display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint}, Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset, - ToOffsetClipped, }; #[derive(Clone)] @@ -551,18 +550,6 @@ impl<'a> MutableSelectionsCollection<'a> { self.select_offset_ranges(ranges); } - pub fn select_clipped_ranges(&mut self, ranges: I) - where - I: IntoIterator>, - T: ToOffsetClipped, - { - let buffer = self.buffer.read(self.cx).snapshot(self.cx); - let ranges = ranges.into_iter().map(|range| { - range.start.to_offset_clipped(&buffer)..range.end.to_offset_clipped(&buffer) - }); - self.select_offset_ranges(ranges); - } - fn select_offset_ranges(&mut self, ranges: I) where I: IntoIterator>, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 674ce4f50eb5988098c32c3d7557852f9e2b912a..ca86b93bfd462067cb28722bcd58d1a801b05ea0 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -357,6 +357,7 @@ pub fn deserialize_diagnostics( .collect() } +//TODO: Deserialize anchors into `Unclipped`? pub fn deserialize_anchor(anchor: proto::Anchor) -> Option { Some(Anchor { timestamp: clock::Local { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index cb8e01d562b39ca102037ad3b16ecf8653d58e7e..f4752270de6c19a3d286ade1a4efb81bf4f475c6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -253,7 +253,7 @@ pub struct Symbol { pub label: CodeLabel, pub name: String, pub kind: lsp::SymbolKind, - pub range: Range, + pub range: Range>, pub signature: [u8; 32], } @@ -2682,9 +2682,9 @@ impl Project { // Expand empty ranges by one codepoint if range.start == range.end { // This will be go to the next boundary when being clipped - range.end.column += 1; - if range.start == range.end && range.end.column > 0 { - range.start.column -= 1; + range.end.0.column += 1; + if range.start == range.end && range.end.0.column > 0 { + range.start.0.column -= 1; } } @@ -3287,7 +3287,7 @@ impl Project { return Task::ready(Ok(Default::default())); }; - let position = position.to_point_utf16(source_buffer); + let position = Unclipped(position.to_point_utf16(source_buffer)); let anchor = source_buffer.anchor_after(position); if worktree.read(cx).as_local().is_some() { @@ -3306,7 +3306,7 @@ impl Project { lsp::TextDocumentIdentifier::new( lsp::Url::from_file_path(buffer_abs_path).unwrap(), ), - point_to_lsp(position), + point_to_lsp(position.0), ), context: Default::default(), work_done_progress_params: Default::default(), @@ -3349,7 +3349,7 @@ impl Project { let range = range_from_lsp(edit.range); let start = snapshot.clip_point_utf16(range.start, Bias::Left); let end = snapshot.clip_point_utf16(range.end, Bias::Left); - if start != range.start || end != range.end { + if start != range.start.0 || end != range.end.0 { log::info!("completion out of expected range"); return None; } @@ -3361,13 +3361,13 @@ impl Project { // If the language server does not provide a range, then infer // the range based on the syntax tree. None => { - if position != clipped_position { + if position.0 != clipped_position { log::info!("completion out of expected range"); return None; } let Range { start, end } = range_for_token .get_or_insert_with(|| { - let offset = position.to_offset_clipped(&snapshot); + let offset = position.to_offset(&snapshot); let (range, kind) = snapshot.surrounding_word(offset); if kind == Some(CharKind::Word) { range @@ -5116,22 +5116,30 @@ impl Project { _: Arc, mut cx: AsyncAppContext, ) -> Result { - let position = envelope - .payload - .position - .and_then(language::proto::deserialize_anchor) - .ok_or_else(|| anyhow!("invalid position"))?; - let version = deserialize_version(envelope.payload.version); let buffer = this.read_with(&cx, |this, cx| { this.opened_buffers .get(&envelope.payload.buffer_id) .and_then(|buffer| buffer.upgrade(cx)) .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id)) })?; + + let position = envelope + .payload + .position + .and_then(language::proto::deserialize_anchor) + .map(|p| { + buffer.read_with(&cx, |buffer, _| { + buffer.clip_point_utf16(Unclipped(p.to_point_utf16(buffer)), Bias::Left) + }) + }) + .ok_or_else(|| anyhow!("invalid position"))?; + + let version = deserialize_version(envelope.payload.version); buffer .update(&mut cx, |buffer, _| buffer.wait_for_version(version)) .await; let version = buffer.read_with(&cx, |buffer, _| buffer.version()); + let completions = this .update(&mut cx, |this, cx| this.completions(&buffer, position, cx)) .await?; @@ -5618,8 +5626,8 @@ impl Project { }, name: serialized_symbol.name, - range: PointUtf16::new(start.row, start.column) - ..PointUtf16::new(end.row, end.column), + range: Unclipped(PointUtf16::new(start.row, start.column)) + ..Unclipped(PointUtf16::new(end.row, end.column)), kind, signature: serialized_symbol .signature @@ -5705,10 +5713,10 @@ impl Project { let mut lsp_edits = lsp_edits.into_iter().peekable(); let mut edits = Vec::new(); - while let Some((mut range, mut new_text)) = lsp_edits.next() { + while let Some((range, mut new_text)) = lsp_edits.next() { // Clip invalid ranges provided by the language server. - range.start = snapshot.clip_point_utf16(range.start, Bias::Left); - range.end = snapshot.clip_point_utf16(range.end, Bias::Left); + let mut range = snapshot.clip_point_utf16(range.start, Bias::Left) + ..snapshot.clip_point_utf16(range.end, Bias::Left); // Combine any LSP edits that are adjacent. // @@ -5720,11 +5728,11 @@ impl Project { // In order for the diffing logic below to work properly, any edits that // cancel each other out must be combined into one. while let Some((next_range, next_text)) = lsp_edits.peek() { - if next_range.start > range.end { - if next_range.start.row > range.end.row + 1 - || next_range.start.column > 0 + if next_range.start.0 > range.end { + if next_range.start.0.row > range.end.row + 1 + || next_range.start.0.column > 0 || snapshot.clip_point_utf16( - PointUtf16::new(range.end.row, u32::MAX), + Unclipped(PointUtf16::new(range.end.row, u32::MAX)), Bias::Left, ) > range.end { @@ -5732,7 +5740,7 @@ impl Project { } new_text.push('\n'); } - range.end = next_range.end; + range.end = snapshot.clip_point_utf16(next_range.end, Bias::Left); new_text.push_str(next_text); lsp_edits.next(); } @@ -5741,8 +5749,8 @@ impl Project { // we can identify the changes more precisely, preserving the locations // of any anchors positioned in the unchanged regions. if range.end.row > range.start.row { - let mut offset = range.start.to_offset_clipped(&snapshot); - let old_text = snapshot.text_for_clamped_range(range).collect::(); + let mut offset = range.start.to_offset(&snapshot); + let old_text = snapshot.text_for_range(range).collect::(); let diff = TextDiff::from_lines(old_text.as_str(), &new_text); let mut moved_since_edit = true; @@ -6053,13 +6061,13 @@ fn serialize_symbol(symbol: &Symbol) -> proto::Symbol { path: symbol.path.path.to_string_lossy().to_string(), name: symbol.name.clone(), kind: unsafe { mem::transmute(symbol.kind) }, - start: Some(proto::Point { - row: symbol.range.start.row, - column: symbol.range.start.column, + start: Some(proto::UnclippedPoint { + row: symbol.range.start.0.row, + column: symbol.range.start.0.column, }), - end: Some(proto::Point { - row: symbol.range.end.row, - column: symbol.range.end.column, + end: Some(proto::UnclippedPoint { + row: symbol.range.end.0.row, + column: symbol.range.end.0.column, }), signature: symbol.signature.to_vec(), } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index ca274b18b8a37f74d9587470c2a9877d900505e8..dfb699fdbb55ece4e32e7c7a362709ff6ddf92ff 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1239,7 +1239,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) { &buffer, vec![ DiagnosticEntry { - range: PointUtf16::new(0, 10)..PointUtf16::new(0, 10), + range: Unclipped(PointUtf16::new(0, 10))..Unclipped(PointUtf16::new(0, 10)), diagnostic: Diagnostic { severity: DiagnosticSeverity::ERROR, message: "syntax error 1".to_string(), @@ -1247,7 +1247,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) { }, }, DiagnosticEntry { - range: PointUtf16::new(1, 10)..PointUtf16::new(1, 10), + range: Unclipped(PointUtf16::new(1, 10))..Unclipped(PointUtf16::new(1, 10)), diagnostic: Diagnostic { severity: DiagnosticSeverity::ERROR, message: "syntax error 2".to_string(), diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index eb755d2d2f360a2691a447c4549c4d83de4150ba..273230fe26feb5f01cc1cdcbacd7c321d689a446 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -151,7 +151,7 @@ impl ProjectSymbolsView { let editor = workspace.open_project_item::(buffer, cx); editor.update(cx, |editor, cx| { editor.change_selections(Some(Autoscroll::center()), cx, |s| { - s.select_clipped_ranges([position..position]) + s.select_ranges([position..position]) }); }); }); diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index ded708370d3f64d00c478661911e34e37fa8dd98..b6516d235d8f81d18a879a50992f4547126a397b 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -412,8 +412,8 @@ message Symbol { string name = 4; int32 kind = 5; string path = 6; - Point start = 7; - Point end = 8; + UnclippedPoint start = 7; + UnclippedPoint end = 8; bytes signature = 9; } @@ -1047,6 +1047,11 @@ message Point { uint32 column = 2; } +message UnclippedPoint { + uint32 row = 1; + uint32 column = 2; +} + message Nonce { uint64 upper_half = 1; uint64 lower_half = 2; diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index b6aef64677b6f06716a6ea40d9b52a42017c3543..5ca5711d9ca8c43cd5f1979ee76ea11e61053bec 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 39; +pub const PROTOCOL_VERSION: u32 = 40; diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index e4469ca141ff2300a58329ab7e4db072063665c9..aa4ef109cdb5b0909aa3f6d76717f3b7e7e3e9e5 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -2475,28 +2475,6 @@ impl ToOffsetUtf16 for OffsetUtf16 { } } -// pub trait Clip { -// fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self; -// } - -// impl Clip for usize { -// fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self { -// snapshot.clip_offset(*self, bias) -// } -// } - -// impl Clip for Point { -// fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self { -// snapshot.clip_point(*self, bias) -// } -// } - -// impl Clip for Unclipped { -// fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self { -// snapshot.clip_point_utf16(self.0, bias) -// } -// } - pub trait FromAnchor { fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self; } From 5e7652698dcfe64df9cafbcea78989a08d5e5485 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 23 Nov 2022 09:56:06 -0800 Subject: [PATCH 50/67] v0.67.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 709be66ade028b2db82c94d693ff1323f10ba760..93631697c1e93de55d1c32ce89360dadd0926f34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7673,7 +7673,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.66.0" +version = "0.67.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 78349fa797722b585d810934803077329d307991..a3023918e378fbc8845ab2a6dcc7d5b22fbcdd8f 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.66.0" +version = "0.67.0" [lib] name = "zed" From b58ae8bdd7760d78ca9418ac4fd8618abbd1d630 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 23 Nov 2022 13:20:47 -0500 Subject: [PATCH 51/67] Clip diagnostic range before and during empty range expansion Co-Authored-By: Max Brunsfeld --- crates/language/src/buffer_tests.rs | 2 +- crates/language/src/diagnostic_set.rs | 4 ++-- crates/project/src/project.rs | 11 +++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 68fe8a294824a0be4a2853f1b41221a951352ae8..82641dbaa459b493d0a5ff36b6c0426f3a77857c 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1337,7 +1337,7 @@ fn test_random_collaboration(cx: &mut MutableAppContext, mut rng: StdRng) { (0..entry_count).map(|_| { let range = buffer.random_byte_range(0, &mut rng); let range = range.to_point_utf16(buffer); - let range = Unclipped(range.start)..Unclipped(range.end); + let range = range.start..range.end; DiagnosticEntry { range, diagnostic: Diagnostic { diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index a4d6dc12c731ec44a60f2fb8078e86355f283d18..cde5a6fb2bab996dbfba9418ac72eac87652ac7b 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -6,7 +6,7 @@ use std::{ ops::Range, }; use sum_tree::{self, Bias, SumTree}; -use text::{Anchor, FromAnchor, PointUtf16, ToOffset, Unclipped}; +use text::{Anchor, FromAnchor, PointUtf16, ToOffset}; #[derive(Clone, Debug, Default)] pub struct DiagnosticSet { @@ -63,7 +63,7 @@ impl DiagnosticSet { pub fn new(iter: I, buffer: &text::BufferSnapshot) -> Self where - I: IntoIterator>>, + I: IntoIterator>, { let mut entries = iter.into_iter().collect::>(); entries.sort_unstable_by_key(|entry| (entry.range.start, Reverse(entry.range.end))); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f4752270de6c19a3d286ade1a4efb81bf4f475c6..432d13076ff3e7cfbc806a561013fd9c4034b1ea 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2677,14 +2677,17 @@ impl Project { end = entry.range.end; } - let mut range = start..end; + let mut range = snapshot.clip_point_utf16(start, Bias::Left) + ..snapshot.clip_point_utf16(end, Bias::Right); // Expand empty ranges by one codepoint if range.start == range.end { // This will be go to the next boundary when being clipped - range.end.0.column += 1; - if range.start == range.end && range.end.0.column > 0 { - range.start.0.column -= 1; + range.end.column += 1; + range.end = snapshot.clip_point_utf16(Unclipped(range.end), Bias::Right); + if range.start == range.end && range.end.column > 0 { + range.start.column -= 1; + range.end = snapshot.clip_point_utf16(Unclipped(range.end), Bias::Left); } } From a666ca3e407cc0e2cbc2431cc5a6d3d650537dff Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 23 Nov 2022 13:28:44 -0500 Subject: [PATCH 52/67] Collapse proto Point into the one kind of use case, utf-16 coords Co-Authored-By: Max Brunsfeld --- crates/project/src/project.rs | 4 ++-- crates/rpc/proto/zed.proto | 13 +++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 432d13076ff3e7cfbc806a561013fd9c4034b1ea..08714d6cd315277480c6897e3742deeecb95a79a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -6064,11 +6064,11 @@ fn serialize_symbol(symbol: &Symbol) -> proto::Symbol { path: symbol.path.path.to_string_lossy().to_string(), name: symbol.name.clone(), kind: unsafe { mem::transmute(symbol.kind) }, - start: Some(proto::UnclippedPoint { + start: Some(proto::PointUtf16 { row: symbol.range.start.0.row, column: symbol.range.start.0.column, }), - end: Some(proto::UnclippedPoint { + end: Some(proto::PointUtf16 { row: symbol.range.end.0.row, column: symbol.range.end.0.column, }), diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index b6516d235d8f81d18a879a50992f4547126a397b..6bfe7124c9ce936fdee3e11b697f3d3925a0cf22 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -412,8 +412,10 @@ message Symbol { string name = 4; int32 kind = 5; string path = 6; - UnclippedPoint start = 7; - UnclippedPoint end = 8; + // Cannot use generate anchors for unopend files, + // so we are forced to use point coords instead + PointUtf16 start = 7; + PointUtf16 end = 8; bytes signature = 9; } @@ -1042,12 +1044,7 @@ message Range { uint64 end = 2; } -message Point { - uint32 row = 1; - uint32 column = 2; -} - -message UnclippedPoint { +message PointUtf16 { uint32 row = 1; uint32 column = 2; } From 03cfd23ac563d74d43d29ec3651362fb8efa0df3 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 23 Nov 2022 13:33:30 -0500 Subject: [PATCH 53/67] Bump protocol version back down as proto changes are non-breaking --- crates/editor/src/multi_buffer.rs | 1 - crates/language/src/proto.rs | 1 - crates/rpc/src/rpc.rs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 969a9702995fe0258e1e7bfeaa3a410b4dea6647..e3f12c18421832a1add4447c4249872e959971de 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1755,7 +1755,6 @@ impl MultiBufferSnapshot { } let mut cursor = self.excerpts.cursor::(); - //Cannot not panic if out of bounds as it will just not reach the target position cursor.seek(&point.0, Bias::Right, &()); let overshoot = if let Some(excerpt) = cursor.item() { let excerpt_start = excerpt diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index ca86b93bfd462067cb28722bcd58d1a801b05ea0..674ce4f50eb5988098c32c3d7557852f9e2b912a 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -357,7 +357,6 @@ pub fn deserialize_diagnostics( .collect() } -//TODO: Deserialize anchors into `Unclipped`? pub fn deserialize_anchor(anchor: proto::Anchor) -> Option { Some(Anchor { timestamp: clock::Local { diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index 5ca5711d9ca8c43cd5f1979ee76ea11e61053bec..b6aef64677b6f06716a6ea40d9b52a42017c3543 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 40; +pub const PROTOCOL_VERSION: u32 = 39; From 55ca085d7d55efd9c9d487a05d60baacbbccdef9 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 23 Nov 2022 13:52:18 -0500 Subject: [PATCH 54/67] Consistency in prefix/suffix/signature of UTF16 point to point conversion Co-Authored-By: Max Brunsfeld --- crates/rope/src/rope.rs | 16 ++++++++-------- crates/text/src/text.rs | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index af74b08743f935f1496936bcc8664442504a96f8..2a1268eab95f53e3e198fd7a889d33502cb05bde 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -351,16 +351,16 @@ impl Rope { .map_or(0, |chunk| chunk.point_utf16_to_offset(overshoot, clip)) } - pub fn point_utf16_to_point_clipped(&self, point: PointUtf16) -> Point { - if point >= self.summary().lines_utf16() { + pub fn unclipped_point_utf16_to_point(&self, point: Unclipped) -> Point { + if point.0 >= self.summary().lines_utf16() { return self.summary().lines; } let mut cursor = self.chunks.cursor::<(PointUtf16, Point)>(); - cursor.seek(&point, Bias::Left, &()); - let overshoot = point - cursor.start().0; + cursor.seek(&point.0, Bias::Left, &()); + let overshoot = Unclipped(point.0 - cursor.start().0); cursor.start().1 + cursor.item().map_or(Point::zero(), |chunk| { - chunk.point_utf16_to_point_clipped(overshoot) + chunk.unclipped_point_utf16_to_point(overshoot) }) } @@ -830,16 +830,16 @@ impl Chunk { offset } - fn point_utf16_to_point_clipped(&self, target: PointUtf16) -> Point { + fn unclipped_point_utf16_to_point(&self, target: Unclipped) -> Point { let mut point = Point::zero(); let mut point_utf16 = PointUtf16::zero(); for ch in self.0.chars() { - if point_utf16 == target { + if point_utf16 == target.0 { break; } - if point_utf16 > target { + if point_utf16 > target.0 { // If the point is past the end of a line or inside of a code point, // return the last valid point before the target. return point; diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index aa4ef109cdb5b0909aa3f6d76717f3b7e7e3e9e5..7e486d231e8bced733779ac1b04183a209a199b8 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1598,8 +1598,8 @@ impl BufferSnapshot { self.visible_text.unclipped_point_utf16_to_offset(point) } - pub fn point_utf16_to_point_clipped(&self, point: PointUtf16) -> Point { - self.visible_text.point_utf16_to_point_clipped(point) + pub fn unclipped_point_utf16_to_point(&self, point: Unclipped) -> Point { + self.visible_text.unclipped_point_utf16_to_point(point) } pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize { @@ -2421,7 +2421,7 @@ impl ToPoint for Point { impl ToPoint for Unclipped { fn to_point<'a>(&self, snapshot: &BufferSnapshot) -> Point { - snapshot.point_utf16_to_point_clipped(self.0) + snapshot.unclipped_point_utf16_to_point(*self) } } From 525d84e5bf94cc20cf407049e5286171b61c18bd Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 23 Nov 2022 13:52:39 -0500 Subject: [PATCH 55/67] Remove spurious lifetimes Co-Authored-By: Max Brunsfeld --- crates/text/src/text.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 7e486d231e8bced733779ac1b04183a209a199b8..0a260c08cee80b1dae15e251be66b45a8bd3e872 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -2361,20 +2361,20 @@ pub trait ToOffset { } impl ToOffset for Point { - fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { snapshot.point_to_offset(*self) } } impl ToOffset for usize { - fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { assert!(*self <= snapshot.len(), "offset {self} is out of range"); *self } } impl ToOffset for Anchor { - fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { snapshot.summary_for_anchor(self) } } @@ -2402,25 +2402,25 @@ pub trait ToPoint { } impl ToPoint for Anchor { - fn to_point<'a>(&self, snapshot: &BufferSnapshot) -> Point { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point { snapshot.summary_for_anchor(self) } } impl ToPoint for usize { - fn to_point<'a>(&self, snapshot: &BufferSnapshot) -> Point { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point { snapshot.offset_to_point(*self) } } impl ToPoint for Point { - fn to_point<'a>(&self, _: &BufferSnapshot) -> Point { + fn to_point(&self, _: &BufferSnapshot) -> Point { *self } } impl ToPoint for Unclipped { - fn to_point<'a>(&self, snapshot: &BufferSnapshot) -> Point { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point { snapshot.unclipped_point_utf16_to_point(*self) } } @@ -2430,25 +2430,25 @@ pub trait ToPointUtf16 { } impl ToPointUtf16 for Anchor { - fn to_point_utf16<'a>(&self, snapshot: &BufferSnapshot) -> PointUtf16 { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 { snapshot.summary_for_anchor(self) } } impl ToPointUtf16 for usize { - fn to_point_utf16<'a>(&self, snapshot: &BufferSnapshot) -> PointUtf16 { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 { snapshot.offset_to_point_utf16(*self) } } impl ToPointUtf16 for PointUtf16 { - fn to_point_utf16<'a>(&self, _: &BufferSnapshot) -> PointUtf16 { + fn to_point_utf16(&self, _: &BufferSnapshot) -> PointUtf16 { *self } } impl ToPointUtf16 for Point { - fn to_point_utf16<'a>(&self, snapshot: &BufferSnapshot) -> PointUtf16 { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 { snapshot.point_to_point_utf16(*self) } } @@ -2458,19 +2458,19 @@ pub trait ToOffsetUtf16 { } impl ToOffsetUtf16 for Anchor { - fn to_offset_utf16<'a>(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 { + fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 { snapshot.summary_for_anchor(self) } } impl ToOffsetUtf16 for usize { - fn to_offset_utf16<'a>(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 { + fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 { snapshot.offset_to_offset_utf16(*self) } } impl ToOffsetUtf16 for OffsetUtf16 { - fn to_offset_utf16<'a>(&self, _snapshot: &BufferSnapshot) -> OffsetUtf16 { + fn to_offset_utf16(&self, _snapshot: &BufferSnapshot) -> OffsetUtf16 { *self } } From 09e6d4487370aeebd290e88d68530eb048c2083f Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 23 Nov 2022 14:02:11 -0500 Subject: [PATCH 56/67] Move Unclipped into separate file --- crates/rope/src/rope.rs | 71 ++---------------------------------- crates/rope/src/unclipped.rs | 57 +++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 68 deletions(-) create mode 100644 crates/rope/src/unclipped.rs diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 2a1268eab95f53e3e198fd7a889d33502cb05bde..d4ee894310a103cd8fd183da3202c756f5b85605 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -1,13 +1,14 @@ mod offset_utf16; mod point; mod point_utf16; +mod unclipped; use arrayvec::ArrayString; use bromberg_sl2::{DigestString, HashMatrix}; use smallvec::SmallVec; use std::{ cmp, fmt, io, mem, - ops::{Add, AddAssign, Range, Sub, SubAssign}, + ops::{AddAssign, Range}, str, }; use sum_tree::{Bias, Dimension, SumTree}; @@ -15,73 +16,7 @@ use sum_tree::{Bias, Dimension, SumTree}; pub use offset_utf16::OffsetUtf16; pub use point::Point; pub use point_utf16::PointUtf16; - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Unclipped(pub T); - -impl std::fmt::Debug for Unclipped { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Unclipped").field(&self.0).finish() - } -} - -impl Default for Unclipped { - fn default() -> Self { - Unclipped(T::default()) - } -} - -impl From for Unclipped { - fn from(value: T) -> Self { - Unclipped(value) - } -} - -impl<'a, T: sum_tree::Dimension<'a, ChunkSummary>> sum_tree::Dimension<'a, ChunkSummary> - for Unclipped -{ - fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) { - self.0.add_summary(summary, &()); - } -} - -impl TextDimension for Unclipped { - fn from_text_summary(summary: &TextSummary) -> Self { - Unclipped(T::from_text_summary(summary)) - } - - fn add_assign(&mut self, other: &Self) { - TextDimension::add_assign(&mut self.0, &other.0); - } -} - -impl> Add> for Unclipped { - type Output = Unclipped; - - fn add(self, rhs: Unclipped) -> Self::Output { - Unclipped(self.0 + rhs.0) - } -} - -impl> Sub> for Unclipped { - type Output = Unclipped; - - fn sub(self, rhs: Unclipped) -> Self::Output { - Unclipped(self.0 - rhs.0) - } -} - -impl> AddAssign> for Unclipped { - fn add_assign(&mut self, rhs: Unclipped) { - self.0 += rhs.0; - } -} - -impl> SubAssign> for Unclipped { - fn sub_assign(&mut self, rhs: Unclipped) { - self.0 -= rhs.0; - } -} +pub use unclipped::Unclipped; #[cfg(test)] const CHUNK_BASE: usize = 6; diff --git a/crates/rope/src/unclipped.rs b/crates/rope/src/unclipped.rs new file mode 100644 index 0000000000000000000000000000000000000000..937cbca0534d0ee8da7059bee79c6625e7d4a329 --- /dev/null +++ b/crates/rope/src/unclipped.rs @@ -0,0 +1,57 @@ +use crate::{ChunkSummary, TextDimension, TextSummary}; +use std::ops::{Add, AddAssign, Sub, SubAssign}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Unclipped(pub T); + +impl From for Unclipped { + fn from(value: T) -> Self { + Unclipped(value) + } +} + +impl<'a, T: sum_tree::Dimension<'a, ChunkSummary>> sum_tree::Dimension<'a, ChunkSummary> + for Unclipped +{ + fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) { + self.0.add_summary(summary, &()); + } +} + +impl TextDimension for Unclipped { + fn from_text_summary(summary: &TextSummary) -> Self { + Unclipped(T::from_text_summary(summary)) + } + + fn add_assign(&mut self, other: &Self) { + TextDimension::add_assign(&mut self.0, &other.0); + } +} + +impl> Add> for Unclipped { + type Output = Unclipped; + + fn add(self, rhs: Unclipped) -> Self::Output { + Unclipped(self.0 + rhs.0) + } +} + +impl> Sub> for Unclipped { + type Output = Unclipped; + + fn sub(self, rhs: Unclipped) -> Self::Output { + Unclipped(self.0 - rhs.0) + } +} + +impl> AddAssign> for Unclipped { + fn add_assign(&mut self, rhs: Unclipped) { + self.0 += rhs.0; + } +} + +impl> SubAssign> for Unclipped { + fn sub_assign(&mut self, rhs: Unclipped) { + self.0 -= rhs.0; + } +} From aeea47323a94bcc69d686632d97f74e7ccb8c1bb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 23 Nov 2022 13:37:22 -0800 Subject: [PATCH 57/67] Fix enclosing-bracket bug that appeared in JS for loops Previously, we were relying on the tree-sitter query's range restriction to avoid returning brackets that did not contain the given range. But the query's range restriction only guarantees that we don't descend into parent nodes unless they intersect the range. --- crates/language/src/buffer.rs | 31 ++++++++------- crates/language/src/buffer_tests.rs | 62 ++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 16 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 2a0b158a7a412695c1d2022448068d93702ed7b0..e8bc2bf314c45784de7653c1d63d803379166f2c 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -2225,11 +2225,12 @@ impl BufferSnapshot { range: Range, ) -> Option<(Range, Range)> { // Find bracket pairs that *inclusively* contain the given range. - let range = range.start.to_offset(self).saturating_sub(1) - ..self.len().min(range.end.to_offset(self) + 1); - let mut matches = self.syntax.matches(range, &self.text, |grammar| { - grammar.brackets_config.as_ref().map(|c| &c.query) - }); + let range = range.start.to_offset(self)..range.end.to_offset(self); + let mut matches = self.syntax.matches( + range.start.saturating_sub(1)..self.len().min(range.end + 1), + &self.text, + |grammar| grammar.brackets_config.as_ref().map(|c| &c.query), + ); let configs = matches .grammars() .iter() @@ -2252,18 +2253,20 @@ impl BufferSnapshot { matches.advance(); - if let Some((open, close)) = open.zip(close) { - let len = close.end - open.start; + let Some((open, close)) = open.zip(close) else { continue }; + if open.start > range.start || close.end < range.end { + continue; + } + let len = close.end - open.start; - if let Some((existing_open, existing_close)) = &result { - let existing_len = existing_close.end - existing_open.start; - if len > existing_len { - continue; - } + if let Some((existing_open, existing_close)) = &result { + let existing_len = existing_close.end - existing_open.start; + if len > existing_len { + continue; } - - result = Some((open, close)); } + + result = Some((open, close)); } result diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 6043127dd526c2d44d0bb66050c83169c73efbe5..116bf175f1e6c480886c61afc94d22fbc94d85b4 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -573,14 +573,72 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) { )) ); - // Regression test: avoid crash when querying at the end of the buffer. assert_eq!( - buffer.enclosing_bracket_point_ranges(buffer.len() - 1..buffer.len()), + buffer.enclosing_bracket_point_ranges(Point::new(4, 1)..Point::new(4, 1)), Some(( Point::new(0, 6)..Point::new(0, 7), Point::new(4, 0)..Point::new(4, 1) )) ); + + // Regression test: avoid crash when querying at the end of the buffer. + assert_eq!( + buffer.enclosing_bracket_point_ranges(Point::new(4, 1)..Point::new(5, 0)), + None + ); +} + +#[gpui::test] +fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children( + cx: &mut MutableAppContext, +) { + let javascript_language = Arc::new( + Language::new( + LanguageConfig { + name: "JavaScript".into(), + ..Default::default() + }, + Some(tree_sitter_javascript::language()), + ) + .with_brackets_query( + r#" + ("{" @open "}" @close) + ("(" @open ")" @close) + "#, + ) + .unwrap(), + ); + + cx.set_global(Settings::test(cx)); + let buffer = cx.add_model(|cx| { + let text = " + for (const a in b) { + // a comment that's longer than the for-loop header + } + " + .unindent(); + Buffer::new(0, text, cx).with_language(javascript_language, cx) + }); + + let buffer = buffer.read(cx); + assert_eq!( + buffer.enclosing_bracket_point_ranges(Point::new(0, 18)..Point::new(0, 18)), + Some(( + Point::new(0, 4)..Point::new(0, 5), + Point::new(0, 17)..Point::new(0, 18) + )) + ); + + // Regression test: even though the parent node of the parentheses (the for loop) does + // intersect the given range, the parentheses themselves do not contain the range, so + // they should not be returned. Only the curly braces contain the range. + assert_eq!( + buffer.enclosing_bracket_point_ranges(Point::new(0, 20)..Point::new(0, 20)), + Some(( + Point::new(0, 19)..Point::new(0, 20), + Point::new(2, 0)..Point::new(2, 1) + )) + ); } #[gpui::test] From f71145bb3255e13a6559ab7e8cc8d4e9a15ca49c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 23 Nov 2022 16:13:10 -0800 Subject: [PATCH 58/67] Add a layer of indirection between excerpt ids and locators --- crates/diagnostics/src/diagnostics.rs | 11 +- crates/editor/src/display_map/block_map.rs | 6 +- crates/editor/src/editor_tests.rs | 16 +- crates/editor/src/element.rs | 7 +- crates/editor/src/multi_buffer.rs | 395 ++++++++++++++------- crates/editor/src/multi_buffer/anchor.rs | 12 +- crates/text/src/locator.rs | 12 +- crates/text/src/text.rs | 4 +- 8 files changed, 297 insertions(+), 166 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index bf237b9ad9ecd1d0eae0a13c50e06dfee6a219ac..6ff74901811eba2f29a2430d63525006a7e6515c 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -322,7 +322,7 @@ impl ProjectDiagnosticsEditor { ); let excerpt_id = excerpts .insert_excerpts_after( - &prev_excerpt_id, + prev_excerpt_id, buffer.clone(), [ExcerptRange { context: excerpt_start..excerpt_end, @@ -384,7 +384,7 @@ impl ProjectDiagnosticsEditor { groups_to_add.push(group_state); } else if let Some((group_ix, group_state)) = to_remove { - excerpts.remove_excerpts(group_state.excerpts.iter(), excerpts_cx); + excerpts.remove_excerpts(group_state.excerpts.iter().copied(), excerpts_cx); group_ixs_to_remove.push(group_ix); blocks_to_remove.extend(group_state.blocks.iter().copied()); } else if let Some((_, group)) = to_keep { @@ -457,10 +457,15 @@ impl ProjectDiagnosticsEditor { } // If any selection has lost its position, move it to start of the next primary diagnostic. + let snapshot = editor.snapshot(cx); for selection in &mut selections { if let Some(new_excerpt_id) = new_excerpt_ids_by_selection_id.get(&selection.id) { let group_ix = match groups.binary_search_by(|probe| { - probe.excerpts.last().unwrap().cmp(new_excerpt_id) + probe + .excerpts + .last() + .unwrap() + .cmp(new_excerpt_id, &snapshot.buffer_snapshot) }) { Ok(ix) | Err(ix) => ix, }; diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index ee07c77d0129408f7674635280b358b87a93dc7d..797f41c52ac829e96a4230d036e6a80ff9d75c30 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -2,7 +2,7 @@ use super::{ wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot}, TextHighlights, }; -use crate::{Anchor, ExcerptRange, ToPoint as _}; +use crate::{Anchor, ExcerptId, ExcerptRange, ToPoint as _}; use collections::{Bound, HashMap, HashSet}; use gpui::{ElementBox, RenderContext}; use language::{BufferSnapshot, Chunk, Patch, Point}; @@ -107,7 +107,7 @@ struct Transform { pub enum TransformBlock { Custom(Arc), ExcerptHeader { - key: usize, + id: ExcerptId, buffer: BufferSnapshot, range: ExcerptRange, height: u8, @@ -371,7 +371,7 @@ impl BlockMap { .make_wrap_point(Point::new(excerpt_boundary.row, 0), Bias::Left) .row(), TransformBlock::ExcerptHeader { - key: excerpt_boundary.key, + id: excerpt_boundary.id, buffer: excerpt_boundary.buffer, range: excerpt_boundary.range, height: if excerpt_boundary.starts_new_buffer { diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 7bd5dda522f6298067619074ce79647e07f1b133..0ed5023c43e691997c2103f8424d02a6f2fae846 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -4718,9 +4718,7 @@ fn test_refresh_selections(cx: &mut gpui::MutableAppContext) { // Refreshing selections is a no-op when excerpts haven't changed. editor.update(cx, |editor, cx| { - editor.change_selections(None, cx, |s| { - s.refresh(); - }); + editor.change_selections(None, cx, |s| s.refresh()); assert_eq!( editor.selections.ranges(cx), [ @@ -4731,7 +4729,7 @@ fn test_refresh_selections(cx: &mut gpui::MutableAppContext) { }); multibuffer.update(cx, |multibuffer, cx| { - multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx); + multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx); }); editor.update(cx, |editor, cx| { // Removing an excerpt causes the first selection to become degenerate. @@ -4745,9 +4743,7 @@ fn test_refresh_selections(cx: &mut gpui::MutableAppContext) { // Refreshing selections will relocate the first selection to the original buffer // location. - editor.change_selections(None, cx, |s| { - s.refresh(); - }); + editor.change_selections(None, cx, |s| s.refresh()); assert_eq!( editor.selections.ranges(cx), [ @@ -4801,7 +4797,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppC }); multibuffer.update(cx, |multibuffer, cx| { - multibuffer.remove_excerpts([&excerpt1_id.unwrap()], cx); + multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx); }); editor.update(cx, |editor, cx| { assert_eq!( @@ -4810,9 +4806,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::MutableAppC ); // Ensure we don't panic when selections are refreshed and that the pending selection is finalized. - editor.change_selections(None, cx, |s| { - s.refresh(); - }); + editor.change_selections(None, cx, |s| s.refresh()); assert_eq!( editor.selections.ranges(cx), [Point::new(0, 3)..Point::new(0, 3)] diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index f62f27bb0d8747d9779fda0814d0708411909d53..1fd90b7a33cc549ac546024a753696c837d9d6ff 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1334,12 +1334,13 @@ impl EditorElement { }) } TransformBlock::ExcerptHeader { - key, + id, buffer, range, starts_new_buffer, .. } => { + let id = *id; let jump_icon = project::File::from_dyn(buffer.file()).map(|file| { let jump_position = range .primary @@ -1356,7 +1357,7 @@ impl EditorElement { enum JumpIcon {} cx.render(&editor, |_, cx| { - MouseEventHandler::::new(*key, cx, |state, _| { + MouseEventHandler::::new(id.into(), cx, |state, _| { let style = style.jump_icon.style_for(state, false); Svg::new("icons/arrow_up_right_8.svg") .with_color(style.color) @@ -1375,7 +1376,7 @@ impl EditorElement { cx.dispatch_action(jump_action.clone()) }) .with_tooltip::( - *key, + id.into(), "Jump to Buffer".to_string(), Some(Box::new(crate::OpenExcerpts)), tooltip_style.clone(), diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index e3f12c18421832a1add4447c4249872e959971de..f8bfd80335965fa92194404ea96327eb8a9ed8c2 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -36,13 +36,13 @@ use util::post_inc; const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize]; -pub type ExcerptId = Locator; +#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct ExcerptId(usize); pub struct MultiBuffer { snapshot: RefCell, buffers: RefCell>, - used_excerpt_ids: SumTree, - next_excerpt_key: usize, + next_excerpt_id: usize, subscriptions: Topic, singleton: bool, replica_id: ReplicaId, @@ -92,7 +92,7 @@ struct BufferState { last_diagnostics_update_count: usize, last_file_update_count: usize, last_git_diff_update_count: usize, - excerpts: Vec, + excerpts: Vec, _subscriptions: [gpui::Subscription; 2], } @@ -100,6 +100,7 @@ struct BufferState { pub struct MultiBufferSnapshot { singleton: bool, excerpts: SumTree, + excerpt_ids: SumTree, parse_count: usize, diagnostics_update_count: usize, trailing_excerpt_update_count: usize, @@ -111,7 +112,6 @@ pub struct MultiBufferSnapshot { pub struct ExcerptBoundary { pub id: ExcerptId, - pub key: usize, pub row: u32, pub buffer: BufferSnapshot, pub range: ExcerptRange, @@ -121,7 +121,7 @@ pub struct ExcerptBoundary { #[derive(Clone)] struct Excerpt { id: ExcerptId, - key: usize, + locator: Locator, buffer_id: usize, buffer: BufferSnapshot, range: ExcerptRange, @@ -130,6 +130,12 @@ struct Excerpt { has_trailing_newline: bool, } +#[derive(Clone, Debug)] +struct ExcerptIdMapping { + id: ExcerptId, + locator: Locator, +} + #[derive(Clone, Debug, Eq, PartialEq)] pub struct ExcerptRange { pub context: Range, @@ -139,6 +145,7 @@ pub struct ExcerptRange { #[derive(Clone, Debug, Default)] struct ExcerptSummary { excerpt_id: ExcerptId, + excerpt_locator: Locator, max_buffer_row: u32, text: TextSummary, } @@ -178,8 +185,7 @@ impl MultiBuffer { Self { snapshot: Default::default(), buffers: Default::default(), - used_excerpt_ids: Default::default(), - next_excerpt_key: Default::default(), + next_excerpt_id: 1, subscriptions: Default::default(), singleton: false, replica_id, @@ -218,8 +224,7 @@ impl MultiBuffer { Self { snapshot: RefCell::new(self.snapshot.borrow().clone()), buffers: RefCell::new(buffers), - used_excerpt_ids: self.used_excerpt_ids.clone(), - next_excerpt_key: self.next_excerpt_key, + next_excerpt_id: 1, subscriptions: Default::default(), singleton: self.singleton, replica_id: self.replica_id, @@ -610,11 +615,14 @@ impl MultiBuffer { let mut selections_by_buffer: HashMap>> = Default::default(); let snapshot = self.read(cx); - let mut cursor = snapshot.excerpts.cursor::>(); + let mut cursor = snapshot.excerpts.cursor::>(); for selection in selections { - cursor.seek(&Some(&selection.start.excerpt_id), Bias::Left, &()); + let start_locator = snapshot.excerpt_locator_for_id(selection.start.excerpt_id); + let end_locator = snapshot.excerpt_locator_for_id(selection.end.excerpt_id); + + cursor.seek(&Some(start_locator), Bias::Left, &()); while let Some(excerpt) = cursor.item() { - if excerpt.id > selection.end.excerpt_id { + if excerpt.locator > *end_locator { break; } @@ -745,7 +753,7 @@ impl MultiBuffer { where O: text::ToOffset, { - self.insert_excerpts_after(&ExcerptId::max(), buffer, ranges, cx) + self.insert_excerpts_after(ExcerptId::max(), buffer, ranges, cx) } pub fn push_excerpts_with_context_lines( @@ -818,7 +826,7 @@ impl MultiBuffer { pub fn insert_excerpts_after( &mut self, - prev_excerpt_id: &ExcerptId, + prev_excerpt_id: ExcerptId, buffer: ModelHandle, ranges: impl IntoIterator>, cx: &mut ModelContext, @@ -854,8 +862,12 @@ impl MultiBuffer { }); let mut snapshot = self.snapshot.borrow_mut(); - let mut cursor = snapshot.excerpts.cursor::>(); - let mut new_excerpts = cursor.slice(&Some(prev_excerpt_id), Bias::Right, &()); + + let mut prev_locator = snapshot.excerpt_locator_for_id(prev_excerpt_id).clone(); + let mut new_excerpt_ids = mem::take(&mut snapshot.excerpt_ids); + let mut cursor = snapshot.excerpts.cursor::>(); + let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right, &()); + prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone(); let edit_start = new_excerpts.summary().text.len; new_excerpts.update_last( @@ -865,25 +877,17 @@ impl MultiBuffer { &(), ); - let mut used_cursor = self.used_excerpt_ids.cursor::(); - used_cursor.seek(prev_excerpt_id, Bias::Right, &()); - let mut prev_id = if let Some(excerpt_id) = used_cursor.prev_item() { - excerpt_id.clone() + let next_locator = if let Some(excerpt) = cursor.item() { + excerpt.locator.clone() } else { - ExcerptId::min() + Locator::max() }; - let next_id = if let Some(excerpt_id) = used_cursor.item() { - excerpt_id.clone() - } else { - ExcerptId::max() - }; - drop(used_cursor); let mut ids = Vec::new(); while let Some(range) = ranges.next() { - let id = ExcerptId::between(&prev_id, &next_id); - if let Err(ix) = buffer_state.excerpts.binary_search(&id) { - buffer_state.excerpts.insert(ix, id.clone()); + let locator = Locator::between(&prev_locator, &next_locator); + if let Err(ix) = buffer_state.excerpts.binary_search(&locator) { + buffer_state.excerpts.insert(ix, locator.clone()); } let range = ExcerptRange { context: buffer_snapshot.anchor_before(&range.context.start) @@ -893,22 +897,20 @@ impl MultiBuffer { ..buffer_snapshot.anchor_after(&primary.end) }), }; + let id = ExcerptId(post_inc(&mut self.next_excerpt_id)); let excerpt = Excerpt::new( - id.clone(), - post_inc(&mut self.next_excerpt_key), + id, + locator.clone(), buffer_id, buffer_snapshot.clone(), range, ranges.peek().is_some() || cursor.item().is_some(), ); new_excerpts.push(excerpt, &()); - prev_id = id.clone(); + prev_locator = locator.clone(); + new_excerpt_ids.push(ExcerptIdMapping { id, locator }, &()); ids.push(id); } - self.used_excerpt_ids.edit( - ids.iter().cloned().map(sum_tree::Edit::Insert).collect(), - &(), - ); let edit_end = new_excerpts.summary().text.len; @@ -917,6 +919,7 @@ impl MultiBuffer { new_excerpts.push_tree(suffix, &()); drop(cursor); snapshot.excerpts = new_excerpts; + snapshot.excerpt_ids = new_excerpt_ids; if changed_trailing_excerpt { snapshot.trailing_excerpt_update_count += 1; } @@ -956,16 +959,16 @@ impl MultiBuffer { let mut excerpts = Vec::new(); let snapshot = self.read(cx); let buffers = self.buffers.borrow(); - let mut cursor = snapshot.excerpts.cursor::>(); - for excerpt_id in buffers + let mut cursor = snapshot.excerpts.cursor::>(); + for locator in buffers .get(&buffer.id()) .map(|state| &state.excerpts) .into_iter() .flatten() { - cursor.seek_forward(&Some(excerpt_id), Bias::Left, &()); + cursor.seek_forward(&Some(locator), Bias::Left, &()); if let Some(excerpt) = cursor.item() { - if excerpt.id == *excerpt_id { + if excerpt.locator == *locator { excerpts.push((excerpt.id.clone(), excerpt.range.clone())); } } @@ -975,10 +978,11 @@ impl MultiBuffer { } pub fn excerpt_ids(&self) -> Vec { - self.buffers + self.snapshot .borrow() - .values() - .flat_map(|state| state.excerpts.iter().cloned()) + .excerpts + .iter() + .map(|entry| entry.id) .collect() } @@ -1061,32 +1065,34 @@ impl MultiBuffer { result } - pub fn remove_excerpts<'a>( + pub fn remove_excerpts( &mut self, - excerpt_ids: impl IntoIterator, + excerpt_ids: impl IntoIterator, cx: &mut ModelContext, ) { self.sync(cx); let mut buffers = self.buffers.borrow_mut(); let mut snapshot = self.snapshot.borrow_mut(); let mut new_excerpts = SumTree::new(); - let mut cursor = snapshot.excerpts.cursor::<(Option<&ExcerptId>, usize)>(); + let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(); let mut edits = Vec::new(); let mut excerpt_ids = excerpt_ids.into_iter().peekable(); - while let Some(mut excerpt_id) = excerpt_ids.next() { + while let Some(excerpt_id) = excerpt_ids.next() { // Seek to the next excerpt to remove, preserving any preceding excerpts. - new_excerpts.push_tree(cursor.slice(&Some(excerpt_id), Bias::Left, &()), &()); + let locator = snapshot.excerpt_locator_for_id(excerpt_id); + new_excerpts.push_tree(cursor.slice(&Some(locator), Bias::Left, &()), &()); + if let Some(mut excerpt) = cursor.item() { - if excerpt.id != *excerpt_id { + if excerpt.id != excerpt_id { continue; } let mut old_start = cursor.start().1; // Skip over the removed excerpt. - loop { + 'remove_excerpts: loop { if let Some(buffer_state) = buffers.get_mut(&excerpt.buffer_id) { - buffer_state.excerpts.retain(|id| id != excerpt_id); + buffer_state.excerpts.retain(|l| l != &excerpt.locator); if buffer_state.excerpts.is_empty() { buffers.remove(&excerpt.buffer_id); } @@ -1094,14 +1100,16 @@ impl MultiBuffer { cursor.next(&()); // Skip over any subsequent excerpts that are also removed. - if let Some(&next_excerpt_id) = excerpt_ids.peek() { + while let Some(&next_excerpt_id) = excerpt_ids.peek() { + let next_locator = snapshot.excerpt_locator_for_id(next_excerpt_id); if let Some(next_excerpt) = cursor.item() { - if next_excerpt.id == *next_excerpt_id { + if next_excerpt.locator == *next_locator { + excerpt_ids.next(); excerpt = next_excerpt; - excerpt_id = excerpt_ids.next().unwrap(); - continue; + continue 'remove_excerpts; } } + break; } break; @@ -1128,6 +1136,7 @@ impl MultiBuffer { new_excerpts.push_tree(suffix, &()); drop(cursor); snapshot.excerpts = new_excerpts; + if changed_trailing_excerpt { snapshot.trailing_excerpt_update_count += 1; } @@ -1307,7 +1316,7 @@ impl MultiBuffer { buffer_state .excerpts .iter() - .map(|excerpt_id| (excerpt_id, buffer_state.buffer.clone(), buffer_edited)), + .map(|locator| (locator, buffer_state.buffer.clone(), buffer_edited)), ); } @@ -1333,14 +1342,14 @@ impl MultiBuffer { snapshot.is_dirty = is_dirty; snapshot.has_conflict = has_conflict; - excerpts_to_edit.sort_unstable_by_key(|(excerpt_id, _, _)| *excerpt_id); + excerpts_to_edit.sort_unstable_by_key(|(locator, _, _)| *locator); let mut edits = Vec::new(); let mut new_excerpts = SumTree::new(); - let mut cursor = snapshot.excerpts.cursor::<(Option<&ExcerptId>, usize)>(); + let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(); - for (id, buffer, buffer_edited) in excerpts_to_edit { - new_excerpts.push_tree(cursor.slice(&Some(id), Bias::Left, &()), &()); + for (locator, buffer, buffer_edited) in excerpts_to_edit { + new_excerpts.push_tree(cursor.slice(&Some(locator), Bias::Left, &()), &()); let old_excerpt = cursor.item().unwrap(); let buffer_id = buffer.id(); let buffer = buffer.read(cx); @@ -1365,8 +1374,8 @@ impl MultiBuffer { ); new_excerpt = Excerpt::new( - id.clone(), - old_excerpt.key, + old_excerpt.id, + locator.clone(), buffer_id, buffer.snapshot(), old_excerpt.range.clone(), @@ -1467,13 +1476,7 @@ impl MultiBuffer { continue; } - let excerpt_ids = self - .buffers - .borrow() - .values() - .flat_map(|b| &b.excerpts) - .cloned() - .collect::>(); + let excerpt_ids = self.excerpt_ids(); if excerpt_ids.is_empty() || (rng.gen() && excerpt_ids.len() < max_excerpts) { let buffer_handle = if rng.gen() || self.buffers.borrow().is_empty() { let text = RandomCharIter::new(&mut *rng).take(10).collect::(); @@ -1511,24 +1514,26 @@ impl MultiBuffer { log::info!( "Inserting excerpts from buffer {} and ranges {:?}: {:?}", buffer_handle.id(), - ranges, + ranges.iter().map(|r| &r.context).collect::>(), ranges .iter() - .map(|range| &buffer_text[range.context.clone()]) + .map(|r| &buffer_text[r.context.clone()]) .collect::>() ); let excerpt_id = self.push_excerpts(buffer_handle.clone(), ranges, cx); - log::info!("Inserted with id: {:?}", excerpt_id); + log::info!("Inserted with ids: {:?}", excerpt_id); } else { let remove_count = rng.gen_range(1..=excerpt_ids.len()); let mut excerpts_to_remove = excerpt_ids .choose_multiple(rng, remove_count) .cloned() .collect::>(); - excerpts_to_remove.sort(); + let snapshot = self.snapshot.borrow(); + excerpts_to_remove.sort_unstable_by(|a, b| a.cmp(b, &*snapshot)); + drop(snapshot); log::info!("Removing excerpts {:?}", excerpts_to_remove); - self.remove_excerpts(&excerpts_to_remove, cx); + self.remove_excerpts(excerpts_to_remove, cx); } } } @@ -1563,6 +1568,38 @@ impl MultiBuffer { } else { self.randomly_edit_excerpts(rng, mutation_count, cx); } + + self.check_invariants(cx); + } + + fn check_invariants(&self, cx: &mut ModelContext) { + let snapshot = self.read(cx); + let excerpts = snapshot.excerpts.items(&()); + let excerpt_ids = snapshot.excerpt_ids.items(&()); + + for (ix, excerpt) in excerpts.iter().enumerate() { + if ix == 0 { + if excerpt.locator <= Locator::min() { + panic!("invalid first excerpt locator {:?}", excerpt.locator); + } + } else { + if excerpt.locator <= excerpts[ix - 1].locator { + panic!("excerpts are out-of-order: {:?}", excerpts); + } + } + } + + for (ix, entry) in excerpt_ids.iter().enumerate() { + if ix == 0 { + if entry.id.cmp(&ExcerptId::min(), &*snapshot).is_le() { + panic!("invalid first excerpt id {:?}", entry.id); + } + } else { + if entry.id <= excerpt_ids[ix - 1].id { + panic!("excerpt ids are out-of-order: {:?}", excerpt_ids); + } + } + } } } @@ -2151,7 +2188,9 @@ impl MultiBufferSnapshot { D: TextDimension + Ord + Sub, { let mut cursor = self.excerpts.cursor::(); - cursor.seek(&Some(&anchor.excerpt_id), Bias::Left, &()); + let locator = self.excerpt_locator_for_id(anchor.excerpt_id); + + cursor.seek(locator, Bias::Left, &()); if cursor.item().is_none() { cursor.next(&()); } @@ -2189,24 +2228,25 @@ impl MultiBufferSnapshot { let mut cursor = self.excerpts.cursor::(); let mut summaries = Vec::new(); while let Some(anchor) = anchors.peek() { - let excerpt_id = &anchor.excerpt_id; + let excerpt_id = anchor.excerpt_id; let excerpt_anchors = iter::from_fn(|| { let anchor = anchors.peek()?; - if anchor.excerpt_id == *excerpt_id { + if anchor.excerpt_id == excerpt_id { Some(&anchors.next().unwrap().text_anchor) } else { None } }); - cursor.seek_forward(&Some(excerpt_id), Bias::Left, &()); + let locator = self.excerpt_locator_for_id(excerpt_id); + cursor.seek_forward(locator, Bias::Left, &()); if cursor.item().is_none() { cursor.next(&()); } let position = D::from_text_summary(&cursor.start().text); if let Some(excerpt) = cursor.item() { - if excerpt.id == *excerpt_id { + if excerpt.id == excerpt_id { let excerpt_buffer_start = excerpt.range.context.start.summary::(&excerpt.buffer); let excerpt_buffer_end = @@ -2240,13 +2280,18 @@ impl MultiBufferSnapshot { I: 'a + IntoIterator, { let mut anchors = anchors.into_iter().enumerate().peekable(); - let mut cursor = self.excerpts.cursor::>(); + let mut cursor = self.excerpts.cursor::>(); + cursor.next(&()); + let mut result = Vec::new(); + while let Some((_, anchor)) = anchors.peek() { - let old_excerpt_id = &anchor.excerpt_id; + let old_excerpt_id = anchor.excerpt_id; // Find the location where this anchor's excerpt should be. - cursor.seek_forward(&Some(old_excerpt_id), Bias::Left, &()); + let old_locator = self.excerpt_locator_for_id(old_excerpt_id); + cursor.seek_forward(&Some(old_locator), Bias::Left, &()); + if cursor.item().is_none() { cursor.next(&()); } @@ -2256,27 +2301,22 @@ impl MultiBufferSnapshot { // Process all of the anchors for this excerpt. while let Some((_, anchor)) = anchors.peek() { - if anchor.excerpt_id != *old_excerpt_id { + if anchor.excerpt_id != old_excerpt_id { break; } - let mut kept_position = false; let (anchor_ix, anchor) = anchors.next().unwrap(); let mut anchor = anchor.clone(); - let id_invalid = - *old_excerpt_id == ExcerptId::max() || *old_excerpt_id == ExcerptId::min(); - let still_exists = next_excerpt.map_or(false, |excerpt| { - excerpt.id == *old_excerpt_id && excerpt.contains(&anchor) - }); - // Leave min and max anchors unchanged if invalid or // if the old excerpt still exists at this location - if id_invalid || still_exists { - kept_position = true; - } + let mut kept_position = next_excerpt + .map_or(false, |e| e.id == old_excerpt_id && e.contains(&anchor)) + || old_excerpt_id == ExcerptId::max() + || old_excerpt_id == ExcerptId::min(); + // If the old excerpt no longer exists at this location, then attempt to // find an equivalent position for this anchor in an adjacent excerpt. - else { + if !kept_position { for excerpt in [next_excerpt, prev_excerpt].iter().filter_map(|e| *e) { if excerpt.contains(&anchor) { anchor.excerpt_id = excerpt.id.clone(); @@ -2285,6 +2325,7 @@ impl MultiBufferSnapshot { } } } + // If there's no adjacent excerpt that contains the anchor's position, // then report that the anchor has lost its position. if !kept_position { @@ -2354,7 +2395,7 @@ impl MultiBufferSnapshot { }; } - let mut cursor = self.excerpts.cursor::<(usize, Option<&ExcerptId>)>(); + let mut cursor = self.excerpts.cursor::<(usize, Option)>(); cursor.seek(&offset, Bias::Right, &()); if cursor.item().is_none() && offset == cursor.start().0 && bias == Bias::Left { cursor.prev(&()); @@ -2382,8 +2423,9 @@ impl MultiBufferSnapshot { } pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor { - let mut cursor = self.excerpts.cursor::>(); - cursor.seek(&Some(&excerpt_id), Bias::Left, &()); + let locator = self.excerpt_locator_for_id(excerpt_id); + let mut cursor = self.excerpts.cursor::>(); + cursor.seek(locator, Bias::Left, &()); if let Some(excerpt) = cursor.item() { if excerpt.id == excerpt_id { let text_anchor = excerpt.clip_anchor(text_anchor); @@ -2401,7 +2443,7 @@ impl MultiBufferSnapshot { pub fn can_resolve(&self, anchor: &Anchor) -> bool { if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() { true - } else if let Some(excerpt) = self.excerpt(&anchor.excerpt_id) { + } else if let Some(excerpt) = self.excerpt(anchor.excerpt_id) { excerpt.buffer.can_resolve(&anchor.text_anchor) } else { false @@ -2456,7 +2498,6 @@ impl MultiBufferSnapshot { let starts_new_buffer = Some(excerpt.buffer_id) != prev_buffer_id; let boundary = ExcerptBoundary { id: excerpt.id.clone(), - key: excerpt.key, row: cursor.start().1.row, buffer: excerpt.buffer.clone(), range: excerpt.range.clone(), @@ -2678,8 +2719,8 @@ impl MultiBufferSnapshot { .flatten() .map(|item| OutlineItem { depth: item.depth, - range: self.anchor_in_excerpt(excerpt_id.clone(), item.range.start) - ..self.anchor_in_excerpt(excerpt_id.clone(), item.range.end), + range: self.anchor_in_excerpt(excerpt_id, item.range.start) + ..self.anchor_in_excerpt(excerpt_id, item.range.end), text: item.text, highlight_ranges: item.highlight_ranges, name_ranges: item.name_ranges, @@ -2688,11 +2729,29 @@ impl MultiBufferSnapshot { )) } - fn excerpt<'a>(&'a self, excerpt_id: &'a ExcerptId) -> Option<&'a Excerpt> { - let mut cursor = self.excerpts.cursor::>(); - cursor.seek(&Some(excerpt_id), Bias::Left, &()); + fn excerpt_locator_for_id<'a>(&'a self, id: ExcerptId) -> &'a Locator { + if id == ExcerptId::min() { + Locator::min_ref() + } else if id == ExcerptId::max() { + Locator::max_ref() + } else { + let mut cursor = self.excerpt_ids.cursor::(); + cursor.seek(&id, Bias::Left, &()); + if let Some(entry) = cursor.item() { + if entry.id == id { + return &entry.locator; + } + } + panic!("invalid excerpt id {:?}", id) + } + } + + fn excerpt<'a>(&'a self, excerpt_id: ExcerptId) -> Option<&'a Excerpt> { + let mut cursor = self.excerpts.cursor::>(); + let locator = self.excerpt_locator_for_id(excerpt_id); + cursor.seek(&Some(locator), Bias::Left, &()); if let Some(excerpt) = cursor.item() { - if excerpt.id == *excerpt_id { + if excerpt.id == excerpt_id { return Some(excerpt); } } @@ -2703,10 +2762,12 @@ impl MultiBufferSnapshot { &'a self, range: &'a Range, ) -> impl 'a + Iterator)> { - let mut cursor = self.excerpts.cursor::>(); - cursor.seek(&Some(&range.start.excerpt_id), Bias::Left, &()); + let mut cursor = self.excerpts.cursor::(); + let start_locator = self.excerpt_locator_for_id(range.start.excerpt_id); + let end_locator = self.excerpt_locator_for_id(range.end.excerpt_id); + cursor.seek(start_locator, Bias::Left, &()); cursor - .take_while(move |excerpt| excerpt.id <= range.end.excerpt_id) + .take_while(move |excerpt| excerpt.locator <= *end_locator) .flat_map(move |excerpt| { let mut query_range = excerpt.range.context.start..excerpt.range.context.end; if excerpt.id == range.start.excerpt_id { @@ -2916,7 +2977,7 @@ impl History { impl Excerpt { fn new( id: ExcerptId, - key: usize, + locator: Locator, buffer_id: usize, buffer: BufferSnapshot, range: ExcerptRange, @@ -2924,7 +2985,7 @@ impl Excerpt { ) -> Self { Excerpt { id, - key, + locator, max_buffer_row: range.context.end.to_point(&buffer).row, text_summary: buffer .text_summary_for_range::(range.context.to_offset(&buffer)), @@ -3010,10 +3071,33 @@ impl Excerpt { } } +impl ExcerptId { + pub fn min() -> Self { + Self(0) + } + + pub fn max() -> Self { + Self(usize::MAX) + } + + pub fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> cmp::Ordering { + let a = snapshot.excerpt_locator_for_id(*self); + let b = snapshot.excerpt_locator_for_id(*other); + a.cmp(&b).then_with(|| self.0.cmp(&other.0)) + } +} + +impl Into for ExcerptId { + fn into(self) -> usize { + self.0 + } +} + impl fmt::Debug for Excerpt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Excerpt") .field("id", &self.id) + .field("locator", &self.locator) .field("buffer_id", &self.buffer_id) .field("range", &self.range) .field("text_summary", &self.text_summary) @@ -3031,19 +3115,44 @@ impl sum_tree::Item for Excerpt { text += TextSummary::from("\n"); } ExcerptSummary { - excerpt_id: self.id.clone(), + excerpt_id: self.id, + excerpt_locator: self.locator.clone(), max_buffer_row: self.max_buffer_row, text, } } } +impl sum_tree::Item for ExcerptIdMapping { + type Summary = ExcerptId; + + fn summary(&self) -> Self::Summary { + self.id + } +} + +impl sum_tree::KeyedItem for ExcerptIdMapping { + type Key = ExcerptId; + + fn key(&self) -> Self::Key { + self.id + } +} + +impl sum_tree::Summary for ExcerptId { + type Context = (); + + fn add_summary(&mut self, other: &Self, _: &()) { + *self = *other; + } +} + impl sum_tree::Summary for ExcerptSummary { type Context = (); fn add_summary(&mut self, summary: &Self, _: &()) { - debug_assert!(summary.excerpt_id > self.excerpt_id); - self.excerpt_id = summary.excerpt_id.clone(); + debug_assert!(summary.excerpt_locator > self.excerpt_locator); + self.excerpt_locator = summary.excerpt_locator.clone(); self.text.add_summary(&summary.text, &()); self.max_buffer_row = cmp::max(self.max_buffer_row, summary.max_buffer_row); } @@ -3067,9 +3176,15 @@ impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize { } } -impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Option<&'a ExcerptId> { +impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, Option<&'a Locator>> for Locator { + fn cmp(&self, cursor_location: &Option<&'a Locator>, _: &()) -> cmp::Ordering { + Ord::cmp(&Some(self), cursor_location) + } +} + +impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Locator { fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering { - Ord::cmp(self, &Some(&cursor_location.excerpt_id)) + Ord::cmp(self, &cursor_location.excerpt_locator) } } @@ -3091,9 +3206,15 @@ impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 { } } -impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a ExcerptId> { +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a Locator> { fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { - *self = Some(&summary.excerpt_id); + *self = Some(&summary.excerpt_locator); + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self = Some(summary.excerpt_id); } } @@ -3591,7 +3712,7 @@ mod tests { let snapshot = multibuffer.update(cx, |multibuffer, cx| { let (buffer_2_excerpt_id, _) = multibuffer.excerpts_for_buffer(&buffer_2, cx)[0].clone(); - multibuffer.remove_excerpts(&[buffer_2_excerpt_id], cx); + multibuffer.remove_excerpts([buffer_2_excerpt_id], cx); multibuffer.snapshot(cx) }); @@ -3780,7 +3901,7 @@ mod tests { // Replace the buffer 1 excerpt with new excerpts from buffer 2. let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| { - multibuffer.remove_excerpts([&excerpt_id_1], cx); + multibuffer.remove_excerpts([excerpt_id_1], cx); let mut ids = multibuffer .push_excerpts( buffer_2.clone(), @@ -3810,9 +3931,8 @@ mod tests { assert_ne!(excerpt_id_2, excerpt_id_1); // Resolve some anchors from the previous snapshot in the new snapshot. - // Although there is still an excerpt with the same id, it is for - // a different buffer, so we don't attempt to resolve the old text - // anchor in the new buffer. + // The current excerpts are from a different buffer, so we don't attempt to + // resolve the old text anchor in the new buffer. assert_eq!( snapshot_2.summary_for_anchor::(&snapshot_1.anchor_before(2)), 0 @@ -3824,6 +3944,9 @@ mod tests { ]), vec![0, 0] ); + + // Refresh anchors from the old snapshot. The return value indicates that both + // anchors lost their original excerpt. let refresh = snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]); assert_eq!( @@ -3837,10 +3960,10 @@ mod tests { // Replace the middle excerpt with a smaller excerpt in buffer 2, // that intersects the old excerpt. let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| { - multibuffer.remove_excerpts([&excerpt_id_3], cx); + multibuffer.remove_excerpts([excerpt_id_3], cx); multibuffer .insert_excerpts_after( - &excerpt_id_3, + excerpt_id_2, buffer_2.clone(), [ExcerptRange { context: 5..8, @@ -3857,8 +3980,8 @@ mod tests { assert_ne!(excerpt_id_5, excerpt_id_3); // Resolve some anchors from the previous snapshot in the new snapshot. - // The anchor in the middle excerpt snaps to the beginning of the - // excerpt, since it is not + // The third anchor can't be resolved, since its excerpt has been removed, + // so it resolves to the same position as its predecessor. let anchors = [ snapshot_2.anchor_before(0), snapshot_2.anchor_after(2), @@ -3867,7 +3990,7 @@ mod tests { ]; assert_eq!( snapshot_3.summaries_for_anchors::(&anchors), - &[0, 2, 5, 13] + &[0, 2, 9, 13] ); let new_anchors = snapshot_3.refresh_anchors(&anchors); @@ -3889,7 +4012,7 @@ mod tests { let mut buffers: Vec> = Vec::new(); let multibuffer = cx.add_model(|_| MultiBuffer::new(0)); - let mut excerpt_ids = Vec::new(); + let mut excerpt_ids = Vec::::new(); let mut expected_excerpts = Vec::<(ModelHandle, Range)>::new(); let mut anchors = Vec::new(); let mut old_versions = Vec::new(); @@ -3919,9 +4042,11 @@ mod tests { .collect::(), ); } - ids_to_remove.sort_unstable(); + let snapshot = multibuffer.read(cx).read(cx); + ids_to_remove.sort_unstable_by(|a, b| a.cmp(&b, &snapshot)); + drop(snapshot); multibuffer.update(cx, |multibuffer, cx| { - multibuffer.remove_excerpts(&ids_to_remove, cx) + multibuffer.remove_excerpts(ids_to_remove, cx) }); } 30..=39 if !expected_excerpts.is_empty() => { @@ -3945,7 +4070,6 @@ mod tests { // Ensure the newly-refreshed anchors point to a valid excerpt and don't // overshoot its boundaries. assert_eq!(anchors.len(), prev_len); - let mut cursor = multibuffer.excerpts.cursor::>(); for anchor in &anchors { if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() @@ -3953,8 +4077,7 @@ mod tests { continue; } - cursor.seek_forward(&Some(&anchor.excerpt_id), Bias::Left, &()); - let excerpt = cursor.item().unwrap(); + let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap(); assert_eq!(excerpt.id, anchor.excerpt_id); assert!(excerpt.contains(anchor)); } @@ -3994,7 +4117,7 @@ mod tests { let excerpt_id = multibuffer.update(cx, |multibuffer, cx| { multibuffer .insert_excerpts_after( - &prev_excerpt_id, + prev_excerpt_id, buffer_handle.clone(), [ExcerptRange { context: start_ix..end_ix, diff --git a/crates/editor/src/multi_buffer/anchor.rs b/crates/editor/src/multi_buffer/anchor.rs index 43723b95fc4e71623c3dc4ed80b299e19bba2211..84b542781619c8ac12d741b39c76c3f425fa40d9 100644 --- a/crates/editor/src/multi_buffer/anchor.rs +++ b/crates/editor/src/multi_buffer/anchor.rs @@ -30,16 +30,16 @@ impl Anchor { } } - pub fn excerpt_id(&self) -> &ExcerptId { - &self.excerpt_id + pub fn excerpt_id(&self) -> ExcerptId { + self.excerpt_id } pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering { - let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id); + let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id, snapshot); if excerpt_id_cmp.is_eq() { if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() { Ordering::Equal - } else if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) { + } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer) } else { Ordering::Equal @@ -51,7 +51,7 @@ impl Anchor { pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor { if self.text_anchor.bias != Bias::Left { - if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) { + if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { return Self { buffer_id: self.buffer_id, excerpt_id: self.excerpt_id.clone(), @@ -64,7 +64,7 @@ impl Anchor { pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor { if self.text_anchor.bias != Bias::Right { - if let Some(excerpt) = snapshot.excerpt(&self.excerpt_id) { + if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { return Self { buffer_id: self.buffer_id, excerpt_id: self.excerpt_id.clone(), diff --git a/crates/text/src/locator.rs b/crates/text/src/locator.rs index 9d38a51df071d1c679f376c3f4c00baf3ab8929a..07b73ace05d80693a081d69f3f0aef410a228319 100644 --- a/crates/text/src/locator.rs +++ b/crates/text/src/locator.rs @@ -3,8 +3,8 @@ use smallvec::{smallvec, SmallVec}; use std::iter; lazy_static! { - pub static ref MIN: Locator = Locator::min(); - pub static ref MAX: Locator = Locator::max(); + static ref MIN: Locator = Locator::min(); + static ref MAX: Locator = Locator::max(); } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -19,6 +19,14 @@ impl Locator { Self(smallvec![u64::MAX]) } + pub fn min_ref() -> &'static Self { + &*MIN + } + + pub fn max_ref() -> &'static Self { + &*MAX + } + pub fn assign(&mut self, other: &Self) { self.0.resize(other.0.len(), 0); self.0.copy_from_slice(&other.0); diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 0a260c08cee80b1dae15e251be66b45a8bd3e872..5c2f7b7a518e63d00568db573960f23e69cb6093 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1770,9 +1770,9 @@ impl BufferSnapshot { fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator { if *anchor == Anchor::MIN { - &locator::MIN + Locator::min_ref() } else if *anchor == Anchor::MAX { - &locator::MAX + Locator::max_ref() } else { let anchor_key = InsertionFragmentKey { timestamp: anchor.timestamp, From 718f802157315260527dc8f21ddda1081673f5a0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 23 Nov 2022 16:56:22 -0800 Subject: [PATCH 59/67] Implement Copy for multibuffer anchors --- crates/editor/src/editor.rs | 35 ++++++++++-------------- crates/editor/src/editor_tests.rs | 6 ++-- crates/editor/src/element.rs | 9 ++---- crates/editor/src/hover_popover.rs | 2 +- crates/editor/src/multi_buffer.rs | 2 +- crates/editor/src/multi_buffer/anchor.rs | 2 +- crates/vim/src/visual.rs | 4 +-- 7 files changed, 26 insertions(+), 34 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 426215eb15aed4ce571bec8f16f22ec63091af17..5bbeed3fb56dd754aa181f867a85956ba71d90b4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1162,7 +1162,7 @@ impl Editor { }); clone.selections.set_state(&self.selections); clone.scroll_position = self.scroll_position; - clone.scroll_top_anchor = self.scroll_top_anchor.clone(); + clone.scroll_top_anchor = self.scroll_top_anchor; clone.searchable = self.searchable; clone } @@ -1305,7 +1305,7 @@ impl Editor { display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)), ongoing_scroll: self.ongoing_scroll, scroll_position: self.scroll_position, - scroll_top_anchor: self.scroll_top_anchor.clone(), + scroll_top_anchor: self.scroll_top_anchor, placeholder_text: self.placeholder_text.clone(), is_focused: self .handle @@ -1791,17 +1791,15 @@ impl Editor { .pending_anchor() .expect("extend_selection not called with pending selection"); if position >= tail { - pending_selection.start = tail_anchor.clone(); + pending_selection.start = tail_anchor; } else { - pending_selection.end = tail_anchor.clone(); + pending_selection.end = tail_anchor; pending_selection.reversed = true; } let mut pending_mode = self.selections.pending_mode().unwrap(); match &mut pending_mode { - SelectMode::Word(range) | SelectMode::Line(range) => { - *range = tail_anchor.clone()..tail_anchor - } + SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor, _ => {} } @@ -2145,10 +2143,9 @@ impl Editor { )); if following_text_allows_autoclose && preceding_text_matches_prefix { let anchor = snapshot.anchor_before(selection.end); - new_selections - .push((selection.map(|_| anchor.clone()), text.len())); + new_selections.push((selection.map(|_| anchor), text.len())); new_autoclose_regions.push(( - anchor.clone(), + anchor, text.len(), selection.id, bracket_pair.clone(), @@ -2169,10 +2166,8 @@ impl Editor { && text.as_ref() == region.pair.end.as_str(); if should_skip { let anchor = snapshot.anchor_after(selection.end); - new_selections.push(( - selection.map(|_| anchor.clone()), - region.pair.end.len(), - )); + new_selections + .push((selection.map(|_| anchor), region.pair.end.len())); continue; } } @@ -2204,7 +2199,7 @@ impl Editor { // text with the given input and move the selection to the end of the // newly inserted text. let anchor = snapshot.anchor_after(selection.end); - new_selections.push((selection.map(|_| anchor.clone()), 0)); + new_selections.push((selection.map(|_| anchor), 0)); edits.push((selection.start..selection.end, text.clone())); } @@ -2306,7 +2301,7 @@ impl Editor { } let anchor = buffer.anchor_after(end); - let new_selection = selection.map(|_| anchor.clone()); + let new_selection = selection.map(|_| anchor); ( (start..end, new_text), (insert_extra_newline, new_selection), @@ -2386,7 +2381,7 @@ impl Editor { .iter() .map(|s| { let anchor = snapshot.anchor_after(s.end); - s.map(|_| anchor.clone()) + s.map(|_| anchor) }) .collect::>() }; @@ -3650,7 +3645,7 @@ impl Editor { String::new(), )); let insertion_anchor = buffer.anchor_after(insertion_point); - edits.push((insertion_anchor.clone()..insertion_anchor, text)); + edits.push((insertion_anchor..insertion_anchor, text)); let row_delta = range_to_move.start.row - insertion_point.row + 1; @@ -3755,7 +3750,7 @@ impl Editor { String::new(), )); let insertion_anchor = buffer.anchor_after(insertion_point); - edits.push((insertion_anchor.clone()..insertion_anchor, text)); + edits.push((insertion_anchor..insertion_anchor, text)); let row_delta = insertion_point.row - range_to_move.end.row + 1; @@ -4625,7 +4620,7 @@ impl Editor { cursor_anchor: position, cursor_position: point, scroll_position: self.scroll_position, - scroll_top_anchor: self.scroll_top_anchor.clone(), + scroll_top_anchor: self.scroll_top_anchor, scroll_top_row, }), cx, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 0ed5023c43e691997c2103f8424d02a6f2fae846..8ac1f9a3fc529426632c8020fe59b53cc8c8f9cc 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -542,7 +542,7 @@ fn test_navigation_history(cx: &mut gpui::MutableAppContext) { // Set scroll position to check later editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx); let original_scroll_position = editor.scroll_position; - let original_scroll_top_anchor = editor.scroll_top_anchor.clone(); + let original_scroll_top_anchor = editor.scroll_top_anchor; // Jump to the end of the document and adjust scroll editor.move_to_end(&MoveToEnd, cx); @@ -556,12 +556,12 @@ fn test_navigation_history(cx: &mut gpui::MutableAppContext) { assert_eq!(editor.scroll_top_anchor, original_scroll_top_anchor); // Ensure we don't panic when navigation data contains invalid anchors *and* points. - let mut invalid_anchor = editor.scroll_top_anchor.clone(); + let mut invalid_anchor = editor.scroll_top_anchor; invalid_anchor.text_anchor.buffer_id = Some(999); let invalid_point = Point::new(9999, 0); editor.navigate( Box::new(NavigationData { - cursor_anchor: invalid_anchor.clone(), + cursor_anchor: invalid_anchor, cursor_position: invalid_point, scroll_top_anchor: invalid_anchor, scroll_top_row: invalid_point.row, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1fd90b7a33cc549ac546024a753696c837d9d6ff..8409786637f1444f36c2814c94e3dc4613d32670 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1607,16 +1607,13 @@ impl Element for EditorElement { highlighted_rows = view.highlighted_rows(); let theme = cx.global::().theme.as_ref(); - highlighted_ranges = view.background_highlights_in_range( - start_anchor.clone()..end_anchor.clone(), - &display_map, - theme, - ); + highlighted_ranges = + view.background_highlights_in_range(start_anchor..end_anchor, &display_map, theme); let mut remote_selections = HashMap::default(); for (replica_id, line_mode, cursor_shape, selection) in display_map .buffer_snapshot - .remote_selections_in_range(&(start_anchor.clone()..end_anchor.clone())) + .remote_selections_in_range(&(start_anchor..end_anchor)) { // The local selections match the leader's selections. if Some(replica_id) == view.leader_replica_id { diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index e4b4da68d35ca33e42a7ec47103c3f2b1d2aa1a1..7369b0a6f40b5a20718d354e3a1ece9fe5c16aa4 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -221,7 +221,7 @@ fn show_hover( start..end } else { - anchor.clone()..anchor.clone() + anchor..anchor }; Some(InfoPopover { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index f8bfd80335965fa92194404ea96327eb8a9ed8c2..d758792e6c29f88e1b17a344c949d2c50f999454 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2305,7 +2305,7 @@ impl MultiBufferSnapshot { break; } let (anchor_ix, anchor) = anchors.next().unwrap(); - let mut anchor = anchor.clone(); + let mut anchor = *anchor; // Leave min and max anchors unchanged if invalid or // if the old excerpt still exists at this location diff --git a/crates/editor/src/multi_buffer/anchor.rs b/crates/editor/src/multi_buffer/anchor.rs index 84b542781619c8ac12d741b39c76c3f425fa40d9..4ecab76c48d7f469b4b844b821dd93c97a44dcb9 100644 --- a/crates/editor/src/multi_buffer/anchor.rs +++ b/crates/editor/src/multi_buffer/anchor.rs @@ -6,7 +6,7 @@ use std::{ }; use sum_tree::Bias; -#[derive(Clone, Eq, PartialEq, Debug, Hash)] +#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)] pub struct Anchor { pub(crate) buffer_id: Option, pub(crate) excerpt_id: ExcerptId, diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index ff454d81a86dc410b6ce997296a0d2d1c93a53e2..95f6c3d8b4caa1f6ad347c7788334aeb0835928a 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -114,12 +114,12 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext Date: Mon, 28 Nov 2022 18:56:27 -0500 Subject: [PATCH 60/67] Remove sign in telemetry event --- crates/client/src/user.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 11b9ef6117f09e34e4c4addf5142fbc31058dd79..4d29669c2f87015ddb15557c183b8e853e3d5534 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -150,7 +150,6 @@ impl UserStore { client.telemetry.set_authenticated_user_info(None, false); } - client.telemetry.report_event("sign in", Default::default()); current_user_tx.send(user).await.ok(); } } From 4436ec48ebab64e08b4360250b2d21d4cb33b04b Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Fri, 25 Nov 2022 15:04:15 -0500 Subject: [PATCH 61/67] Add "added_to_mailing_list" column on signups table --- ...20221125192125_add_added_to_mailing_list_to_signups.sql | 2 ++ crates/collab/src/db.rs | 7 +++++-- crates/collab/src/db_tests.rs | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 crates/collab/migrations/20221125192125_add_added_to_mailing_list_to_signups.sql diff --git a/crates/collab/migrations/20221125192125_add_added_to_mailing_list_to_signups.sql b/crates/collab/migrations/20221125192125_add_added_to_mailing_list_to_signups.sql new file mode 100644 index 0000000000000000000000000000000000000000..b154396df1259aa73b5e1a17c9db27d04510e062 --- /dev/null +++ b/crates/collab/migrations/20221125192125_add_added_to_mailing_list_to_signups.sql @@ -0,0 +1,2 @@ +ALTER TABLE "signups" + ADD "added_to_mailing_list" BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 1609764f6e0d5b88d41c56300c977e024eaa8c83..85ace9a5f2f858da63d4cf1a671f046c64142d39 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -390,10 +390,11 @@ impl Db { platform_unknown, editor_features, programming_languages, - device_id + device_id, + added_to_mailing_list ) VALUES - ($1, $2, FALSE, $3, $4, $5, FALSE, $6, $7, $8) + ($1, $2, FALSE, $3, $4, $5, FALSE, $6, $7, $8, $9) ON CONFLICT (email_address) DO UPDATE SET email_address = excluded.email_address RETURNING id @@ -407,6 +408,7 @@ impl Db { .bind(&signup.editor_features) .bind(&signup.programming_languages) .bind(&signup.device_id) + .bind(&signup.added_to_mailing_list) .execute(&self.pool) .await?; Ok(()) @@ -1270,6 +1272,7 @@ pub struct Signup { pub editor_features: Vec, pub programming_languages: Vec, pub device_id: Option, + pub added_to_mailing_list: bool, } #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)] diff --git a/crates/collab/src/db_tests.rs b/crates/collab/src/db_tests.rs index b3f964b8a7b3767f1f1d2d96bc1acdd56990aa82..6260eadc4a83f259b3dcb06ce650ea50788112cc 100644 --- a/crates/collab/src/db_tests.rs +++ b/crates/collab/src/db_tests.rs @@ -657,6 +657,7 @@ async fn test_signups() { editor_features: vec!["speed".into()], programming_languages: vec!["rust".into(), "c".into()], device_id: Some(format!("device_id_{i}")), + added_to_mailing_list: i != 0, // One user failed to subscribe }) .collect::>(); From 049c0f8ba4d743c2cae09b8595b035c456436642 Mon Sep 17 00:00:00 2001 From: Joseph Lyons Date: Tue, 29 Nov 2022 12:57:51 -0500 Subject: [PATCH 62/67] Order invites by creation time --- crates/collab/src/db.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 1609764f6e0d5b88d41c56300c977e024eaa8c83..6aeb70a6dab01bd41ea7fde051b3fafa3ce35dc0 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -773,6 +773,8 @@ where WHERE NOT email_confirmation_sent AND (platform_mac OR platform_unknown) + ORDER BY + created_at LIMIT $1 ", ) From 5965113fc8ac84b07b2c9cac4b4003efd7e6728a Mon Sep 17 00:00:00 2001 From: Julia Date: Mon, 28 Nov 2022 23:34:38 -0500 Subject: [PATCH 63/67] Add verify macros & use in one location for point conversion --- Cargo.lock | 9 +++++++++ Cargo.toml | 1 + crates/rope/Cargo.toml | 2 +- crates/rope/src/rope.rs | 10 ++++++---- crates/verify/Cargo.toml | 11 +++++++++++ crates/verify/src/verify.rs | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 crates/verify/Cargo.toml create mode 100644 crates/verify/src/verify.rs diff --git a/Cargo.lock b/Cargo.lock index 93631697c1e93de55d1c32ce89360dadd0926f34..550b240b650e48d54b5510ffa431eb6671c97211 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4778,6 +4778,7 @@ dependencies = [ "smallvec", "sum_tree", "util", + "verify", ] [[package]] @@ -6844,6 +6845,14 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "verify" +version = "0.1.0" +dependencies = [ + "backtrace", + "log", +] + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 8e9814c4481c0472033d8818776c5edba946cf6b..1461855e22d9c00269af9848e9d5748fc5981804 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ members = [ "crates/theme_selector", "crates/theme_testbench", "crates/util", + "crates/verify", "crates/vim", "crates/workspace", "crates/zed", diff --git a/crates/rope/Cargo.toml b/crates/rope/Cargo.toml index 0f754c1fb3f30e820ceb76d816c2cf09abb2d2a5..fb7836fab8a31fcafc2f5c75e739d70817833fce 100644 --- a/crates/rope/Cargo.toml +++ b/crates/rope/Cargo.toml @@ -12,7 +12,7 @@ smallvec = { version = "1.6", features = ["union"] } sum_tree = { path = "../sum_tree" } arrayvec = "0.7.1" log = { version = "0.4.16", features = ["kv_unstable_serde"] } - +verify = { path = "../verify" } [dev-dependencies] rand = "0.8.3" diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index d4ee894310a103cd8fd183da3202c756f5b85605..03810be0b986b1efa008982f8f8d5e440bca5956 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -12,6 +12,7 @@ use std::{ str, }; use sum_tree::{Bias, Dimension, SumTree}; +use verify::{verify, verify_not}; pub use offset_utf16::OffsetUtf16; pub use point::Point; @@ -680,10 +681,11 @@ impl Chunk { let mut offset = 0; let mut point = Point::new(0, 0); for ch in self.0.chars() { - if point >= target { - if point > target { - panic!("point {:?} is inside of character {:?}", target, ch); - } + verify_not!(point > target, ("point {:?} is inside of character {:?}", target, ch), else { + point = target; + }); + + if point == target { break; } diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..72d64511f302fab2a2f92e5ad1ecca6153d65714 --- /dev/null +++ b/crates/verify/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "verify" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/verify.rs" + +[dependencies] +backtrace = "0.3" +log = "0.4" diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs new file mode 100644 index 0000000000000000000000000000000000000000..9e1a4a5c897a9d4109ef2dbd9de9dc2859018391 --- /dev/null +++ b/crates/verify/src/verify.rs @@ -0,0 +1,33 @@ +pub use backtrace::Backtrace; + +#[macro_export] +macro_rules! verify { + ( $expression:expr, else $block:expr ) => { + verify!($expression, (""), else $block) + }; + + ( $expression:expr, ( $($fmt_arg:tt)* ), else $block:expr ) => {{ + let verify_str = stringify!($expression); + + if !$expression { + if cfg!(debug_assertions) { + panic!("Claim failed {:?}: {}", verify_str, format_args!($($fmt_arg)*)); + } else { + let backtrace = $crate::Backtrace::new(); + log::error!("Claim failed {:?}\n{:?}", verify_str, backtrace); + $block + } + } + }}; +} + +#[macro_export] +macro_rules! verify_not { + ( $expression:expr, else $block:expr ) => { + verify_not!($expression, (""), else $block) + }; + + ( $expression:expr, ( $($fmt_arg:tt)* ), else $block:expr ) => { + verify!(!$expression, ( $($fmt_arg)* ), else $block) + }; +} From 2b979d3b88f7025407c0ee0a65a9d90a96f02685 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 30 Nov 2022 00:01:40 -0500 Subject: [PATCH 64/67] Don't panic rope point conversions --- crates/rope/src/rope.rs | 47 ++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 03810be0b986b1efa008982f8f8d5e440bca5956..569d48dcbd71926d7c1088000072d0af96a112e4 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -681,8 +681,8 @@ impl Chunk { let mut offset = 0; let mut point = Point::new(0, 0); for ch in self.0.chars() { - verify_not!(point > target, ("point {:?} is inside of character {:?}", target, ch), else { - point = target; + verify_not!(point > target, ("point {target:?} is inside of character {ch:?}"), else { + return offset; }); if point == target { @@ -691,16 +691,19 @@ impl Chunk { if ch == '\n' { point.row += 1; - if point.row > target.row { - panic!( - "point {:?} is beyond the end of a line with length {}", - target, point.column - ); - } point.column = 0; + + verify_not!( + point.row > target.row, + ("point {target:?} is beyond the end of a line with length {}", point.column), + else { + return offset; + } + ); } else { point.column += ch.len_utf8() as u32; } + offset += ch.len_utf8(); } offset @@ -739,26 +742,36 @@ impl Chunk { if ch == '\n' { point.row += 1; point.column = 0; - if point.row > target.row { - if clip { + + if clip { + if point.row > target.row { // Return the offset of the newline return offset; } - panic!( - "point {:?} is beyond the end of a line with length {}", - target, point.column - ); + } else { + verify_not!( + point.row > target.row, + ("point {target:?} is beyond the end of a line with length {}", point.column), + else { + // Return the offset of the newline + return offset; + } + ) } } else { point.column += ch.len_utf16() as u32; } - if point > target { - if clip { + if clip { + if point > target { // Return the offset of the codepoint which we have landed within, bias left return offset; } - panic!("point {:?} is inside of codepoint {:?}", target, ch); + } else { + verify_not!(point > target, ("point {target:?} is inside of codepoint {ch:?}"), else { + // Return the offset of the codepoint which we have landed within, bias left + return offset; + }); } offset += ch.len_utf8(); From 023ecd595b7248c1a7f8b13a2307ed54692e1a5d Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 30 Nov 2022 13:03:15 -0500 Subject: [PATCH 65/67] Change verify macro to debug panic Co-Authored-By: Max Brunsfeld --- Cargo.lock | 10 +------ Cargo.toml | 1 - crates/rope/Cargo.toml | 2 +- crates/rope/src/rope.rs | 56 ++++++++++++++++--------------------- crates/util/Cargo.toml | 1 + crates/util/src/lib.rs | 13 +++++++++ crates/verify/Cargo.toml | 11 -------- crates/verify/src/verify.rs | 33 ---------------------- 8 files changed, 40 insertions(+), 87 deletions(-) delete mode 100644 crates/verify/Cargo.toml delete mode 100644 crates/verify/src/verify.rs diff --git a/Cargo.lock b/Cargo.lock index 550b240b650e48d54b5510ffa431eb6671c97211..24cd7a7748622e2c7a12f49f125ffa0c123a7dfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4778,7 +4778,6 @@ dependencies = [ "smallvec", "sum_tree", "util", - "verify", ] [[package]] @@ -6786,6 +6785,7 @@ name = "util" version = "0.1.0" dependencies = [ "anyhow", + "backtrace", "futures 0.3.24", "git2", "lazy_static", @@ -6845,14 +6845,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" -[[package]] -name = "verify" -version = "0.1.0" -dependencies = [ - "backtrace", - "log", -] - [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 1461855e22d9c00269af9848e9d5748fc5981804..8e9814c4481c0472033d8818776c5edba946cf6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,6 @@ members = [ "crates/theme_selector", "crates/theme_testbench", "crates/util", - "crates/verify", "crates/vim", "crates/workspace", "crates/zed", diff --git a/crates/rope/Cargo.toml b/crates/rope/Cargo.toml index fb7836fab8a31fcafc2f5c75e739d70817833fce..bd1dc690db8b34b78846f96cbb06d36ec70cc65b 100644 --- a/crates/rope/Cargo.toml +++ b/crates/rope/Cargo.toml @@ -12,7 +12,7 @@ smallvec = { version = "1.6", features = ["union"] } sum_tree = { path = "../sum_tree" } arrayvec = "0.7.1" log = { version = "0.4.16", features = ["kv_unstable_serde"] } -verify = { path = "../verify" } +util = { path = "../util" } [dev-dependencies] rand = "0.8.3" diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 569d48dcbd71926d7c1088000072d0af96a112e4..e4f2bf50116d13519039cd551751c72911c5b304 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -12,7 +12,7 @@ use std::{ str, }; use sum_tree::{Bias, Dimension, SumTree}; -use verify::{verify, verify_not}; +use util::debug_panic; pub use offset_utf16::OffsetUtf16; pub use point::Point; @@ -681,9 +681,10 @@ impl Chunk { let mut offset = 0; let mut point = Point::new(0, 0); for ch in self.0.chars() { - verify_not!(point > target, ("point {target:?} is inside of character {ch:?}"), else { + if point > target { + debug_panic!("point {target:?} is inside of character {ch:?}"); return offset; - }); + } if point == target { break; @@ -693,13 +694,13 @@ impl Chunk { point.row += 1; point.column = 0; - verify_not!( - point.row > target.row, - ("point {target:?} is beyond the end of a line with length {}", point.column), - else { - return offset; - } - ); + if point.row > target.row { + debug_panic!( + "point {target:?} is beyond the end of a line with length {}", + point.column + ); + return offset; + } } else { point.column += ch.len_utf8() as u32; } @@ -743,35 +744,26 @@ impl Chunk { point.row += 1; point.column = 0; - if clip { - if point.row > target.row { - // Return the offset of the newline - return offset; + if point.row > target.row { + if !clip { + debug_panic!( + "point {target:?} is beyond the end of a line with length {}", + point.column + ); } - } else { - verify_not!( - point.row > target.row, - ("point {target:?} is beyond the end of a line with length {}", point.column), - else { - // Return the offset of the newline - return offset; - } - ) + // Return the offset of the newline + return offset; } } else { point.column += ch.len_utf16() as u32; } - if clip { - if point > target { - // Return the offset of the codepoint which we have landed within, bias left - return offset; + if point > target { + if !clip { + debug_panic!("point {target:?} is inside of codepoint {ch:?}"); } - } else { - verify_not!(point > target, ("point {target:?} is inside of codepoint {ch:?}"), else { - // Return the offset of the codepoint which we have landed within, bias left - return offset; - }); + // Return the offset of the codepoint which we have landed within, bias left + return offset; } offset += ch.len_utf8(); diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index c083137156fa5c39f3b9d53568f7c319d05c6a3f..fc16eeb53c3751006917e181b2f4ae184d9d2940 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -11,6 +11,7 @@ test-support = ["serde_json", "tempdir", "git2"] [dependencies] anyhow = "1.0.38" +backtrace = "0.3" futures = "0.3" log = { version = "0.4.16", features = ["kv_unstable_serde"] } lazy_static = "1.4.0" diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index e35f2df7d42f6f87f30f9f99f49a20b2c95b62af..22d63a0996f10d72294974036b85498632d0d680 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -1,6 +1,7 @@ #[cfg(any(test, feature = "test-support"))] pub mod test; +pub use backtrace::Backtrace; use futures::Future; use rand::{seq::SliceRandom, Rng}; use std::{ @@ -10,6 +11,18 @@ use std::{ task::{Context, Poll}, }; +#[macro_export] +macro_rules! debug_panic { + ( $($fmt_arg:tt)* ) => { + if cfg!(debug_assertions) { + panic!( $($fmt_arg)* ); + } else { + let backtrace = $crate::Backtrace::new(); + log::error!("{}\n{:?}", format_args!($($fmt_arg)*), backtrace); + } + }; +} + pub fn truncate(s: &str, max_chars: usize) -> &str { match s.char_indices().nth(max_chars) { None => s, diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml deleted file mode 100644 index 72d64511f302fab2a2f92e5ad1ecca6153d65714..0000000000000000000000000000000000000000 --- a/crates/verify/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "verify" -version = "0.1.0" -edition = "2021" - -[lib] -path = "src/verify.rs" - -[dependencies] -backtrace = "0.3" -log = "0.4" diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs deleted file mode 100644 index 9e1a4a5c897a9d4109ef2dbd9de9dc2859018391..0000000000000000000000000000000000000000 --- a/crates/verify/src/verify.rs +++ /dev/null @@ -1,33 +0,0 @@ -pub use backtrace::Backtrace; - -#[macro_export] -macro_rules! verify { - ( $expression:expr, else $block:expr ) => { - verify!($expression, (""), else $block) - }; - - ( $expression:expr, ( $($fmt_arg:tt)* ), else $block:expr ) => {{ - let verify_str = stringify!($expression); - - if !$expression { - if cfg!(debug_assertions) { - panic!("Claim failed {:?}: {}", verify_str, format_args!($($fmt_arg)*)); - } else { - let backtrace = $crate::Backtrace::new(); - log::error!("Claim failed {:?}\n{:?}", verify_str, backtrace); - $block - } - } - }}; -} - -#[macro_export] -macro_rules! verify_not { - ( $expression:expr, else $block:expr ) => { - verify_not!($expression, (""), else $block) - }; - - ( $expression:expr, ( $($fmt_arg:tt)* ), else $block:expr ) => { - verify!(!$expression, ( $($fmt_arg)* ), else $block) - }; -} From 41b2fde10d8285d4e77c246fdcb330d3850d4f23 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 30 Nov 2022 13:11:08 -0500 Subject: [PATCH 66/67] Style Co-Authored-By: Max Brunsfeld --- crates/rope/src/rope.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index e4f2bf50116d13519039cd551751c72911c5b304..53713e3f7a35f7e0d1f3c463575f60f0c5af0efe 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -680,13 +680,12 @@ impl Chunk { fn point_to_offset(&self, target: Point) -> usize { let mut offset = 0; let mut point = Point::new(0, 0); - for ch in self.0.chars() { - if point > target { - debug_panic!("point {target:?} is inside of character {ch:?}"); - return offset; - } - if point == target { + for ch in self.0.chars() { + if point >= target { + if point > target { + debug_panic!("point {target:?} is inside of character {ch:?}"); + } break; } @@ -699,7 +698,7 @@ impl Chunk { "point {target:?} is beyond the end of a line with length {}", point.column ); - return offset; + break; } } else { point.column += ch.len_utf8() as u32; @@ -707,6 +706,7 @@ impl Chunk { offset += ch.len_utf8(); } + offset } From d70996bb9923a06fa1e7334a372fd3d32677fe19 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 30 Nov 2022 14:10:10 -0800 Subject: [PATCH 67/67] collab 0.2.5 --- Cargo.lock | 2 +- crates/collab/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24cd7a7748622e2c7a12f49f125ffa0c123a7dfd..e04624d686cf723619e3ec966d1f64b241c0ff2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1028,7 +1028,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.2.4" +version = "0.2.5" dependencies = [ "anyhow", "async-trait", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 57a57a00c1b83d675af5d6f2142a560ffbec3b2d..09f379526eec23d44f2057e48b2fb7d7b27e2d17 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.2.4" +version = "0.2.5" [[bin]] name = "collab"