Compare commits

...

50 Commits

Author SHA1 Message Date
Max Brunsfeld
d70996bb99 collab 0.2.5 2022-11-30 14:10:10 -08:00
Julia
5a0c39cbed Merge pull request #1922 from zed-industries/dont-panic-clip-instead
Dont panic in point conversion, clip instead
2022-11-30 13:28:10 -05:00
Julia
41b2fde10d Style
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2022-11-30 13:11:08 -05:00
Julia
023ecd595b Change verify macro to debug panic
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2022-11-30 13:03:15 -05:00
Julia
2b979d3b88 Don't panic rope point conversions 2022-11-30 12:43:43 -05:00
Julia
5965113fc8 Add verify macros & use in one location for point conversion 2022-11-30 12:43:43 -05:00
Joseph T. Lyons
3a1cd6ed3a Merge pull request #1913 from zed-industries/Add-column-to-signups-for-added-to-mailing-list
Add "added_to_mailing_list" column on signups table
2022-11-29 19:30:11 -05:00
Joseph T. Lyons
9f9398476d Merge pull request #1920 from zed-industries/order-invites-by-creation-time
Order invites by creation time
2022-11-29 14:28:53 -05:00
Joseph Lyons
049c0f8ba4 Order invites by creation time 2022-11-29 12:57:51 -05:00
Joseph Lyons
4436ec48eb Add "added_to_mailing_list" column on signups table 2022-11-29 02:13:13 -05:00
Joseph T. Lyons
5a9a0f9fa5 Merge pull request #1918 from zed-industries/remove-sign-in-telemetry-event
Remove sign in telemetry event
2022-11-29 01:59:33 -05:00
Joseph Lyons
d2cd9c94f7 Remove sign in telemetry event 2022-11-28 18:56:27 -05:00
Max Brunsfeld
3adc0b947f Merge pull request #1917 from zed-industries/integer-excerpt-ids
Use integers for excerpt ids, map them to locators internally
2022-11-28 14:27:35 -08:00
Max Brunsfeld
718f802157 Implement Copy for multibuffer anchors 2022-11-28 14:18:49 -08:00
Max Brunsfeld
f71145bb32 Add a layer of indirection between excerpt ids and locators 2022-11-28 14:18:49 -08:00
Max Brunsfeld
0b0fe91545 Merge pull request #1912 from zed-industries/matching-brackets-must-contain-range
Fix enclosing-bracket bug that appeared in JS for loops
2022-11-23 13:44:48 -08:00
Max Brunsfeld
aeea47323a 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.
2022-11-23 13:37:22 -08:00
Julia
e4185f38cf Merge pull request #1910 from zed-industries/lsp-coordinate-clamp
Don't trust LSP coordinates to be within document bounds
2022-11-23 14:07:37 -05:00
Julia
09e6d44873 Move Unclipped into separate file 2022-11-23 14:02:11 -05:00
Julia
525d84e5bf Remove spurious lifetimes
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2022-11-23 13:52:39 -05:00
Julia
55ca085d7d Consistency in prefix/suffix/signature of UTF16 point to point conversion
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2022-11-23 13:52:18 -05:00
Julia
03cfd23ac5 Bump protocol version back down as proto changes are non-breaking 2022-11-23 13:40:49 -05:00
Julia
a666ca3e40 Collapse proto Point into the one kind of use case, utf-16 coords
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2022-11-23 13:28:44 -05:00
Julia
b58ae8bdd7 Clip diagnostic range before and during empty range expansion
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2022-11-23 13:21:05 -05:00
Max Brunsfeld
5e7652698d v0.67.x dev 2022-11-23 09:56:06 -08:00
Julia
e51cbf67ab Fixup compile errors 2022-11-22 02:49:47 -05:00
Julia
8c75df30cb Wrap a bunch of traits for Unclipped<T> 2022-11-21 15:58:44 -05:00
Julia
1c84e77c37 Start adding concept of Unclipped text coordinates
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2022-11-21 15:48:25 -05:00
Max Brunsfeld
b3a92979a3 Merge pull request #1911 from zed-industries/single-file-worktree-event-extension
Fix file extension retrieval for single-file worktrees
2022-11-21 12:41:35 -08:00
Max Brunsfeld
55d3c09b6b 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.
2022-11-21 12:34:36 -08:00
Julia
436c89650a Rename clamped -> clipped 2022-11-21 15:23:00 -05:00
Julia
4ead1ecbbf Simply logic of this method
Co-Authored-By: Max Brunsfeld <max@zed.dev>
2022-11-21 14:25:01 -05:00
Julia
074e3cfbd6 Clamp UTF-16 to point conversions 2022-11-21 14:25:01 -05:00
Julia
bb32599ded Clamp for all UTF-16 to offset conversions which used to use ToOffset 2022-11-21 14:25:01 -05:00
Julia
f9cbed5a1f Clamp UTF-16 coordinate while performing LSP edits rather than panicing 2022-11-21 11:48:13 -05:00
Kay Simmons
0078bea877 change bump-version to install jq if its not already installed 2022-11-18 13:42:46 -08:00
Kay Simmons
bb80cee19e Merge pull request #1814 from zed-industries/golden-ratio
Active Pane Magnification
2022-11-18 13:14:03 -08:00
Kay Simmons
0c50c0959d Merge pull request #1906 from zed-industries/mouse-down-capture-on-click-fix
Fix mouse down falling through popovers
2022-11-18 13:10:50 -08:00
Kay Simmons
75b8a12ab3 address issue where mouse down events weren't getting captured after the multiple handlers change 2022-11-18 13:04:27 -08:00
Max Brunsfeld
d090d230e2 Merge pull request #1903 from zed-industries/override-pyright-completion-sorting
Add LspAdapter hook for processing completions, fix completion sorting from Pyright
2022-11-17 15:30:07 -08:00
Max Brunsfeld
bca635e5d3 Add LspAdapter hook for processing completions, fix completion sorting from Pyright 2022-11-17 15:26:46 -08:00
Julia
3938adf60a Merge pull request #1902 from zed-industries/event-handlers-are-multitude
Allow having multiple mouse event handlers of the same kind
2022-11-17 17:19:32 -05:00
Julia
6537def97e Allow having multiple mouse event handlers of the same kind
Co-Authored-By: Kay Simmons <kay@zed.dev>
2022-11-17 17:01:34 -05:00
Max Brunsfeld
5020c70a04 collab 0.2.4 2022-11-17 11:44:29 -08:00
Mikayla Maki
0a63d2e3e1 Merge pull request #1900 from zed-industries/fix-terminal-performance
Check for wakeups correctly
2022-11-17 11:17:13 -08:00
Mikayla Maki
ce0dfde8ee Check for wakeups correctly 2022-11-17 11:14:31 -08:00
Joseph Lyons
93824dd239 Fix top-level header in discord webhook action 2022-11-16 20:02:15 -05:00
Joseph Lyons
c613c98e37 Move comment to correct location 2022-11-16 17:28:50 -05:00
Max Brunsfeld
4e4299d500 v0.66.x dev 2022-11-16 14:22:18 -08:00
K Simmons
e2ba8d6df7 Add active pane magnification setting which grows the active pane making it easier to see it's contents 2022-10-25 17:24:19 -07:00
45 changed files with 1236 additions and 787 deletions

View File

@@ -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 }}
```

5
Cargo.lock generated
View File

@@ -1028,7 +1028,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.3"
version = "0.2.5"
dependencies = [
"anyhow",
"async-trait",
@@ -6785,6 +6785,7 @@ name = "util"
version = "0.1.0"
dependencies = [
"anyhow",
"backtrace",
"futures 0.3.24",
"git2",
"lazy_static",
@@ -7673,7 +7674,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.65.0"
version = "0.67.0"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -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"
// }
// }
// }
}
}

View File

@@ -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();
}
}

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
version = "0.2.3"
version = "0.2.5"
[[bin]]
name = "collab"

View File

@@ -0,0 +1,2 @@
ALTER TABLE "signups"
ADD "added_to_mailing_list" BOOLEAN NOT NULL DEFAULT FALSE;

View File

@@ -390,10 +390,11 @@ impl Db<sqlx::Postgres> {
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<sqlx::Postgres> {
.bind(&signup.editor_features)
.bind(&signup.programming_languages)
.bind(&signup.device_id)
.bind(&signup.added_to_mailing_list)
.execute(&self.pool)
.await?;
Ok(())
@@ -773,6 +775,8 @@ where
WHERE
NOT email_confirmation_sent AND
(platform_mac OR platform_unknown)
ORDER BY
created_at
LIMIT $1
",
)
@@ -1270,6 +1274,7 @@ pub struct Signup {
pub editor_features: Vec<String>,
pub programming_languages: Vec<String>,
pub device_id: Option<String>,
pub added_to_mailing_list: bool,
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromRow)]

View File

@@ -646,7 +646,6 @@ async fn test_signups() {
let usernames = (0..8).map(|i| format!("person-{i}")).collect::<Vec<_>>();
// people sign up on the waitlist
let all_signups = usernames
.iter()
.enumerate()
@@ -658,11 +657,13 @@ 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::<Vec<Signup>>();
// 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();
}

View File

@@ -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,
};
@@ -738,7 +743,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 +793,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<char>`, which does not implement the `Copy` trait"
@@ -801,7 +806,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<char>`, which does not implement the `Copy` trait"
@@ -814,7 +819,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 +830,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 +841,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 +852,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 +944,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 +1045,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 +1058,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,

View File

@@ -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<Block>),
ExcerptHeader {
key: usize,
id: ExcerptId,
buffer: BufferSnapshot,
range: ExcerptRange<text::Anchor>,
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 {

View File

@@ -73,6 +73,7 @@ use std::{
mem,
num::NonZeroU32,
ops::{Deref, DerefMut, Range, RangeInclusive},
path::Path,
sync::Arc,
time::{Duration, Instant},
};
@@ -1161,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
}
@@ -1304,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
@@ -1790,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,
_ => {}
}
@@ -2144,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(),
@@ -2168,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;
}
}
@@ -2203,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()));
}
@@ -2305,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),
@@ -2385,7 +2381,7 @@ impl Editor {
.iter()
.map(|s| {
let anchor = snapshot.anchor_after(s.end);
s.map(|_| anchor.clone())
s.map(|_| anchor)
})
.collect::<Vec<_>>()
};
@@ -3649,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;
@@ -3754,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;
@@ -4624,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,
@@ -6536,15 +6532,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 }));
}
}
}

View File

@@ -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,
@@ -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)]

View File

@@ -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::<JumpIcon>::new(*key, cx, |state, _| {
MouseEventHandler::<JumpIcon>::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::<JumpIcon, _>(
*key,
id.into(),
"Jump to Buffer".to_string(),
Some(Box::new(crate::OpenExcerpts)),
tooltip_style.clone(),
@@ -1606,16 +1607,13 @@ impl Element for EditorElement {
highlighted_rows = view.highlighted_rows();
let theme = cx.global::<Settings>().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 {

View File

@@ -221,7 +221,7 @@ fn show_hover(
start..end
} else {
anchor.clone()..anchor.clone()
anchor..anchor
};
Some(InfoPopover {

View File

@@ -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::{
@@ -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<MultiBufferSnapshot>,
buffers: RefCell<HashMap<usize, BufferState>>,
used_excerpt_ids: SumTree<ExcerptId>,
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<ExcerptId>,
excerpts: Vec<Locator>,
_subscriptions: [gpui::Subscription; 2],
}
@@ -100,6 +100,7 @@ struct BufferState {
pub struct MultiBufferSnapshot {
singleton: bool,
excerpts: SumTree<Excerpt>,
excerpt_ids: SumTree<ExcerptIdMapping>,
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<text::Anchor>,
@@ -121,7 +121,7 @@ pub struct ExcerptBoundary {
#[derive(Clone)]
struct Excerpt {
id: ExcerptId,
key: usize,
locator: Locator,
buffer_id: usize,
buffer: BufferSnapshot,
range: ExcerptRange<text::Anchor>,
@@ -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<T> {
pub context: Range<T>,
@@ -139,6 +145,7 @@ pub struct ExcerptRange<T> {
#[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<usize, Vec<Selection<text::Anchor>>> =
Default::default();
let snapshot = self.read(cx);
let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>();
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
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<O>(
@@ -818,7 +826,7 @@ impl MultiBuffer {
pub fn insert_excerpts_after<O>(
&mut self,
prev_excerpt_id: &ExcerptId,
prev_excerpt_id: ExcerptId,
buffer: ModelHandle<Buffer>,
ranges: impl IntoIterator<Item = ExcerptRange<O>>,
cx: &mut ModelContext<Self>,
@@ -854,8 +862,12 @@ impl MultiBuffer {
});
let mut snapshot = self.snapshot.borrow_mut();
let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>();
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::<Option<&Locator>>();
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::<Locator>();
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::<Option<&ExcerptId>>();
for excerpt_id in buffers
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
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<ExcerptId> {
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<Item = &'a ExcerptId>,
excerpt_ids: impl IntoIterator<Item = ExcerptId>,
cx: &mut ModelContext<Self>,
) {
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::<Vec<_>>();
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::<String>();
@@ -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::<Vec<_>>(),
ranges
.iter()
.map(|range| &buffer_text[range.context.clone()])
.map(|r| &buffer_text[r.context.clone()])
.collect::<Vec<_>>()
);
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::<Vec<_>>();
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<Self>) {
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);
}
}
}
}
}
@@ -1749,20 +1786,20 @@ impl MultiBufferSnapshot {
*cursor.start() + overshoot
}
pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
pub fn clip_point_utf16(&self, point: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 {
if let Some((_, _, buffer)) = self.as_singleton() {
return buffer.clip_point_utf16(point, bias);
}
let mut cursor = self.excerpts.cursor::<PointUtf16>();
cursor.seek(&point, Bias::Right, &());
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()
@@ -2151,7 +2188,9 @@ impl MultiBufferSnapshot {
D: TextDimension + Ord + Sub<D, Output = D>,
{
let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
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::<ExcerptSummary>();
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::<D>(&excerpt.buffer);
let excerpt_buffer_end =
@@ -2240,13 +2280,18 @@ impl MultiBufferSnapshot {
I: 'a + IntoIterator<Item = &'a Anchor>,
{
let mut anchors = anchors.into_iter().enumerate().peekable();
let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>();
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
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)
});
let mut anchor = *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<ExcerptId>)>();
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::<Option<&ExcerptId>>();
cursor.seek(&Some(&excerpt_id), Bias::Left, &());
let locator = self.excerpt_locator_for_id(excerpt_id);
let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
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::<Option<&ExcerptId>>();
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::<ExcerptId>();
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::<Option<&Locator>>();
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<Anchor>,
) -> impl 'a + Iterator<Item = (ReplicaId, bool, CursorShape, Selection<Anchor>)> {
let mut cursor = self.excerpts.cursor::<Option<&ExcerptId>>();
cursor.seek(&Some(&range.start.excerpt_id), Bias::Left, &());
let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
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<text::Anchor>,
@@ -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::<TextSummary, _>(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<usize> 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<ExcerptId> {
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
*self = Some(summary.excerpt_id);
}
}
@@ -3274,12 +3395,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 +3408,12 @@ impl ToOffset for OffsetUtf16 {
}
}
impl ToOffset for PointUtf16 {
fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
snapshot.point_utf16_to_offset(*self)
}
}
impl ToOffsetUtf16 for OffsetUtf16 {
fn to_offset_utf16(&self, _snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
*self
@@ -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::<usize>(&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::<usize, _>(&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<ModelHandle<Buffer>> = Vec::new();
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
let mut excerpt_ids = Vec::new();
let mut excerpt_ids = Vec::<ExcerptId>::new();
let mut expected_excerpts = Vec::<(ModelHandle<Buffer>, Range<text::Anchor>)>::new();
let mut anchors = Vec::new();
let mut old_versions = Vec::new();
@@ -3919,9 +4042,11 @@ mod tests {
.collect::<String>(),
);
}
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::<Option<&ExcerptId>>();
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,
@@ -4158,12 +4281,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()

View File

@@ -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<usize>,
pub(crate) excerpt_id: ExcerptId,
@@ -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(),

View File

@@ -544,11 +544,21 @@ 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);
}
fn select_offset_ranges<I>(&mut self, ranges: I)
where
I: IntoIterator<Item = Range<usize>>,
{
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

View File

@@ -475,27 +475,35 @@ 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 = event_cx.handled;
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() {
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() {
break;
}
}

View File

@@ -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<MouseEvent>, Option<MouseButton>) {
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),
}
}
}

View File

@@ -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<dyn Fn(MouseEvent, &mut EventContext)>;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct HandlerKey {
event_kind: Discriminant<MouseEvent>,
button: Option<MouseButton>,
}
impl HandlerKey {
pub fn new(event_kind: Discriminant<MouseEvent>, button: Option<MouseButton>) -> HandlerKey {
HandlerKey { event_kind, button }
}
}
#[derive(Clone, Default)]
pub struct HandlerSet {
#[allow(clippy::type_complexity)]
pub set: HashMap<
(Discriminant<MouseEvent>, Option<MouseButton>),
Rc<dyn Fn(MouseEvent, &mut EventContext)>,
>,
set: HashMap<HandlerKey, SmallVec<[HandlerCallback; 1]>>,
}
impl HandlerSet {
pub fn capture_all() -> Self {
#[allow(clippy::type_complexity)]
let mut set: HashMap<
(Discriminant<MouseEvent>, Option<MouseButton>),
Rc<dyn Fn(MouseEvent, &mut EventContext)>,
> = Default::default();
let mut set: HashMap<HandlerKey, SmallVec<[HandlerCallback; 1]>> = 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<MouseEvent>, Option<MouseButton>),
) -> Option<Rc<dyn Fn(MouseEvent, &mut EventContext)>> {
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<MouseEvent>,
discriminant: Discriminant<MouseEvent>,
button: Option<MouseButton>,
) -> bool {
self.set.contains_key(&(event, button))
self.set
.contains_key(&HandlerKey::new(discriminant, button))
}
fn insert(
&mut self,
event_kind: Discriminant<MouseEvent>,
button: Option<MouseButton>,
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);

View File

@@ -2225,11 +2225,12 @@ impl BufferSnapshot {
range: Range<T>,
) -> Option<(Range<usize>, Range<usize>)> {
// 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

View File

@@ -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]
@@ -1337,6 +1395,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 = range.start..range.end;
DiagnosticEntry {
range,
diagnostic: Diagnostic {

View File

@@ -71,7 +71,7 @@ impl DiagnosticSet {
diagnostics: SumTree::from_iter(
entries.into_iter().map(|entry| DiagnosticEntry {
range: buffer.anchor_before(entry.range.start)
..buffer.anchor_after(entry.range.end),
..buffer.anchor_before(entry.range.end),
diagnostic: entry.diagnostic,
}),
buffer,

View File

@@ -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<Self>, 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<Self>,
completion: &lsp::CompletionItem,
@@ -1041,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<PointUtf16> {
Unclipped(PointUtf16::new(point.line, point.character))
}
pub fn range_to_lsp(range: Range<PointUtf16>) -> lsp::Range {
@@ -1052,7 +1064,7 @@ pub fn range_to_lsp(range: Range<PointUtf16>) -> lsp::Range {
}
}
pub fn range_from_lsp(range: lsp::Range) -> Range<PointUtf16> {
pub fn range_from_lsp(range: lsp::Range) -> Range<Unclipped<PointUtf16>> {
let mut start = point_from_lsp(range.start);
let mut end = point_from_lsp(range.end);
if start > end {

View File

@@ -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,

View File

@@ -128,8 +128,8 @@ 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.anchor_after(start)..buffer.anchor_before(end)));
}

View File

@@ -26,6 +26,7 @@ use language::{
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Event as BufferEvent,
File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, OffsetRangeExt,
Operation, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction,
Unclipped,
};
use lsp::{
DiagnosticSeverity, DiagnosticTag, DocumentHighlightKind, LanguageServer, LanguageString,
@@ -252,7 +253,7 @@ pub struct Symbol {
pub label: CodeLabel,
pub name: String,
pub kind: lsp::SymbolKind,
pub range: Range<PointUtf16>,
pub range: Range<Unclipped<PointUtf16>>,
pub signature: [u8; 32],
}
@@ -2597,7 +2598,7 @@ impl Project {
language_server_id: usize,
abs_path: PathBuf,
version: Option<i32>,
diagnostics: Vec<DiagnosticEntry<PointUtf16>>,
diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
cx: &mut ModelContext<Project>,
) -> Result<(), anyhow::Error> {
let (worktree, relative_path) = self
@@ -2635,7 +2636,7 @@ impl Project {
fn update_buffer_diagnostics(
&mut self,
buffer: &ModelHandle<Buffer>,
mut diagnostics: Vec<DiagnosticEntry<PointUtf16>>,
mut diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
version: Option<i32>,
cx: &mut ModelContext<Self>,
) -> Result<()> {
@@ -2659,7 +2660,7 @@ impl Project {
let mut sanitized_diagnostics = Vec::new();
let edits_since_save = Patch::new(
snapshot
.edits_since::<PointUtf16>(buffer.read(cx).saved_version())
.edits_since::<Unclipped<PointUtf16>>(buffer.read(cx).saved_version())
.collect(),
);
for entry in diagnostics {
@@ -2679,13 +2680,14 @@ impl Project {
let mut range = snapshot.clip_point_utf16(start, Bias::Left)
..snapshot.clip_point_utf16(end, Bias::Right);
// 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);
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.start = snapshot.clip_point_utf16(range.start, Bias::Left);
range.end = snapshot.clip_point_utf16(Unclipped(range.end), Bias::Left);
}
}
@@ -3288,7 +3290,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() {
@@ -3307,7 +3309,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(),
@@ -3329,88 +3331,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.0 || end != range.end.0 {
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.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(&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)
@@ -5114,22 +5119,30 @@ impl Project {
_: Arc<Client>,
mut cx: AsyncAppContext,
) -> Result<proto::GetCompletionsResponse> {
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?;
@@ -5616,8 +5629,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
@@ -5703,10 +5716,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.
//
@@ -5718,11 +5731,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
{
@@ -5730,7 +5743,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();
}
@@ -6051,13 +6064,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::PointUtf16 {
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::PointUtf16 {
row: symbol.range.end.0.row,
column: symbol.range.end.0.column,
}),
signature: symbol.signature.to_vec(),
}

View File

@@ -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(),

View File

@@ -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<Task<()>>,
poll_task: Option<Task<()>>,
share: Option<ShareState>,
diagnostics: HashMap<Arc<Path>, Vec<DiagnosticEntry<PointUtf16>>>,
diagnostics: HashMap<Arc<Path>, Vec<DiagnosticEntry<Unclipped<PointUtf16>>>>,
diagnostic_summaries: TreeMap<PathKey, DiagnosticSummary>,
client: Arc<Client>,
fs: Arc<dyn Fs>,
@@ -499,7 +500,10 @@ impl LocalWorktree {
})
}
pub fn diagnostics_for_path(&self, path: &Path) -> Option<Vec<DiagnosticEntry<PointUtf16>>> {
pub fn diagnostics_for_path(
&self,
path: &Path,
) -> Option<Vec<DiagnosticEntry<Unclipped<PointUtf16>>>> {
self.diagnostics.get(path).cloned()
}
@@ -507,7 +511,7 @@ impl LocalWorktree {
&mut self,
language_server_id: usize,
worktree_path: Arc<Path>,
diagnostics: Vec<DiagnosticEntry<PointUtf16>>,
diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
_: &mut ModelContext<Worktree>,
) -> Result<bool> {
self.diagnostics.remove(&worktree_path);

View File

@@ -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"] }
util = { path = "../util" }
[dev-dependencies]
rand = "0.8.3"

View File

@@ -1,16 +1,23 @@
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::Range, str};
use std::{
cmp, fmt, io, mem,
ops::{AddAssign, Range},
str,
};
use sum_tree::{Bias, Dimension, SumTree};
use util::debug_panic;
pub use offset_utf16::OffsetUtf16;
pub use point::Point;
pub use point_utf16::PointUtf16;
pub use unclipped::Unclipped;
#[cfg(test)]
const CHUNK_BASE: usize = 6;
@@ -260,6 +267,14 @@ impl Rope {
}
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<PointUtf16>) -> 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,20 +284,20 @@ 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, clip))
}
pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point {
if point >= self.summary().lines_utf16() {
pub fn unclipped_point_utf16_to_point(&self, point: Unclipped<PointUtf16>) -> 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(overshoot))
+ cursor.item().map_or(Point::zero(), |chunk| {
chunk.unclipped_point_utf16_to_point(overshoot)
})
}
pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize {
@@ -330,11 +345,11 @@ impl Rope {
}
}
pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
pub fn clip_point_utf16(&self, point: Unclipped<PointUtf16>, bias: Bias) -> PointUtf16 {
let mut cursor = self.chunks.cursor::<PointUtf16>();
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()
@@ -665,28 +680,33 @@ 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 {
if point > target {
panic!("point {:?} is inside of character {:?}", target, ch);
debug_panic!("point {target:?} is inside of character {ch:?}");
}
break;
}
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;
if point.row > target.row {
debug_panic!(
"point {target:?} is beyond the end of a line with length {}",
point.column
);
break;
}
} else {
point.column += ch.len_utf8() as u32;
}
offset += ch.len_utf8();
}
offset
}
@@ -711,45 +731,62 @@ impl Chunk {
point_utf16
}
fn point_utf16_to_offset(&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);
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;
if point.row > target.row {
panic!(
"point {:?} is beyond the end of a line with length {}",
target, point.column
);
}
point.column = 0;
if point.row > target.row {
if !clip {
debug_panic!(
"point {target:?} is beyond the end of a line with length {}",
point.column
);
}
// Return the offset of the newline
return offset;
}
} else {
point.column += ch.len_utf16() as u32;
}
if point > target {
if !clip {
debug_panic!("point {target:?} is inside of codepoint {ch:?}");
}
// Return the offset of the codepoint which we have landed within, bias left
return offset;
}
offset += ch.len_utf8();
}
offset
}
fn point_utf16_to_point(&self, target: PointUtf16) -> Point {
fn unclipped_point_utf16_to_point(&self, target: Unclipped<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.0 {
break;
}
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;
}
if ch == '\n' {
point_utf16 += PointUtf16::new(1, 0);
point += Point::new(1, 0);
@@ -758,6 +795,7 @@ impl Chunk {
point += Point::new(0, ch.len_utf8() as u32);
}
}
point
}
@@ -777,11 +815,11 @@ impl Chunk {
unreachable!()
}
fn clip_point_utf16(&self, target: PointUtf16, bias: Bias) -> PointUtf16 {
fn clip_point_utf16(&self, target: Unclipped<PointUtf16>, 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,
@@ -917,7 +955,7 @@ impl std::ops::Add<Self> for TextSummary {
type Output = Self;
fn add(mut self, rhs: Self) -> Self::Output {
self.add_assign(&rhs);
AddAssign::add_assign(&mut self, &rhs);
self
}
}
@@ -1114,15 +1152,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)
);
@@ -1238,7 +1276,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 +1288,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.
// 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);
}
}

View File

@@ -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<T>(pub T);
impl<T> From<T> for Unclipped<T> {
fn from(value: T) -> Self {
Unclipped(value)
}
}
impl<'a, T: sum_tree::Dimension<'a, ChunkSummary>> sum_tree::Dimension<'a, ChunkSummary>
for Unclipped<T>
{
fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
self.0.add_summary(summary, &());
}
}
impl<T: TextDimension> TextDimension for Unclipped<T> {
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<T: Add<T, Output = T>> Add<Unclipped<T>> for Unclipped<T> {
type Output = Unclipped<T>;
fn add(self, rhs: Unclipped<T>) -> Self::Output {
Unclipped(self.0 + rhs.0)
}
}
impl<T: Sub<T, Output = T>> Sub<Unclipped<T>> for Unclipped<T> {
type Output = Unclipped<T>;
fn sub(self, rhs: Unclipped<T>) -> Self::Output {
Unclipped(self.0 - rhs.0)
}
}
impl<T: AddAssign<T>> AddAssign<Unclipped<T>> for Unclipped<T> {
fn add_assign(&mut self, rhs: Unclipped<T>) {
self.0 += rhs.0;
}
}
impl<T: SubAssign<T>> SubAssign<Unclipped<T>> for Unclipped<T> {
fn sub_assign(&mut self, rhs: Unclipped<T>) {
self.0 -= rhs.0;
}
}

View File

@@ -412,8 +412,10 @@ message Symbol {
string name = 4;
int32 kind = 5;
string path = 6;
Point start = 7;
Point 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,7 +1044,7 @@ message Range {
uint64 end = 2;
}
message Point {
message PointUtf16 {
uint32 row = 1;
uint32 column = 2;
}

View File

@@ -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,
@@ -253,6 +254,8 @@ pub struct SettingsFileContent {
#[serde(default)]
pub buffer_font_size: Option<f32>,
#[serde(default)]
pub active_pane_magnification: Option<f32>,
#[serde(default)]
pub cursor_blink: Option<bool>,
#[serde(default)]
pub hover_popover_enabled: Option<bool>,
@@ -312,6 +315,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(),
@@ -367,6 +371,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);
@@ -458,6 +466,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,

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -1594,8 +1594,12 @@ impl BufferSnapshot {
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<PointUtf16>) -> usize {
self.visible_text.unclipped_point_utf16_to_offset(point)
}
pub fn unclipped_point_utf16_to_point(&self, point: Unclipped<PointUtf16>) -> Point {
self.visible_text.unclipped_point_utf16_to_point(point)
}
pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize {
@@ -1766,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,
@@ -1803,7 +1807,10 @@ impl BufferSnapshot {
}
pub fn anchor_at<T: ToOffset>(&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() {
@@ -1840,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<PointUtf16>, bias: Bias) -> PointUtf16 {
self.visible_text.clip_point_utf16(point, bias)
}
@@ -2354,32 +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 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 {
fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
assert!(*self <= snapshot.len(), "offset {self} is out of range");
*self
}
}
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 {
fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
snapshot.summary_for_anchor(self)
}
}
@@ -2390,31 +2385,43 @@ impl<'a, T: ToOffset> ToOffset for &'a T {
}
}
impl ToOffset for PointUtf16 {
fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
snapshot.point_utf16_to_offset(*self)
}
}
impl ToOffset for Unclipped<PointUtf16> {
fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
snapshot.unclipped_point_utf16_to_offset(*self)
}
}
pub trait ToPoint {
fn to_point(&self, snapshot: &BufferSnapshot) -> Point;
}
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 PointUtf16 {
fn to_point<'a>(&self, snapshot: &BufferSnapshot) -> Point {
snapshot.point_utf16_to_point(*self)
impl ToPoint for Point {
fn to_point(&self, _: &BufferSnapshot) -> Point {
*self
}
}
impl ToPoint for Point {
fn to_point<'a>(&self, _: &BufferSnapshot) -> Point {
*self
impl ToPoint for Unclipped<PointUtf16> {
fn to_point(&self, snapshot: &BufferSnapshot) -> Point {
snapshot.unclipped_point_utf16_to_point(*self)
}
}
@@ -2423,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)
}
}
@@ -2451,45 +2458,23 @@ 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
}
}
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 PointUtf16 {
fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self {
snapshot.clip_point_utf16(*self, bias)
}
}
pub trait FromAnchor {
fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self;
}

View File

@@ -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"

View File

@@ -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,

View File

@@ -114,12 +114,12 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspac
};
edits.push((expanded_range, "\n"));
new_selections.push(selection.map(|_| anchor.clone()));
new_selections.push(selection.map(|_| anchor));
} else {
let range = selection.map(|p| p.to_point(map)).range();
let anchor = map.buffer_snapshot.anchor_after(range.end);
edits.push((range, ""));
new_selections.push(selection.map(|_| anchor.clone()));
new_selections.push(selection.map(|_| anchor));
}
selection.goal = SelectionGoal::None;
});

View File

@@ -8,6 +8,7 @@ use gpui::{
};
use project::Project;
use serde::Deserialize;
use settings::Settings;
use theme::Theme;
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -63,10 +64,17 @@ impl PaneGroup {
theme: &Theme,
follower_states: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
cx: &mut RenderContext<Workspace>,
) -> 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<Pane>> {
@@ -104,12 +112,20 @@ impl Member {
Member::Axis(PaneAxis { axis, members })
}
fn contains(&self, needle: &ViewHandle<Pane>) -> 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<Project>,
theme: &Theme,
follower_states: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
cx: &mut RenderContext<Workspace>,
) -> ElementBox {
enum FollowIntoExternalProject {}
@@ -236,7 +252,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,
),
}
}
@@ -337,12 +360,19 @@ impl PaneAxis {
theme: &Theme,
follower_state: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
cx: &mut RenderContext<Workspace>,
) -> 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::<Settings>().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;
@@ -356,7 +386,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()
}

View File

@@ -2787,6 +2787,7 @@ impl View for Workspace {
&theme,
&self.follower_states_by_leader,
self.active_call(),
self.active_pane(),
cx,
))
.flex(1., true)

View File

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
version = "0.65.0"
version = "0.67.0"
[lib]
name = "zed"

View File

@@ -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,

View File

@@ -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