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 | 453 +++++++++++++++-------------- crates/settings/src/settings.rs | 9 + crates/workspace/src/pane_group.rs | 40 ++- crates/workspace/src/workspace.rs | 1 + 4 files changed, 273 insertions(+), 230 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 3eb82e94c7..0487b11c19 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" - }, - "Rust": { - "tab_size": 4 - }, - "JavaScript": { - "tab_size": 2 - }, - "TypeScript": { - "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" - // } + // 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/" + // } + // } + // + // + "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" + }, + "Rust": { + "tab_size": 4 + }, + "JavaScript": { + "tab_size": 2 + }, + "TypeScript": { + "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" + // } + // } + // } + } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index d8f8e8926a..a8a98f649a 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 6c379ffd2a..c6a2ef49a3 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 e7752219c5..289ba5e05e 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 f79a7b851d..29c444bfee 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 5abc89321c..4436ab416e 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 5dd9c483af..3eb15c9c5e 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 cca40cbe5a..3bfe10eedc 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 c1e17a8e1a..76bb4394dd 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 0000000000..280219a119 --- /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 0000000000..7a69a818ef --- /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 29c444bfee..e43c8473ea 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 205017da1f..a46a56de58 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 419c7a79a5..6c074a2d75 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 4436ab416e..5e9319b128 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 3eb15c9c5e..be735df9c0 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 e43c8473ea..bc127b24c4 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 a46a56de58..8ac180fcc1 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 be735df9c0..7cfbd9de2f 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!()) - } + test_edit_sequence( + "Rust", + &[ + " + fn a() { + b!(c!()) + } + ", + " + fn a() { + «d»!(c!()) + } + ", + " + fn a() { + «e»d!(c!()) + } + ", + " + fn a() { + ed!«[»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(&[ + 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() { - two!( - three.four, - ); - } + <«body»> + <% if «@one» ; end %> + + ", - " - fn one() { - t«en - .eleven( - twelve, - » - three.four, - ); - } - ", - ]); + ); } #[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 7cfbd9de2f..11811b999f 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 11811b999f..0a8919d6a3 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 0a8919d6a3..711a65c3e9 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 0000000000..91b21d081f --- /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 47e06fc545..a80a6b5298 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 2bf2701f23..d3bf1f28e5 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 9fd9bef825..3a92c3ef14 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 c9358ddc2a..8306978c9c 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 db8fb8e3ff..055aa06706 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 3c28f6b512..0675610dcb 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 0675610dcb..0e2723201a 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 711a65c3e9..026e4857c5 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 65f5985edf..6884de7e20 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 3eb5d68516..b6787c930c 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 763917a464..7bd5dda522 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 0e2723201a..1563cb9de4 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 7a71bfe0e5..b110252250 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 1563cb9de4..e1a0b9c00a 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 bc127b24c4..17ac0a2283 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 8ac180fcc1..ac8bf018f9 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 026e4857c5..d843f5e85b 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 65866baf7f..3866ee6c7b 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 d73523c8bd..bda45053b1 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 e849632a2d..c6862e66e4 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 473bbd4f52..014eeecc0c 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 13dcf4fef1..7072a3fe94 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 cabea7d013..324ccdc0c6 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,62 +91,10 @@ async fn main() { }, ) .await - .expect("failed to insert user") - .user_id, - ); + .expect("failed to insert user"); + } } } - - 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"); - } - 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"); - } } async fn fetch_github( 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 17ac0a2283..158791ac97 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 ac8bf018f9..8e9814c448 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 d843f5e85b..65d01e9493 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 199f42df65..ddcd6c5898 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 761dad6675..5d52a664de 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 76bb4394dd..4c33e7329f 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 0000000000..6aad293d34 --- /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 10da609d57..281c176360 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 558a687703..d6cb733ce6 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 7456cb5598..33a6ffa6e3 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 2fd8ce27b8..4ab54ad8e6 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 8c900d9f4a..9622049a9c 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 cb1f186bde..a426e2e0d4 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 9cb063c1e5..5fb263501f 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 44f2a8cb16..daefebdbdd 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 54e7eaf463..2db4ef2d3d 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 5fcdc5fcfd..eb750bed55 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 281c176360..1609764f6e 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 8eda7d34e2..c4e95f10ce 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 7c89eacde8..774d8fbc9e 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 25100037d7..f62f27bb0d 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 129d36dadd..f6a1a5d8e6 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 f1b747d647..53a0b70b35 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 0eab7f0bc9..79836b70e8 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 a309f5123f..862807a74d 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 77d1e67cae..36dab73149 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 2254eea5af..4dd97aa4c6 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 5d52a664de..8b5b20e81e 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 d6cb733ce6..7330f6d009 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 bd2e1c0fca..78349fa797 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 c4e95f10ce..b3f964b8a7 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 65866baf7f..9066dbdd81 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 8b5b20e81e..7e469e19fe 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 7330f6d009..709be66ade 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 33a6ffa6e3..57a57a00c1 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 | 121 ++++++++++++++++++-------- 3 files changed, 112 insertions(+), 61 deletions(-) diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index e43e428fe6..c988bb330b 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 d7370ac75f..00d1ddbf8b 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 4b5217cc2d..0fdc76ebbf 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(); + let mut set: HashMap> = HashMap::default(); - set.insert((MouseEvent::move_disc(), None), Rc::new(|_, _| {})); - set.insert((MouseEvent::hover_disc(), None), Rc::new(|_, _| {})); + 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( - (MouseEvent::up_out_disc(), Some(button)), - Rc::new(|_, _| {}), + 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( + 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 | 157 +++++++++++++++-------------- crates/zed/src/languages/python.rs | 19 ++++ 4 files changed, 116 insertions(+), 81 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 5e9319b128..47d724866d 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 f93d99f76b..674ce4f50e 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 e1a0b9c00a..a7124a1969 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; - } - - 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(), - ) - } - // 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"); + 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; } - }; - 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(), + 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(), ) - }), - lsp_completion, - } + } + // 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; + } + }; + + 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 e6e55eeac4..ba6ccf7bf0 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 c988bb330b..d15051ef12 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 7800cc4408..ce95536950 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 a7124a1969..c4e920db84 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 8c357801e3..b6a4930f0b 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 72ae018a16..272e425651 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 b52327cac0..fe2e4c9c21 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 37f6e76340..78c6b50003 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 c4e920db84..2036ef3cd8 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 272e425651..42ffe5c6ed 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 dd5934f979..7206dbb199 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 38475daf28..70645bbfe0 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 256405f20e..01e38d269e 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 273230fe26..60623e99ca 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 b6a4930f0b..83e9c96ca9 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 42ffe5c6ed..aec46b03cf 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 83e9c96ca9..ca1f35bb13 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 7206dbb199..4575e9ce5e 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 70645bbfe0..aa25b47680 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 01e38d269e..14026d9f4e 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 2036ef3cd8..e1339ba434 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 60623e99ca..eb755d2d2f 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 ca1f35bb13..9b52815ae3 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 aec46b03cf..8cdf087b53 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 dd5934f979..426215eb15 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 fe2e4c9c21..a4d6dc12c7 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 47d724866d..c3f2c3716b 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 78c6b50003..ae6d18a9a9 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 e1339ba434..e1e839db1f 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 055aa06706..795143c3e0 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 9b52815ae3..27f0b8cdb4 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 8cdf087b53..e4469ca141 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 6043127dd5..68fe8a2948 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 ae6d18a9a9..3ea1261735 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 e1e839db1f..cb8e01d562 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 795143c3e0..3bab90d5e3 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 27f0b8cdb4..af74b08743 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 b4fb6a503c..bf237b9ad9 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 4575e9ce5e..dd5934f979 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 aa25b47680..969a970299 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 14026d9f4e..facc1b0491 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 674ce4f50e..ca86b93bfd 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 cb8e01d562..f4752270de 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 ca274b18b8..dfb699fdbb 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 eb755d2d2f..273230fe26 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 ded708370d..b6516d235d 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 b6aef64677..5ca5711d9c 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 e4469ca141..aa4ef109cd 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 709be66ade..93631697c1 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 78349fa797..a3023918e3 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 68fe8a2948..82641dbaa4 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 a4d6dc12c7..cde5a6fb2b 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 f4752270de..432d13076f 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 432d13076f..08714d6cd3 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 b6516d235d..6bfe7124c9 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 969a970299..e3f12c1842 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 ca86b93bfd..674ce4f50e 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 5ca5711d9c..b6aef64677 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 af74b08743..2a1268eab9 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 aa4ef109cd..7e486d231e 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 7e486d231e..0a260c08ce 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 2a1268eab9..d4ee894310 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 0000000000..937cbca053 --- /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 | 35 ++++++++-------- crates/language/src/buffer_tests.rs | 62 ++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 18 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 2a0b158a7a..e8bc2bf314 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; - - 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)); + 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; + } + } + + result = Some((open, close)); } result diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 6043127dd5..116bf175f1 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 bf237b9ad9..6ff7490181 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 ee07c77d01..797f41c52a 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 7bd5dda522..0ed5023c43 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 f62f27bb0d..1fd90b7a33 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 e3f12c1842..f8bfd80335 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 43723b95fc..84b5427816 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 9d38a51df0..07b73ace05 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 0a260c08ce..5c2f7b7a51 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 426215eb15..5bbeed3fb5 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 0ed5023c43..8ac1f9a3fc 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 1fd90b7a33..8409786637 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 e4b4da68d3..7369b0a6f4 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 f8bfd80335..d758792e6c 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 84b5427816..4ecab76c48 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 ff454d81a8..95f6c3d8b4 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 11b9ef6117..4d29669c2f 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 0000000000..b154396df1 --- /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 1609764f6e..85ace9a5f2 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 b3f964b8a7..6260eadc4a 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 1609764f6e..6aeb70a6da 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 93631697c1..550b240b65 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 8e9814c448..1461855e22 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 0f754c1fb3..fb7836fab8 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 d4ee894310..03810be0b9 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 0000000000..72d64511f3 --- /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 0000000000..9e1a4a5c89 --- /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 03810be0b9..569d48dcbd 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 550b240b65..24cd7a7748 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 1461855e22..8e9814c448 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 fb7836fab8..bd1dc690db 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 569d48dcbd..e4f2bf5011 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 c083137156..fc16eeb53c 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 e35f2df7d4..22d63a0996 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 72d64511f3..0000000000 --- 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 9e1a4a5c89..0000000000 --- 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 e4f2bf5011..53713e3f7a 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 24cd7a7748..e04624d686 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 57a57a00c1..09f379526e 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"