Compare commits

..

10 Commits

Author SHA1 Message Date
Richard Feldman
74e80993a3 Move login metehods out of AgentServer 2025-10-22 12:27:15 -04:00
Richard Feldman
70993b3ce3 Use Vec<String> since that's what extensions will use. 2025-10-22 11:34:05 -04:00
Richard Feldman
a47a387186 Separate out local and remote login/logout 2025-10-22 11:31:13 -04:00
Richard Feldman
5f6f7edc99 Restore return value 2025-10-22 11:17:16 -04:00
Richard Feldman
4f46ac8c2d Don't have default login/logout commands 2025-10-22 11:09:35 -04:00
Richard Feldman
946c41b4b3 Revert "Use a static slice rather than vec"
This reverts commit 7f7312ca8f.
2025-10-22 11:09:05 -04:00
Richard Feldman
7f7312ca8f Use a static slice rather than vec 2025-10-22 11:09:02 -04:00
Richard Feldman
4232b59a59 Split out login_commands and logout_commands 2025-10-22 11:03:47 -04:00
Richard Feldman
b00901c126 Handle oauth fallback 2025-10-22 10:21:07 -04:00
Richard Feldman
e1a83a5fe6 Support multiple agent connections 2025-10-21 22:24:57 -04:00
177 changed files with 2074 additions and 6065 deletions

View File

@@ -847,8 +847,7 @@ jobs:
auto-release-preview:
name: Auto release preview
if: |
false
&& startsWith(github.ref, 'refs/tags/v')
startsWith(github.ref, 'refs/tags/v')
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, bundle-windows-x64]
runs-on:

111
Cargo.lock generated
View File

@@ -151,7 +151,7 @@ dependencies = [
"context_server",
"ctor",
"db",
"derive_more 0.99.20",
"derive_more",
"editor",
"env_logger 0.11.8",
"fs",
@@ -210,30 +210,16 @@ dependencies = [
[[package]]
name = "agent-client-protocol"
version = "0.5.0"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f655394a107cd601bd2e5375c2d909ea83adc65678a0e0e8d77613d3c848a7d"
checksum = "3aaa2bd05a2401887945f8bfd70026e90bc3cf96c62ab9eba2779835bf21dc60"
dependencies = [
"agent-client-protocol-schema",
"anyhow",
"async-broadcast",
"async-trait",
"derive_more 2.0.1",
"futures 0.3.31",
"log",
"parking_lot",
"serde",
"serde_json",
]
[[package]]
name = "agent-client-protocol-schema"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61be4454304d7df1a5b44c4ae55e707ffe72eac4dfb1ef8762510ce8d8f6d924"
dependencies = [
"anyhow",
"derive_more 2.0.1",
"schemars 1.0.4",
"serde",
"serde_json",
@@ -856,7 +842,7 @@ dependencies = [
"anyhow",
"async-trait",
"collections",
"derive_more 0.99.20",
"derive_more",
"extension",
"futures 0.3.31",
"gpui",
@@ -2078,7 +2064,7 @@ dependencies = [
"bitflags 2.9.4",
"cexpr",
"clang-sys",
"itertools 0.12.1",
"itertools 0.11.0",
"log",
"prettyplease",
"proc-macro2",
@@ -2098,7 +2084,7 @@ dependencies = [
"bitflags 2.9.4",
"cexpr",
"clang-sys",
"itertools 0.12.1",
"itertools 0.11.0",
"log",
"prettyplease",
"proc-macro2",
@@ -3093,7 +3079,7 @@ dependencies = [
"cloud_llm_client",
"collections",
"credentials_provider",
"derive_more 0.99.20",
"derive_more",
"feature_flags",
"fs",
"futures 0.3.31",
@@ -3553,7 +3539,7 @@ name = "command_palette_hooks"
version = "0.1.0"
dependencies = [
"collections",
"derive_more 0.99.20",
"derive_more",
"gpui",
"workspace",
]
@@ -4870,27 +4856,6 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "derive_more"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.106",
"unicode-xid",
]
[[package]]
name = "derive_refineable"
version = "0.1.0"
@@ -5037,7 +5002,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users 0.5.2",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -5641,7 +5606,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -6376,14 +6341,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "fs_benchmarks"
version = "0.1.0"
dependencies = [
"fs",
"gpui",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
@@ -6918,7 +6875,7 @@ dependencies = [
"askpass",
"async-trait",
"collections",
"derive_more 0.99.20",
"derive_more",
"futures 0.3.31",
"git2",
"gpui",
@@ -7184,7 +7141,7 @@ dependencies = [
"core-video",
"cosmic-text",
"ctor",
"derive_more 0.99.20",
"derive_more",
"embed-resource",
"env_logger 0.11.8",
"etagere",
@@ -7683,7 +7640,7 @@ dependencies = [
"async-fs",
"async-tar",
"bytes 1.10.1",
"derive_more 0.99.20",
"derive_more",
"futures 0.3.31",
"http 1.3.1",
"http-body 1.0.1",
@@ -10337,7 +10294,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -12890,23 +12847,6 @@ dependencies = [
"zlog",
]
[[package]]
name = "project_benchmarks"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"client",
"futures 0.3.31",
"gpui",
"http_client",
"language",
"node_runtime",
"project",
"settings",
"watch",
]
[[package]]
name = "project_panel"
version = "0.1.0"
@@ -13343,7 +13283,7 @@ dependencies = [
"once_cell",
"socket2 0.6.1",
"tracing",
"windows-sys 0.60.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -14426,7 +14366,7 @@ dependencies = [
"errno 0.3.14",
"libc",
"linux-raw-sys 0.11.0",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -15211,7 +15151,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"derive_more 0.99.20",
"derive_more",
"ec4rs",
"fs",
"futures 0.3.31",
@@ -15294,7 +15234,6 @@ dependencies = [
"schemars 1.0.4",
"search",
"serde",
"serde_json",
"session",
"settings",
"strum 0.27.2",
@@ -16877,7 +16816,7 @@ dependencies = [
"getrandom 0.3.4",
"once_cell",
"rustix 1.1.2",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -17005,7 +16944,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"derive_more 0.99.20",
"derive_more",
"fs",
"futures 0.3.31",
"gpui",
@@ -19559,7 +19498,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.48.0",
]
[[package]]
@@ -20608,16 +20547,6 @@ dependencies = [
"zlog",
]
[[package]]
name = "worktree_benchmarks"
version = "0.1.0"
dependencies = [
"fs",
"gpui",
"settings",
"worktree",
]
[[package]]
name = "writeable"
version = "0.6.1"

View File

@@ -70,7 +70,6 @@ members = [
"crates/file_finder",
"crates/file_icons",
"crates/fs",
"crates/fs_benchmarks",
"crates/fsevent",
"crates/fuzzy",
"crates/git",
@@ -126,7 +125,6 @@ members = [
"crates/picker",
"crates/prettier",
"crates/project",
"crates/project_benchmarks",
"crates/project_panel",
"crates/project_symbols",
"crates/prompt_store",
@@ -195,7 +193,6 @@ members = [
"crates/web_search_providers",
"crates/workspace",
"crates/worktree",
"crates/worktree_benchmarks",
"crates/x_ai",
"crates/zed",
"crates/zed_actions",
@@ -440,7 +437,7 @@ zlog_settings = { path = "crates/zlog_settings" }
# External crates
#
agent-client-protocol = { version = "0.5.0", features = ["unstable"] }
agent-client-protocol = { version = "=0.4.3", features = ["unstable"] }
aho-corasick = "1.1"
alacritty_terminal = "0.25.1-rc1"
any_vec = "0.14"

View File

@@ -1080,8 +1080,7 @@
{
"context": "StashList || (StashList > Picker > Editor)",
"bindings": {
"ctrl-shift-backspace": "stash_picker::DropStashItem",
"ctrl-shift-v": "stash_picker::ShowStashItem"
"ctrl-shift-backspace": "stash_picker::DropStashItem"
}
},
{
@@ -1267,22 +1266,12 @@
"ctrl-pagedown": "settings_editor::FocusNextFile"
}
},
{
"context": "StashDiff > Editor",
"bindings": {
"ctrl-space": "git::ApplyCurrentStash",
"ctrl-shift-space": "git::PopCurrentStash",
"ctrl-shift-backspace": "git::DropCurrentStash"
}
},
{
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"up": "settings_editor::FocusPreviousNavEntry",
"shift-tab": "settings_editor::FocusPreviousNavEntry",
"down": "settings_editor::FocusNextNavEntry",
"tab": "settings_editor::FocusNextNavEntry",
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",

View File

@@ -1153,8 +1153,7 @@
"context": "StashList || (StashList > Picker > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-backspace": "stash_picker::DropStashItem",
"ctrl-shift-v": "stash_picker::ShowStashItem"
"ctrl-shift-backspace": "stash_picker::DropStashItem"
}
},
{
@@ -1372,23 +1371,12 @@
"cmd-}": "settings_editor::FocusNextFile"
}
},
{
"context": "StashDiff > Editor",
"use_key_equivalents": true,
"bindings": {
"ctrl-space": "git::ApplyCurrentStash",
"ctrl-shift-space": "git::PopCurrentStash",
"ctrl-shift-backspace": "git::DropCurrentStash"
}
},
{
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"up": "settings_editor::FocusPreviousNavEntry",
"shift-tab": "settings_editor::FocusPreviousNavEntry",
"down": "settings_editor::FocusNextNavEntry",
"tab": "settings_editor::FocusNextNavEntry",
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",

View File

@@ -1106,8 +1106,7 @@
"context": "StashList || (StashList > Picker > Editor)",
"use_key_equivalents": true,
"bindings": {
"ctrl-shift-backspace": "stash_picker::DropStashItem",
"ctrl-shift-v": "stash_picker::ShowStashItem"
"ctrl-shift-backspace": "stash_picker::DropStashItem"
}
},
{
@@ -1295,23 +1294,12 @@
"ctrl-pagedown": "settings_editor::FocusNextFile"
}
},
{
"context": "StashDiff > Editor",
"use_key_equivalents": true,
"bindings": {
"ctrl-space": "git::ApplyCurrentStash",
"ctrl-shift-space": "git::PopCurrentStash",
"ctrl-shift-backspace": "git::DropCurrentStash"
}
},
{
"context": "SettingsWindow > NavigationMenu",
"use_key_equivalents": true,
"bindings": {
"up": "settings_editor::FocusPreviousNavEntry",
"shift-tab": "settings_editor::FocusPreviousNavEntry",
"down": "settings_editor::FocusNextNavEntry",
"tab": "settings_editor::FocusNextNavEntry",
"right": "settings_editor::ExpandNavEntry",
"left": "settings_editor::CollapseNavEntry",
"pageup": "settings_editor::FocusPreviousRootNavEntry",

View File

@@ -1,8 +1,8 @@
{
"$schema": "zed://schemas/settings",
/// The displayed name of this project. If not set or null, the root directory name
/// The displayed name of this project. If not set or empty, the root directory name
/// will be displayed.
"project_name": null,
"project_name": "",
// The name of the Zed theme to use for the UI.
//
// `mode` is one of:
@@ -1091,10 +1091,10 @@
// Only the file Zed had indexed will be used, not necessary all the gitignored files.
//
// Can accept 3 values:
// * "all": Use all gitignored files
// * "indexed": Use only the files Zed had indexed
// * "smart": Be smart and search for ignored when called from a gitignored worktree
"include_ignored": "smart"
// * `true`: Use all gitignored files
// * `false`: Use only the files Zed had indexed
// * `null`: Be smart and search for ignored when called from a gitignored worktree
"include_ignored": null
},
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
@@ -1350,9 +1350,7 @@
// Whether to show the active language button in the status bar.
"active_language_button": true,
// Whether to show the cursor position button in the status bar.
"cursor_position_button": true,
// Whether to show active line endings button in the status bar.
"line_endings_button": false
"cursor_position_button": true
},
// Settings specific to the terminal
"terminal": {
@@ -1823,7 +1821,7 @@
"use_on_type_format": false
},
"Vue.js": {
"language_servers": ["vue-language-server", "vtsls", "..."],
"language_servers": ["vue-language-server", "..."],
"prettier": {
"allowed": true
}

View File

@@ -49,9 +49,8 @@
"panel.background": "#3a3735ff",
"panel.focused_border": "#83a598ff",
"pane.focused_border": null,
"scrollbar.thumb.active_background": "#83a598ac",
"scrollbar.thumb.hover_background": "#fbf1c74c",
"scrollbar.thumb.background": "#a899844c",
"scrollbar.thumb.background": "#fbf1c74c",
"scrollbar.thumb.hover_background": "#494340ff",
"scrollbar.thumb.border": "#494340ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#373432ff",
@@ -455,9 +454,8 @@
"panel.background": "#393634ff",
"panel.focused_border": "#83a598ff",
"pane.focused_border": null,
"scrollbar.thumb.active_background": "#83a598ac",
"scrollbar.thumb.hover_background": "#fbf1c74c",
"scrollbar.thumb.background": "#a899844c",
"scrollbar.thumb.background": "#fbf1c74c",
"scrollbar.thumb.hover_background": "#494340ff",
"scrollbar.thumb.border": "#494340ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#343130ff",
@@ -861,9 +859,8 @@
"panel.background": "#3b3735ff",
"panel.focused_border": null,
"pane.focused_border": null,
"scrollbar.thumb.active_background": "#83a598ac",
"scrollbar.thumb.hover_background": "#fbf1c74c",
"scrollbar.thumb.background": "#a899844c",
"scrollbar.thumb.background": "#fbf1c74c",
"scrollbar.thumb.hover_background": "#494340ff",
"scrollbar.thumb.border": "#494340ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#393634ff",
@@ -1267,9 +1264,8 @@
"panel.background": "#ecddb4ff",
"panel.focused_border": null,
"pane.focused_border": null,
"scrollbar.thumb.active_background": "#458588ac",
"scrollbar.thumb.hover_background": "#2828284c",
"scrollbar.thumb.background": "#7c6f644c",
"scrollbar.thumb.background": "#2828284c",
"scrollbar.thumb.hover_background": "#ddcca7ff",
"scrollbar.thumb.border": "#ddcca7ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#eee0b7ff",
@@ -1673,9 +1669,8 @@
"panel.background": "#ecddb5ff",
"panel.focused_border": null,
"pane.focused_border": null,
"scrollbar.thumb.active_background": "#458588ac",
"scrollbar.thumb.hover_background": "#2828284c",
"scrollbar.thumb.background": "#7c6f644c",
"scrollbar.thumb.background": "#2828284c",
"scrollbar.thumb.hover_background": "#ddcca7ff",
"scrollbar.thumb.border": "#ddcca7ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#eee1bbff",
@@ -2079,9 +2074,8 @@
"panel.background": "#ecdcb3ff",
"panel.focused_border": null,
"pane.focused_border": null,
"scrollbar.thumb.active_background": "#458588ac",
"scrollbar.thumb.hover_background": "#2828284c",
"scrollbar.thumb.background": "#7c6f644c",
"scrollbar.thumb.background": "#2828284c",
"scrollbar.thumb.hover_background": "#ddcca7ff",
"scrollbar.thumb.border": "#ddcca7ff",
"scrollbar.track.background": "#00000000",
"scrollbar.track.border": "#eddeb5ff",

View File

@@ -328,7 +328,7 @@ impl ToolCall {
location: acp::ToolCallLocation,
project: WeakEntity<Project>,
cx: &mut AsyncApp,
) -> Option<ResolvedLocation> {
) -> Option<AgentLocation> {
let buffer = project
.update(cx, |project, cx| {
project
@@ -350,14 +350,17 @@ impl ToolCall {
})
.ok()?;
Some(ResolvedLocation { buffer, position })
Some(AgentLocation {
buffer: buffer.downgrade(),
position,
})
}
fn resolve_locations(
&self,
project: Entity<Project>,
cx: &mut App,
) -> Task<Vec<Option<ResolvedLocation>>> {
) -> Task<Vec<Option<AgentLocation>>> {
let locations = self.locations.clone();
project.update(cx, |_, cx| {
cx.spawn(async move |project, cx| {
@@ -371,23 +374,6 @@ impl ToolCall {
}
}
// Separate so we can hold a strong reference to the buffer
// for saving on the thread
#[derive(Clone, Debug, PartialEq, Eq)]
struct ResolvedLocation {
buffer: Entity<Buffer>,
position: Anchor,
}
impl From<&ResolvedLocation> for AgentLocation {
fn from(value: &ResolvedLocation) -> Self {
Self {
buffer: value.buffer.downgrade(),
position: value.position,
}
}
}
#[derive(Debug)]
pub enum ToolCallStatus {
/// The tool call hasn't started running yet, but we start showing it to
@@ -1407,43 +1393,35 @@ impl AcpThread {
let task = tool_call.resolve_locations(project, cx);
cx.spawn(async move |this, cx| {
let resolved_locations = task.await;
this.update(cx, |this, cx| {
let project = this.project.clone();
for location in resolved_locations.iter().flatten() {
this.shared_buffers
.insert(location.buffer.clone(), location.buffer.read(cx).snapshot());
}
let Some((ix, tool_call)) = this.tool_call_mut(&id) else {
return;
};
if let Some(Some(location)) = resolved_locations.last() {
project.update(cx, |project, cx| {
let should_ignore = if let Some(agent_location) = project.agent_location() {
let snapshot = location.buffer.read(cx).snapshot();
let old_position = agent_location.position.to_point(&snapshot);
let new_position = location.position.to_point(&snapshot);
agent_location.buffer == location.buffer
// ignore this so that when we get updates from the edit tool
// the position doesn't reset to the startof line
&& (old_position.row == new_position.row
&& old_position.column > new_position.column)
} else {
false
};
if !should_ignore {
project.set_agent_location(Some(location.into()), cx);
if let Some(agent_location) = project.agent_location() {
let should_ignore = agent_location.buffer == location.buffer
&& location
.buffer
.update(cx, |buffer, _| {
let snapshot = buffer.snapshot();
let old_position =
agent_location.position.to_point(&snapshot);
let new_position = location.position.to_point(&snapshot);
// ignore this so that when we get updates from the edit tool
// the position doesn't reset to the startof line
old_position.row == new_position.row
&& old_position.column > new_position.column
})
.ok()
.unwrap_or_default();
if !should_ignore {
project.set_agent_location(Some(location.clone()), cx);
}
}
});
}
let resolved_locations = resolved_locations
.iter()
.map(|l| l.as_ref().map(|l| AgentLocation::from(l)))
.collect::<Vec<_>>();
if tool_call.resolved_locations != resolved_locations {
tool_call.resolved_locations = resolved_locations;
cx.emit(AcpThreadEvent::EntryUpdated(ix));

View File

@@ -236,21 +236,21 @@ impl PendingDiff {
fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
let ranges = self.excerpt_ranges(cx);
let base_text = self.base_text.clone();
let new_buffer = self.new_buffer.read(cx);
let language_registry = new_buffer.language_registry();
let language_registry = self.new_buffer.read(cx).language_registry();
let path = new_buffer
let path = self
.new_buffer
.read(cx)
.file()
.map(|file| file.path().display(file.path_style(cx)))
.unwrap_or("untitled".into())
.into();
let replica_id = new_buffer.replica_id();
// Replace the buffer in the multibuffer with the snapshot
let buffer = cx.new(|cx| {
let language = self.new_buffer.read(cx).language().cloned();
let buffer = TextBuffer::new_normalized(
replica_id,
0,
cx.entity_id().as_non_zero_u64().into(),
self.new_buffer.read(cx).line_ending(),
self.new_buffer.read(cx).as_rope().clone(),

View File

@@ -93,8 +93,8 @@ struct WatchedConnection {
messages: Vec<WatchedConnectionMessage>,
list_state: ListState,
connection: Weak<acp::ClientSideConnection>,
incoming_request_methods: HashMap<acp::RequestId, Arc<str>>,
outgoing_request_methods: HashMap<acp::RequestId, Arc<str>>,
incoming_request_methods: HashMap<i32, Arc<str>>,
outgoing_request_methods: HashMap<i32, Arc<str>>,
_task: Task<()>,
}
@@ -175,7 +175,7 @@ impl AcpTools {
}
};
method_map.insert(id.clone(), method.clone());
method_map.insert(id, method.clone());
(Some(id), method.into(), MessageType::Request, Ok(params))
}
acp::StreamMessageContent::Response { id, result } => {
@@ -338,7 +338,6 @@ impl AcpTools {
.children(
message
.request_id
.as_ref()
.map(|req_id| div().child(ui::Chip::new(req_id.to_string()))),
),
)
@@ -390,7 +389,7 @@ impl AcpTools {
struct WatchedConnectionMessage {
name: SharedString,
request_id: Option<acp::RequestId>,
request_id: Option<i32>,
direction: acp::StreamMessageDirection,
message_type: MessageType,
params: Result<Option<serde_json::Value>, acp::Error>,

View File

@@ -308,13 +308,12 @@ mod tests {
use indoc::indoc;
use language::{BufferId, TextBuffer};
use rand::prelude::*;
use text::ReplicaId;
use util::test::{generate_marked_text, marked_text_ranges};
#[test]
fn test_empty_query() {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
"Hello world\nThis is a test\nFoo bar baz",
);
@@ -328,7 +327,7 @@ mod tests {
#[test]
fn test_streaming_exact_match() {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
"Hello world\nThis is a test\nFoo bar baz",
);
@@ -352,7 +351,7 @@ mod tests {
#[test]
fn test_streaming_fuzzy_match() {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
indoc! {"
function foo(a, b) {
@@ -386,7 +385,7 @@ mod tests {
#[test]
fn test_incremental_improvement() {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
"Line 1\nLine 2\nLine 3\nLine 4\nLine 5",
);
@@ -411,7 +410,7 @@ mod tests {
#[test]
fn test_incomplete_lines_buffering() {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
indoc! {"
The quick brown fox
@@ -438,7 +437,7 @@ mod tests {
#[test]
fn test_multiline_fuzzy_match() {
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
indoc! {r#"
impl Display for User {
@@ -692,11 +691,7 @@ mod tests {
}
"#};
let buffer = TextBuffer::new(
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
text.to_string(),
);
let buffer = TextBuffer::new(0, BufferId::new(1).unwrap(), text.to_string());
let snapshot = buffer.snapshot();
let mut matcher = StreamingFuzzyMatcher::new(snapshot.clone());
@@ -729,7 +724,7 @@ mod tests {
#[track_caller]
fn assert_location_resolution(text_with_expected_range: &str, query: &str, rng: &mut StdRng) {
let (text, expected_ranges) = marked_text_ranges(text_with_expected_range, false);
let buffer = TextBuffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text.clone());
let buffer = TextBuffer::new(0, BufferId::new(1).unwrap(), text.clone());
let snapshot = buffer.snapshot();
let mut matcher = StreamingFuzzyMatcher::new(snapshot);

View File

@@ -2,6 +2,7 @@ use std::{any::Any, path::Path, rc::Rc, sync::Arc};
use agent_servers::{AgentServer, AgentServerDelegate};
use anyhow::Result;
use collections::HashMap;
use fs::Fs;
use gpui::{App, Entity, SharedString, Task};
use prompt_store::PromptStore;
@@ -41,7 +42,7 @@ impl AgentServer for NativeAgentServer {
) -> Task<
Result<(
Rc<dyn acp_thread::AgentConnection>,
Option<task::SpawnInTerminal>,
HashMap<String, task::SpawnInTerminal>,
)>,
> {
log::debug!(
@@ -67,7 +68,7 @@ impl AgentServer for NativeAgentServer {
Ok((
Rc::new(connection) as Rc<dyn acp_thread::AgentConnection>,
None,
HashMap::default(),
))
})
}

View File

@@ -40,7 +40,7 @@ pub struct AcpConnection {
// NB: Don't move this into the wait_task, since we need to ensure the process is
// killed on drop (setting kill_on_drop on the command seems to not always work).
child: smol::process::Child,
_io_task: Task<Result<(), acp::Error>>,
_io_task: Task<Result<()>>,
_wait_task: Task<Result<()>>,
_stderr_task: Task<Result<()>>,
}

View File

@@ -73,7 +73,12 @@ pub trait AgentServer: Send {
root_dir: Option<&Path>,
delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>>;
) -> Task<
Result<(
Rc<dyn AgentConnection>,
HashMap<String, task::SpawnInTerminal>,
)>,
>;
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
}
@@ -84,6 +89,30 @@ impl dyn AgentServer {
}
}
/// Extension trait for ACP-specific agent capabilities.
/// This trait is only implemented by agents that use the Agent Client Protocol (ACP).
pub trait AcpAgentServer: AgentServer {
/// Returns the list of slash commands that should trigger Zed's authentication UI
/// when running locally (e.g., "/login").
/// These commands will be intercepted by Zed to show the auth method selection UI.
fn local_login_commands(&self) -> Vec<String>;
/// Returns the list of slash commands that should trigger Zed's authentication UI
/// when running remotely (e.g., "/login").
/// These commands will be intercepted by Zed to show the auth method selection UI.
fn remote_login_commands(&self) -> Vec<String>;
/// Returns the list of logout-related slash commands that should be sent to the agent
/// when running locally to let it reset internal state (e.g., "/logout").
/// These commands will be added to available_commands and passed through to the agent.
fn local_logout_commands(&self) -> Vec<String>;
/// Returns the list of logout-related slash commands that should be sent to the agent
/// when running remotely to let it reset internal state (e.g., "/logout").
/// These commands will be added to available_commands and passed through to the agent.
fn remote_logout_commands(&self) -> Vec<String>;
}
/// Load the default proxy environment variables to pass through to the agent
pub fn load_proxy_env(cx: &mut App) -> HashMap<String, String> {
let proxy_url = cx

View File

@@ -7,10 +7,11 @@ use std::sync::Arc;
use std::{any::Any, path::PathBuf};
use anyhow::{Context as _, Result};
use collections::HashMap;
use gpui::{App, AppContext as _, SharedString, Task};
use project::agent_server_store::{AllAgentServersSettings, CLAUDE_CODE_NAME};
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
use acp_thread::AgentConnection;
#[derive(Clone)]
@@ -60,7 +61,12 @@ impl AgentServer for ClaudeCode {
root_dir: Option<&Path>,
delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
) -> Task<
Result<(
Rc<dyn AgentConnection>,
HashMap<String, task::SpawnInTerminal>,
)>,
> {
let name = self.name();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
@@ -69,7 +75,7 @@ impl AgentServer for ClaudeCode {
let default_mode = self.default_mode(cx);
cx.spawn(async move |cx| {
let (command, root_dir, login) = store
let (command, root_dir, auth_commands) = store
.update(cx, |store, cx| {
let agent = store
.get_external_agent(&CLAUDE_CODE_NAME.into())
@@ -92,7 +98,7 @@ impl AgentServer for ClaudeCode {
cx,
)
.await?;
Ok((connection, login))
Ok((connection, auth_commands))
})
}
@@ -100,3 +106,21 @@ impl AgentServer for ClaudeCode {
self
}
}
impl AcpAgentServer for ClaudeCode {
fn local_login_commands(&self) -> Vec<String> {
vec!["login".to_string()]
}
fn remote_login_commands(&self) -> Vec<String> {
vec!["login".to_string()]
}
fn local_logout_commands(&self) -> Vec<String> {
vec!["logout".to_string()]
}
fn remote_logout_commands(&self) -> Vec<String> {
vec!["logout".to_string()]
}
}

View File

@@ -5,12 +5,13 @@ use std::{any::Any, path::Path};
use acp_thread::AgentConnection;
use agent_client_protocol as acp;
use anyhow::{Context as _, Result};
use collections::HashMap;
use fs::Fs;
use gpui::{App, AppContext as _, SharedString, Task};
use project::agent_server_store::{AllAgentServersSettings, CODEX_NAME};
use settings::{SettingsStore, update_settings_file};
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
#[derive(Clone)]
pub struct Codex;
@@ -61,7 +62,12 @@ impl AgentServer for Codex {
root_dir: Option<&Path>,
delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
) -> Task<
Result<(
Rc<dyn AgentConnection>,
HashMap<String, task::SpawnInTerminal>,
)>,
> {
let name = self.name();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
@@ -70,7 +76,7 @@ impl AgentServer for Codex {
let default_mode = self.default_mode(cx);
cx.spawn(async move |cx| {
let (command, root_dir, login) = store
let (command, root_dir, auth_commands) = store
.update(cx, |store, cx| {
let agent = store
.get_external_agent(&CODEX_NAME.into())
@@ -96,7 +102,7 @@ impl AgentServer for Codex {
cx,
)
.await?;
Ok((connection, login))
Ok((connection, auth_commands))
})
}
@@ -104,3 +110,21 @@ impl AgentServer for Codex {
self
}
}
impl AcpAgentServer for Codex {
fn local_login_commands(&self) -> Vec<String> {
vec![]
}
fn remote_login_commands(&self) -> Vec<String> {
vec![]
}
fn local_logout_commands(&self) -> Vec<String> {
vec![]
}
fn remote_logout_commands(&self) -> Vec<String> {
vec![]
}
}

View File

@@ -1,12 +1,13 @@
use crate::{AgentServerDelegate, load_proxy_env};
use crate::{AcpAgentServer, AgentServerDelegate, load_proxy_env};
use acp_thread::AgentConnection;
use agent_client_protocol as acp;
use anyhow::{Context as _, Result};
use collections::HashMap;
use fs::Fs;
use gpui::{App, AppContext as _, SharedString, Task};
use project::agent_server_store::{AllAgentServersSettings, ExternalAgentServerName};
use settings::{SettingsStore, update_settings_file};
use std::{path::Path, rc::Rc, sync::Arc};
use std::{any::Any, path::Path, rc::Rc, sync::Arc};
use ui::IconName;
/// A generic agent server implementation for custom user-defined agents
@@ -65,7 +66,12 @@ impl crate::AgentServer for CustomAgentServer {
root_dir: Option<&Path>,
delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
) -> Task<
Result<(
Rc<dyn AgentConnection>,
HashMap<String, task::SpawnInTerminal>,
)>,
> {
let name = self.name();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
@@ -74,7 +80,7 @@ impl crate::AgentServer for CustomAgentServer {
let extra_env = load_proxy_env(cx);
cx.spawn(async move |cx| {
let (command, root_dir, login) = store
let (command, root_dir, auth_commands) = store
.update(cx, |store, cx| {
let agent = store
.get_external_agent(&ExternalAgentServerName(name.clone()))
@@ -99,11 +105,29 @@ impl crate::AgentServer for CustomAgentServer {
cx,
)
.await?;
Ok((connection, login))
Ok((connection, auth_commands))
})
}
fn into_any(self: Rc<Self>) -> Rc<dyn std::any::Any> {
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
}
impl AcpAgentServer for CustomAgentServer {
fn local_login_commands(&self) -> Vec<String> {
vec![]
}
fn remote_login_commands(&self) -> Vec<String> {
vec![]
}
fn local_logout_commands(&self) -> Vec<String> {
vec![]
}
fn remote_logout_commands(&self) -> Vec<String> {
vec![]
}
}

View File

@@ -1,9 +1,10 @@
use std::rc::Rc;
use std::{any::Any, path::Path};
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
use acp_thread::AgentConnection;
use anyhow::{Context as _, Result};
use collections::HashMap;
use gpui::{App, SharedString, Task};
use language_models::provider::google::GoogleLanguageModelProvider;
use project::agent_server_store::GEMINI_NAME;
@@ -29,7 +30,12 @@ impl AgentServer for Gemini {
root_dir: Option<&Path>,
delegate: AgentServerDelegate,
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
) -> Task<
Result<(
Rc<dyn AgentConnection>,
HashMap<String, task::SpawnInTerminal>,
)>,
> {
let name = self.name();
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
let is_remote = delegate.project.read(cx).is_via_remote_server();
@@ -47,7 +53,7 @@ impl AgentServer for Gemini {
{
extra_env.insert("GEMINI_API_KEY".into(), api_key);
}
let (command, root_dir, login) = store
let (command, root_dir, auth_commands) = store
.update(cx, |store, cx| {
let agent = store
.get_external_agent(&GEMINI_NAME.into())
@@ -71,7 +77,7 @@ impl AgentServer for Gemini {
cx,
)
.await?;
Ok((connection, login))
Ok((connection, auth_commands))
})
}
@@ -80,6 +86,26 @@ impl AgentServer for Gemini {
}
}
impl AcpAgentServer for Gemini {
fn local_login_commands(&self) -> Vec<String> {
vec!["login".to_string()]
}
fn remote_login_commands(&self) -> Vec<String> {
// When remote, OAuth doesn't work, so login is handled via the
// auth_commands mapping (oauth-personal -> spawn-gemini-cli)
vec![]
}
fn local_logout_commands(&self) -> Vec<String> {
vec![]
}
fn remote_logout_commands(&self) -> Vec<String> {
vec![]
}
}
#[cfg(test)]
pub(crate) mod tests {
use project::agent_server_store::AgentServerCommand;

View File

@@ -260,10 +260,11 @@ impl ThreadFeedbackState {
pub struct AcpThreadView {
agent: Rc<dyn AgentServer>,
acp_agent: Option<Rc<dyn agent_servers::AcpAgentServer>>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
thread_state: ThreadState,
login: Option<task::SpawnInTerminal>,
auth_commands: HashMap<String, task::SpawnInTerminal>,
history_store: Entity<HistoryStore>,
hovered_recent_history_item: Option<usize>,
entry_view_state: Entity<EntryViewState>,
@@ -403,8 +404,38 @@ impl AcpThreadView {
let show_codex_windows_warning = crate::ExternalAgent::parse_built_in(agent.as_ref())
== Some(crate::ExternalAgent::Codex);
// Try to downcast to AcpAgentServer for ACP-specific functionality
let acp_agent = agent
.clone()
.into_any()
.downcast::<agent_servers::Gemini>()
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
.or_else(|_| {
agent
.clone()
.into_any()
.downcast::<agent_servers::ClaudeCode>()
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
})
.or_else(|_| {
agent
.clone()
.into_any()
.downcast::<agent_servers::Codex>()
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
})
.or_else(|_| {
agent
.clone()
.into_any()
.downcast::<agent_servers::CustomAgentServer>()
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
})
.ok();
Self {
agent: agent.clone(),
acp_agent,
workspace: workspace.clone(),
project: project.clone(),
entry_view_state,
@@ -416,7 +447,7 @@ impl AcpThreadView {
window,
cx,
),
login: None,
auth_commands: HashMap::default(),
message_editor,
model_selector: None,
profile_selector: None,
@@ -509,8 +540,9 @@ impl AcpThreadView {
let connect_task = agent.connect(root_dir.as_deref(), delegate, cx);
let load_task = cx.spawn_in(window, async move |this, cx| {
let connection = match connect_task.await {
Ok((connection, login)) => {
this.update(cx, |this, _| this.login = login).ok();
Ok((connection, auth_commands)) => {
this.update(cx, |this, _| this.auth_commands = auth_commands)
.ok();
connection
}
Err(err) => {
@@ -1051,20 +1083,52 @@ impl AcpThreadView {
let text = self.message_editor.read(cx).text(cx);
let text = text.trim();
if text == "/login" || text == "/logout" {
// Check if this is a login or logout command (only for ACP agents)
let command_name = text.strip_prefix('/');
let is_remote = self.project.read(cx).is_via_remote_server();
let (login_commands, logout_commands) = if let Some(acp_agent) = &self.acp_agent {
let login = if is_remote {
acp_agent.remote_login_commands()
} else {
acp_agent.local_login_commands()
};
let logout = if is_remote {
acp_agent.remote_logout_commands()
} else {
acp_agent.local_logout_commands()
};
(login, logout)
} else {
(vec![], vec![])
};
let is_login_command = if let Some(cmd) = command_name {
login_commands.iter().any(|c| c == cmd)
} else {
false
};
let is_logout_command = if let Some(cmd) = command_name {
logout_commands.iter().any(|c| c == cmd)
} else {
false
};
if is_login_command || is_logout_command {
let ThreadState::Ready { thread, .. } = &self.thread_state else {
return;
};
let connection = thread.read(cx).connection().clone();
let can_login = !connection.auth_methods().is_empty() || self.login.is_some();
let can_login = !connection.auth_methods().is_empty() || !self.auth_commands.is_empty();
// Does the agent have a specific logout command? Prefer that in case they need to reset internal state.
let logout_supported = text == "/logout"
let logout_supported = is_logout_command
&& self
.available_commands
.borrow()
.iter()
.any(|command| command.name == "logout");
.any(|command| command_name == Some(command.name.as_str()));
if can_login && !logout_supported {
self.message_editor
.update(cx, |editor, cx| editor.clear(window, cx));
@@ -1421,25 +1485,39 @@ impl AcpThreadView {
AcpThreadEvent::AvailableCommandsUpdated(available_commands) => {
let mut available_commands = available_commands.clone();
if thread
.read(cx)
.connection()
.auth_methods()
.iter()
.any(|method| method.id.0.as_ref() == "claude-login")
{
available_commands.push(acp::AvailableCommand {
name: "login".to_owned(),
description: "Authenticate".to_owned(),
input: None,
meta: None,
});
available_commands.push(acp::AvailableCommand {
name: "logout".to_owned(),
description: "Authenticate".to_owned(),
input: None,
meta: None,
});
// Add auth commands only for ACP agents
if let Some(acp_agent) = &self.acp_agent {
let is_remote = self.project.read(cx).is_via_remote_server();
let login_commands = if is_remote {
acp_agent.remote_login_commands()
} else {
acp_agent.local_login_commands()
};
let logout_commands = if is_remote {
acp_agent.remote_logout_commands()
} else {
acp_agent.local_logout_commands()
};
// Add login commands from the agent
for command_name in login_commands {
available_commands.push(acp::AvailableCommand {
name: command_name,
description: "Authenticate".to_owned(),
input: None,
meta: None,
});
}
// Add logout commands from the agent
for command_name in logout_commands {
available_commands.push(acp::AvailableCommand {
name: command_name,
description: "Authenticate".to_owned(),
input: None,
meta: None,
});
}
}
self.available_commands.replace(available_commands);
@@ -1561,10 +1639,7 @@ impl AcpThreadView {
self.thread_error.take();
configuration_view.take();
pending_auth_method.replace(method.clone());
let authenticate = if (method.0.as_ref() == "claude-login"
|| method.0.as_ref() == "spawn-gemini-cli")
&& let Some(login) = self.login.clone()
{
let authenticate = if let Some(login) = self.auth_commands.get(method.0.as_ref()).cloned() {
if let Some(workspace) = self.workspace.upgrade() {
Self::spawn_external_agent_login(login, workspace, false, window, cx)
} else {
@@ -3270,48 +3345,34 @@ impl AcpThreadView {
})
.children(connection.auth_methods().iter().enumerate().rev().map(
|(ix, method)| {
let (method_id, name) = if self
.project
.read(cx)
.is_via_remote_server()
&& method.id.0.as_ref() == "oauth-personal"
&& method.name == "Log in with Google"
{
("spawn-gemini-cli".into(), "Log in with Gemini CLI".into())
} else {
(method.id.0.clone(), method.name.clone())
};
let method_id = method.id.clone();
let method_id_str = method.id.0.to_string();
Button::new(
SharedString::from(method.id.0.clone()),
method.name.clone(),
)
.label_size(LabelSize::Small)
.map(|this| {
if ix == 0 {
this.style(ButtonStyle::Tinted(TintColor::Warning))
} else {
this.style(ButtonStyle::Outlined)
}
})
.when_some(method.description.clone(), |this, description| {
this.tooltip(Tooltip::text(description))
})
.on_click({
cx.listener(move |this, _, window, cx| {
telemetry::event!(
"Authenticate Agent Started",
agent = this.agent.telemetry_id(),
method = method_id_str
);
Button::new(SharedString::from(method_id.clone()), name)
.label_size(LabelSize::Small)
.map(|this| {
if ix == 0 {
this.style(ButtonStyle::Tinted(TintColor::Warning))
} else {
this.style(ButtonStyle::Outlined)
}
})
.when_some(
method.description.clone(),
|this, description| {
this.tooltip(Tooltip::text(description))
},
)
.on_click({
cx.listener(move |this, _, window, cx| {
telemetry::event!(
"Authenticate Agent Started",
agent = this.agent.telemetry_id(),
method = method_id
);
this.authenticate(
acp::AuthMethodId(method_id.clone()),
window,
cx,
)
})
this.authenticate(method_id.clone(), window, cx)
})
})
},
)),
)
@@ -6033,8 +6094,13 @@ pub(crate) mod tests {
_root_dir: Option<&Path>,
_delegate: AgentServerDelegate,
_cx: &mut App,
) -> Task<gpui::Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
Task::ready(Ok((Rc::new(self.connection.clone()), None)))
) -> Task<
gpui::Result<(
Rc<dyn AgentConnection>,
HashMap<String, task::SpawnInTerminal>,
)>,
> {
Task::ready(Ok((Rc::new(self.connection.clone()), HashMap::default())))
}
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {

View File

@@ -1395,10 +1395,6 @@ impl Panel for AgentPanel {
"AgentPanel"
}
fn panel_key() -> &'static str {
AGENT_PANEL_KEY
}
fn position(&self, _window: &Window, cx: &App) -> DockPosition {
agent_panel_dock_position(cx)
}

View File

@@ -84,32 +84,10 @@ impl ZedAiOnboarding {
self
}
fn render_dismiss_button(&self) -> Option<AnyElement> {
self.dismiss_onboarding.as_ref().map(|dismiss_callback| {
let callback = dismiss_callback.clone();
h_flex()
.absolute()
.top_0()
.right_0()
.child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click(move |_, window, cx| {
telemetry::event!("Banner Dismissed", source = "AI Onboarding",);
callback(window, cx)
}),
)
.into_any_element()
})
}
fn render_sign_in_disclaimer(&self, _cx: &mut App) -> AnyElement {
let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn);
v_flex()
.relative()
.gap_1()
.child(Headline::new("Welcome to Zed AI"))
.child(
@@ -131,7 +109,6 @@ impl ZedAiOnboarding {
}
}),
)
.children(self.render_dismiss_button())
.into_any_element()
}
@@ -203,7 +180,27 @@ impl ZedAiOnboarding {
)
.child(PlanDefinitions.free_plan(is_v2)),
)
.children(self.render_dismiss_button())
.when_some(
self.dismiss_onboarding.as_ref(),
|this, dismiss_callback| {
let callback = dismiss_callback.clone();
this.child(
h_flex().absolute().top_0().right_0().child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click(move |_, window, cx| {
telemetry::event!(
"Banner Dismissed",
source = "AI Onboarding",
);
callback(window, cx)
}),
),
)
},
)
.child(
v_flex()
.mt_2()
@@ -248,7 +245,26 @@ impl ZedAiOnboarding {
.mb_2(),
)
.child(PlanDefinitions.pro_trial(is_v2, false))
.children(self.render_dismiss_button())
.when_some(
self.dismiss_onboarding.as_ref(),
|this, dismiss_callback| {
let callback = dismiss_callback.clone();
this.child(
h_flex().absolute().top_0().right_0().child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click(move |_, window, cx| {
telemetry::event!(
"Banner Dismissed",
source = "AI Onboarding",
);
callback(window, cx)
}),
),
)
},
)
.into_any_element()
}
@@ -262,7 +278,26 @@ impl ZedAiOnboarding {
.mb_2(),
)
.child(PlanDefinitions.pro_plan(is_v2, false))
.children(self.render_dismiss_button())
.when_some(
self.dismiss_onboarding.as_ref(),
|this, dismiss_callback| {
let callback = dismiss_callback.clone();
this.child(
h_flex().absolute().top_0().right_0().child(
IconButton::new("dismiss_onboarding", IconName::Close)
.icon_size(IconSize::Small)
.tooltip(Tooltip::text("Dismiss"))
.on_click(move |_, window, cx| {
telemetry::event!(
"Banner Dismissed",
source = "AI Onboarding",
);
callback(window, cx)
}),
),
)
},
)
.into_any_element()
}
}

View File

@@ -486,7 +486,7 @@ pub enum ContextSummary {
Error,
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Default, Clone, Debug, Eq, PartialEq)]
pub struct ContextSummaryContent {
pub text: String,
pub done: bool,
@@ -523,11 +523,7 @@ impl ContextSummary {
match self {
ContextSummary::Content(content) => content,
ContextSummary::Pending | ContextSummary::Error => {
let content = ContextSummaryContent {
text: "".to_string(),
done: false,
timestamp: clock::Lamport::MIN,
};
let content = ContextSummaryContent::default();
*self = ContextSummary::Content(content);
self.content_as_mut().unwrap()
}
@@ -800,7 +796,7 @@ impl AssistantContext {
};
let first_message_id = MessageId(clock::Lamport {
replica_id: ReplicaId::LOCAL,
replica_id: 0,
value: 0,
});
let message = MessageAnchor {
@@ -2696,7 +2692,7 @@ impl AssistantContext {
self.summary = ContextSummary::Content(ContextSummaryContent {
text: "".to_string(),
done: false,
timestamp: clock::Lamport::MIN,
timestamp: clock::Lamport::default(),
});
replace_old = true;
}
@@ -3121,7 +3117,7 @@ impl SavedContext {
let mut first_message_metadata = None;
for message in self.messages {
if message.id == MessageId(clock::Lamport::MIN) {
if message.id == MessageId(clock::Lamport::default()) {
first_message_metadata = Some(message.metadata);
} else {
operations.push(ContextOperation::InsertMessage {
@@ -3145,7 +3141,7 @@ impl SavedContext {
if let Some(metadata) = first_message_metadata {
let timestamp = next_timestamp.tick();
operations.push(ContextOperation::UpdateMessage {
message_id: MessageId(clock::Lamport::MIN),
message_id: MessageId(clock::Lamport::default()),
metadata: MessageMetadata {
role: metadata.role,
status: metadata.status,

View File

@@ -741,7 +741,7 @@ async fn test_serialization(cx: &mut TestAppContext) {
);
}
#[gpui::test(iterations = 25)]
#[gpui::test(iterations = 100)]
async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: StdRng) {
cx.update(init_test);
@@ -771,7 +771,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
let context = cx.new(|cx| {
AssistantContext::new(
context_id.clone(),
ReplicaId::new(i as u16),
i as ReplicaId,
language::Capability::ReadWrite,
registry.clone(),
prompt_builder.clone(),
@@ -789,7 +789,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
if let ContextEvent::Operation(op) = event {
network
.lock()
.broadcast(ReplicaId::new(i as u16), vec![op.to_proto()]);
.broadcast(i as ReplicaId, vec![op.to_proto()]);
}
}
})
@@ -797,7 +797,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
});
contexts.push(context);
network.lock().add_peer(ReplicaId::new(i as u16));
network.lock().add_peer(i as ReplicaId);
}
let mut mutation_count = operations;
@@ -943,9 +943,9 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
mutation_count -= 1;
}
_ => {
let replica_id = ReplicaId::new(context_index as u16);
let replica_id = context_index as ReplicaId;
if network.lock().is_disconnected(replica_id) {
network.lock().reconnect_peer(replica_id, ReplicaId::new(0));
network.lock().reconnect_peer(replica_id, 0);
let (ops_to_send, ops_to_receive) = cx.read(|cx| {
let host_context = &contexts[0].read(cx);
@@ -971,7 +971,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
network.lock().broadcast(replica_id, ops_to_send);
context.update(cx, |context, cx| context.apply_ops(ops_to_receive, cx));
} else if rng.random_bool(0.1) && replica_id != ReplicaId::new(0) {
} else if rng.random_bool(0.1) && replica_id != 0 {
log::info!("Context {}: disconnecting", context_index);
network.lock().disconnect_peer(replica_id);
} else if network.lock().has_unreceived(replica_id) {
@@ -996,25 +996,25 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
assert_eq!(
context.buffer.read(cx).text(),
first_context.buffer.read(cx).text(),
"Context {:?} text != Context 0 text",
"Context {} text != Context 0 text",
context.buffer.read(cx).replica_id()
);
assert_eq!(
context.message_anchors,
first_context.message_anchors,
"Context {:?} messages != Context 0 messages",
"Context {} messages != Context 0 messages",
context.buffer.read(cx).replica_id()
);
assert_eq!(
context.messages_metadata,
first_context.messages_metadata,
"Context {:?} message metadata != Context 0 message metadata",
"Context {} message metadata != Context 0 message metadata",
context.buffer.read(cx).replica_id()
);
assert_eq!(
context.slash_command_output_sections,
first_context.slash_command_output_sections,
"Context {:?} slash command output sections != Context 0 slash command output sections",
"Context {} slash command output sections != Context 0 slash command output sections",
context.buffer.read(cx).replica_id()
);
}

View File

@@ -85,7 +85,7 @@ struct PendingHunk {
new_status: DiffHunkSecondaryStatus,
}
#[derive(Debug, Clone)]
#[derive(Debug, Default, Clone)]
pub struct DiffHunkSummary {
buffer_range: Range<Anchor>,
}
@@ -114,9 +114,7 @@ impl sum_tree::Summary for DiffHunkSummary {
type Context<'a> = &'a text::BufferSnapshot;
fn zero(_cx: Self::Context<'_>) -> Self {
DiffHunkSummary {
buffer_range: Anchor::MIN..Anchor::MIN,
}
Default::default()
}
fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
@@ -939,9 +937,7 @@ impl BufferDiff {
pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
if self.secondary_diff.is_some() {
self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
buffer_range: Anchor::MIN..Anchor::MIN,
});
self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary::default());
cx.emit(BufferDiffEvent::DiffChanged {
changed_range: Some(Anchor::MIN..Anchor::MAX),
});
@@ -1372,7 +1368,7 @@ mod tests {
use gpui::TestAppContext;
use pretty_assertions::{assert_eq, assert_ne};
use rand::{Rng as _, rngs::StdRng};
use text::{Buffer, BufferId, ReplicaId, Rope};
use text::{Buffer, BufferId, Rope};
use unindent::Unindent as _;
use util::test::marked_text_ranges;
@@ -1397,7 +1393,7 @@ mod tests {
"
.unindent();
let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
assert_hunks(
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
@@ -1471,7 +1467,7 @@ mod tests {
"
.unindent();
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
let mut uncommitted_diff =
BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
@@ -1540,7 +1536,7 @@ mod tests {
"
.unindent();
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let diff = cx
.update(|cx| {
BufferDiffSnapshot::new_with_base_text(
@@ -1803,7 +1799,7 @@ mod tests {
for example in table {
let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
let hunk_range =
buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
@@ -1876,11 +1872,7 @@ mod tests {
"
.unindent();
let buffer = Buffer::new(
ReplicaId::LOCAL,
BufferId::new(1).unwrap(),
buffer_text.clone(),
);
let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text.clone());
let unstaged = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
let uncommitted = BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
let unstaged_diff = cx.new(|cx| {
@@ -1953,7 +1945,7 @@ mod tests {
"
.unindent();
let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1);
let empty_diff = cx.update(|cx| BufferDiffSnapshot::empty(&buffer, cx));
let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);

View File

@@ -9,7 +9,7 @@ use rpc::{
proto::{self, PeerId},
};
use std::{sync::Arc, time::Duration};
use text::{BufferId, ReplicaId};
use text::BufferId;
use util::ResultExt;
pub const ACKNOWLEDGE_DEBOUNCE_INTERVAL: Duration = Duration::from_millis(250);
@@ -65,12 +65,7 @@ impl ChannelBuffer {
let buffer = cx.new(|cx| {
let capability = channel_store.read(cx).channel_capability(channel.id);
language::Buffer::remote(
buffer_id,
ReplicaId::new(response.replica_id as u16),
capability,
base_text,
)
language::Buffer::remote(buffer_id, response.replica_id as u16, capability, base_text)
})?;
buffer.update(cx, |buffer, cx| buffer.apply_ops(operations, cx))?;
@@ -277,7 +272,7 @@ impl ChannelBuffer {
self.connected
}
pub fn replica_id(&self, cx: &App) -> ReplicaId {
pub fn replica_id(&self, cx: &App) -> u16 {
self.buffer.read(cx).replica_id()
}
}

View File

@@ -17,7 +17,6 @@ pub enum CliRequest {
wsl: Option<String>,
wait: bool,
open_new_workspace: Option<bool>,
reuse: bool,
env: Option<HashMap<String, String>>,
user_data_dir: Option<String>,
},

View File

@@ -62,14 +62,11 @@ struct Args {
#[arg(short, long)]
wait: bool,
/// Add files to the currently open workspace
#[arg(short, long, overrides_with_all = ["new", "reuse"])]
#[arg(short, long, overrides_with = "new")]
add: bool,
/// Create a new workspace
#[arg(short, long, overrides_with_all = ["add", "reuse"])]
#[arg(short, long, overrides_with = "add")]
new: bool,
/// Reuse an existing window, replacing its workspace
#[arg(short, long, overrides_with_all = ["add", "new"])]
reuse: bool,
/// Sets a custom directory for all user data (e.g., database, extensions, logs).
/// This overrides the default platform-specific data directory location:
#[cfg_attr(target_os = "macos", doc = "`~/Library/Application Support/Zed`.")]
@@ -377,7 +374,6 @@ fn main() -> Result<()> {
wsl,
wait: args.wait,
open_new_workspace,
reuse: args.reuse,
env,
user_data_dir: user_data_dir_for_thread,
})?;

View File

@@ -943,7 +943,7 @@ impl Collaborator {
pub fn from_proto(message: proto::Collaborator) -> Result<Self> {
Ok(Self {
peer_id: message.peer_id.context("invalid peer id")?,
replica_id: ReplicaId::new(message.replica_id as u16),
replica_id: message.replica_id as ReplicaId,
user_id: message.user_id as UserId,
is_host: message.is_host,
committer_name: message.committer_name,

View File

@@ -4,73 +4,33 @@ use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::{
cmp::{self, Ordering},
fmt,
fmt, iter,
};
pub use system_clock::*;
pub const LOCAL_BRANCH_REPLICA_ID: u16 = u16::MAX;
pub const AGENT_REPLICA_ID: u16 = u16::MAX - 1;
/// A unique identifier for each distributed node.
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub struct ReplicaId(u16);
impl ReplicaId {
/// The local replica
pub const LOCAL: ReplicaId = ReplicaId(0);
/// The remote replica of the connected remote server.
pub const REMOTE_SERVER: ReplicaId = ReplicaId(1);
/// The agent's unique identifier.
pub const AGENT: ReplicaId = ReplicaId(2);
/// A local branch.
pub const LOCAL_BRANCH: ReplicaId = ReplicaId(3);
/// The first collaborative replica ID, any replica equal or greater than this is a collaborative replica.
pub const FIRST_COLLAB_ID: ReplicaId = ReplicaId(8);
pub fn new(id: u16) -> Self {
ReplicaId(id)
}
pub fn as_u16(&self) -> u16 {
self.0
}
pub fn is_remote(self) -> bool {
self == ReplicaId::REMOTE_SERVER || self >= ReplicaId::FIRST_COLLAB_ID
}
}
impl fmt::Debug for ReplicaId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if *self == ReplicaId::LOCAL {
write!(f, "<local>")
} else if *self == ReplicaId::REMOTE_SERVER {
write!(f, "<remote>")
} else if *self == ReplicaId::AGENT {
write!(f, "<agent>")
} else if *self == ReplicaId::LOCAL_BRANCH {
write!(f, "<branch>")
} else {
write!(f, "{}", self.0)
}
}
}
pub type ReplicaId = u16;
/// A [Lamport sequence number](https://en.wikipedia.org/wiki/Lamport_timestamp).
pub type Seq = u32;
/// A [Lamport timestamp](https://en.wikipedia.org/wiki/Lamport_timestamp),
/// used to determine the ordering of events in the editor.
#[derive(Clone, Copy, Eq, Hash, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct Lamport {
pub replica_id: ReplicaId,
pub value: Seq,
}
/// A [version vector](https://en.wikipedia.org/wiki/Version_vector).
/// A [vector clock](https://en.wikipedia.org/wiki/Vector_clock).
#[derive(Clone, Default, Hash, Eq, PartialEq)]
pub struct Global {
// 4 is chosen as it is the biggest count that does not increase the size of the field itself.
// Coincidentally, it also covers all the important non-collab replica ids.
values: SmallVec<[u32; 4]>,
values: SmallVec<[u32; 8]>,
local_branch_value: u32,
}
impl Global {
@@ -78,31 +38,30 @@ impl Global {
Self::default()
}
/// Fetches the sequence number for the given replica ID.
pub fn get(&self, replica_id: ReplicaId) -> Seq {
self.values.get(replica_id.0 as usize).copied().unwrap_or(0) as Seq
}
/// Observe the lamport timestampe.
///
/// This sets the current sequence number of the observed replica ID to the maximum of this global's observed sequence and the observed timestamp.
pub fn observe(&mut self, timestamp: Lamport) {
debug_assert_ne!(timestamp.replica_id, Lamport::MAX.replica_id);
if timestamp.value > 0 {
let new_len = timestamp.replica_id.0 as usize + 1;
if new_len > self.values.len() {
self.values.resize(new_len, 0);
}
let entry = &mut self.values[timestamp.replica_id.0 as usize];
*entry = cmp::max(*entry, timestamp.value);
if replica_id == LOCAL_BRANCH_REPLICA_ID {
self.local_branch_value
} else {
self.values.get(replica_id as usize).copied().unwrap_or(0) as Seq
}
}
pub fn observe(&mut self, timestamp: Lamport) {
if timestamp.value > 0 {
if timestamp.replica_id == LOCAL_BRANCH_REPLICA_ID {
self.local_branch_value = cmp::max(self.local_branch_value, timestamp.value);
} else {
let new_len = timestamp.replica_id as usize + 1;
if new_len > self.values.len() {
self.values.resize(new_len, 0);
}
let entry = &mut self.values[timestamp.replica_id as usize];
*entry = cmp::max(*entry, timestamp.value);
}
}
}
/// Join another global.
///
/// This observes all timestamps from the other global.
#[doc(alias = "synchronize")]
pub fn join(&mut self, other: &Self) {
if other.values.len() > self.values.len() {
self.values.resize(other.values.len(), 0);
@@ -111,36 +70,34 @@ impl Global {
for (left, right) in self.values.iter_mut().zip(&other.values) {
*left = cmp::max(*left, *right);
}
self.local_branch_value = cmp::max(self.local_branch_value, other.local_branch_value);
}
/// Meet another global.
///
/// Sets all unobserved timestamps of this global to the sequences of other and sets all observed timestamps of this global to the minimum observed of both globals.
pub fn meet(&mut self, other: &Self) {
if other.values.len() > self.values.len() {
self.values.resize(other.values.len(), 0);
}
let mut new_len = 0;
for (ix, (left, &right)) in self.values.iter_mut().zip(&other.values).enumerate() {
match (*left, right) {
// left has not observed the replica
(0, _) => *left = right,
// right has not observed the replica
(_, 0) => (),
(_, _) => *left = cmp::min(*left, right),
for (ix, (left, right)) in self
.values
.iter_mut()
.zip(other.values.iter().chain(iter::repeat(&0)))
.enumerate()
{
if *left == 0 {
*left = *right;
} else if *right > 0 {
*left = cmp::min(*left, *right);
}
if *left != 0 {
new_len = ix + 1;
}
}
if other.values.len() == self.values.len() {
// only truncate if other was equal or shorter (which at this point
// cant be due to the resize above) to `self` as otherwise we would
// truncate the unprocessed tail that is guaranteed to contain
// non-null timestamps
self.values.truncate(new_len);
}
self.values.resize(new_len, 0);
self.local_branch_value = cmp::min(self.local_branch_value, other.local_branch_value);
}
pub fn observed(&self, timestamp: Lamport) -> bool {
@@ -148,18 +105,20 @@ impl Global {
}
pub fn observed_any(&self, other: &Self) -> bool {
self.iter()
.zip(other.iter())
.any(|(left, right)| right.value > 0 && left.value >= right.value)
self.values
.iter()
.zip(other.values.iter())
.any(|(left, right)| *right > 0 && left >= right)
|| (other.local_branch_value > 0 && self.local_branch_value >= other.local_branch_value)
}
pub fn observed_all(&self, other: &Self) -> bool {
if self.values.len() < other.values.len() {
return false;
}
self.iter()
.zip(other.iter())
.all(|(left, right)| left.value >= right.value)
let mut rhs = other.values.iter();
self.values.iter().all(|left| match rhs.next() {
Some(right) => left >= right,
None => true,
}) && rhs.next().is_none()
&& self.local_branch_value >= other.local_branch_value
}
pub fn changed_since(&self, other: &Self) -> bool {
@@ -169,21 +128,21 @@ impl Global {
.iter()
.zip(other.values.iter())
.any(|(left, right)| left > right)
|| self.local_branch_value > other.local_branch_value
}
pub fn most_recent(&self) -> Option<Lamport> {
self.iter().max_by_key(|timestamp| timestamp.value)
}
/// Iterates all replicas observed by this global as well as any unobserved replicas whose ID is lower than the highest observed replica.
pub fn iter(&self) -> impl Iterator<Item = Lamport> + '_ {
self.values
.iter()
.enumerate()
.map(|(replica_id, seq)| Lamport {
replica_id: ReplicaId(replica_id as u16),
replica_id: replica_id as ReplicaId,
value: *seq,
})
.chain((self.local_branch_value > 0).then_some(Lamport {
replica_id: LOCAL_BRANCH_REPLICA_ID,
value: self.local_branch_value,
}))
}
}
@@ -214,12 +173,12 @@ impl PartialOrd for Lamport {
impl Lamport {
pub const MIN: Self = Self {
replica_id: ReplicaId(u16::MIN),
replica_id: ReplicaId::MIN,
value: Seq::MIN,
};
pub const MAX: Self = Self {
replica_id: ReplicaId(u16::MAX),
replica_id: ReplicaId::MAX,
value: Seq::MAX,
};
@@ -231,7 +190,7 @@ impl Lamport {
}
pub fn as_u64(self) -> u64 {
((self.value as u64) << 32) | (self.replica_id.0 as u64)
((self.value as u64) << 32) | (self.replica_id as u64)
}
pub fn tick(&mut self) -> Self {
@@ -252,7 +211,7 @@ impl fmt::Debug for Lamport {
} else if *self == Self::MIN {
write!(f, "Lamport {{MIN}}")
} else {
write!(f, "Lamport {{{:?}: {}}}", self.replica_id, self.value)
write!(f, "Lamport {{{}: {}}}", self.replica_id, self.value)
}
}
}
@@ -261,10 +220,16 @@ impl fmt::Debug for Global {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Global {{")?;
for timestamp in self.iter() {
if timestamp.replica_id.0 > 0 {
if timestamp.replica_id > 0 {
write!(f, ", ")?;
}
write!(f, "{:?}: {}", timestamp.replica_id, timestamp.value)?;
if timestamp.replica_id == LOCAL_BRANCH_REPLICA_ID {
write!(f, "<branch>: {}", timestamp.value)?;
} else if timestamp.replica_id == AGENT_REPLICA_ID {
write!(f, "<agent>: {}", timestamp.value)?;
} else {
write!(f, "{}: {}", timestamp.replica_id, timestamp.value)?;
}
}
write!(f, "}}")
}

View File

@@ -62,9 +62,9 @@ impl Database {
.iter()
.map(|c| c.replica_id)
.collect::<HashSet<_>>();
let mut replica_id = ReplicaId(clock::ReplicaId::FIRST_COLLAB_ID.as_u16() as i32);
let mut replica_id = ReplicaId(0);
while replica_ids.contains(&replica_id) {
replica_id = ReplicaId(replica_id.0 + 1);
replica_id.0 += 1;
}
let collaborator = channel_buffer_collaborator::ActiveModel {
channel_id: ActiveValue::Set(channel_id),
@@ -203,7 +203,7 @@ impl Database {
while let Some(row) = rows.next().await {
let row = row?;
let timestamp = clock::Lamport {
replica_id: clock::ReplicaId::new(row.replica_id as u16),
replica_id: row.replica_id as u16,
value: row.lamport_timestamp as u32,
};
server_version.observe(timestamp);
@@ -701,11 +701,7 @@ impl Database {
return Ok(());
}
let mut text_buffer = text::Buffer::new(
clock::ReplicaId::LOCAL,
text::BufferId::new(1).unwrap(),
base_text,
);
let mut text_buffer = text::Buffer::new(0, text::BufferId::new(1).unwrap(), base_text);
text_buffer.apply_ops(operations.into_iter().filter_map(operation_from_wire));
let base_text = text_buffer.text();
@@ -938,7 +934,7 @@ pub fn operation_from_wire(operation: proto::Operation) -> Option<text::Operatio
match operation.variant? {
proto::operation::Variant::Edit(edit) => Some(text::Operation::Edit(EditOperation {
timestamp: clock::Lamport {
replica_id: clock::ReplicaId::new(edit.replica_id as u16),
replica_id: edit.replica_id as text::ReplicaId,
value: edit.lamport_timestamp,
},
version: version_from_wire(&edit.version),
@@ -953,7 +949,7 @@ pub fn operation_from_wire(operation: proto::Operation) -> Option<text::Operatio
})),
proto::operation::Variant::Undo(undo) => Some(text::Operation::Undo(UndoOperation {
timestamp: clock::Lamport {
replica_id: clock::ReplicaId::new(undo.replica_id as u16),
replica_id: undo.replica_id as text::ReplicaId,
value: undo.lamport_timestamp,
},
version: version_from_wire(&undo.version),
@@ -963,7 +959,7 @@ pub fn operation_from_wire(operation: proto::Operation) -> Option<text::Operatio
.map(|c| {
(
clock::Lamport {
replica_id: clock::ReplicaId::new(c.replica_id as u16),
replica_id: c.replica_id as text::ReplicaId,
value: c.lamport_timestamp,
},
c.count,
@@ -979,7 +975,7 @@ fn version_from_wire(message: &[proto::VectorClockEntry]) -> clock::Global {
let mut version = clock::Global::new();
for entry in message {
version.observe(clock::Lamport {
replica_id: clock::ReplicaId::new(entry.replica_id as u16),
replica_id: entry.replica_id as text::ReplicaId,
value: entry.timestamp,
});
}
@@ -990,7 +986,7 @@ fn version_to_wire(version: &clock::Global) -> Vec<proto::VectorClockEntry> {
let mut message = Vec::new();
for entry in version.iter() {
message.push(proto::VectorClockEntry {
replica_id: entry.replica_id.as_u16() as u32,
replica_id: entry.replica_id as u32,
timestamp: entry.value,
});
}

View File

@@ -91,18 +91,14 @@ impl Database {
.await?;
}
let replica_id = if is_ssh_project {
clock::ReplicaId::REMOTE_SERVER
} else {
clock::ReplicaId::LOCAL
};
let replica_id = if is_ssh_project { 1 } else { 0 };
project_collaborator::ActiveModel {
project_id: ActiveValue::set(project.id),
connection_id: ActiveValue::set(connection.id as i32),
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
user_id: ActiveValue::set(participant.user_id),
replica_id: ActiveValue::set(ReplicaId(replica_id.as_u16() as i32)),
replica_id: ActiveValue::set(ReplicaId(replica_id)),
is_host: ActiveValue::set(true),
id: ActiveValue::NotSet,
committer_name: ActiveValue::Set(None),
@@ -845,7 +841,7 @@ impl Database {
.iter()
.map(|c| c.replica_id)
.collect::<HashSet<_>>();
let mut replica_id = ReplicaId(clock::ReplicaId::FIRST_COLLAB_ID.as_u16() as i32);
let mut replica_id = ReplicaId(1);
while replica_ids.contains(&replica_id) {
replica_id.0 += 1;
}

View File

@@ -1,7 +1,7 @@
use super::*;
use crate::test_both_dbs;
use language::proto::{self, serialize_version};
use text::{Buffer, ReplicaId};
use text::Buffer;
test_both_dbs!(
test_channel_buffers,
@@ -70,11 +70,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
.await
.unwrap();
let mut buffer_a = Buffer::new(
ReplicaId::new(0),
text::BufferId::new(1).unwrap(),
"".to_string(),
);
let mut buffer_a = Buffer::new(0, text::BufferId::new(1).unwrap(), "".to_string());
let operations = vec![
buffer_a.edit([(0..0, "hello world")]),
buffer_a.edit([(5..5, ", cruel")]),
@@ -99,7 +95,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
.unwrap();
let mut buffer_b = Buffer::new(
ReplicaId::new(0),
0,
text::BufferId::new(1).unwrap(),
buffer_response_b.base_text,
);
@@ -128,7 +124,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
rpc::proto::Collaborator {
user_id: a_id.to_proto(),
peer_id: Some(rpc::proto::PeerId { id: 1, owner_id }),
replica_id: ReplicaId::FIRST_COLLAB_ID.as_u16() as u32,
replica_id: 0,
is_host: false,
committer_name: None,
committer_email: None,
@@ -136,7 +132,7 @@ async fn test_channel_buffers(db: &Arc<Database>) {
rpc::proto::Collaborator {
user_id: b_id.to_proto(),
peer_id: Some(rpc::proto::PeerId { id: 2, owner_id }),
replica_id: ReplicaId::FIRST_COLLAB_ID.as_u16() as u32 + 1,
replica_id: 1,
is_host: false,
committer_name: None,
committer_email: None,
@@ -232,8 +228,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
.await
.unwrap();
let res = db
.join_channel_buffer(channel, user_id, connection_id)
db.join_channel_buffer(channel, user_id, connection_id)
.await
.unwrap();
@@ -244,7 +239,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
);
text_buffers.push(Buffer::new(
ReplicaId::new(res.replica_id as u16),
0,
text::BufferId::new(1).unwrap(),
"".to_string(),
));
@@ -281,12 +276,7 @@ async fn test_channel_buffers_last_operations(db: &Database) {
db.join_channel_buffer(buffers[1].channel_id, user_id, connection_id)
.await
.unwrap();
let replica_id = text_buffers[1].replica_id();
text_buffers[1] = Buffer::new(
replica_id,
text::BufferId::new(1).unwrap(),
"def".to_string(),
);
text_buffers[1] = Buffer::new(1, text::BufferId::new(1).unwrap(), "def".to_string());
update_buffer(
buffers[1].channel_id,
user_id,
@@ -314,32 +304,20 @@ async fn test_channel_buffers_last_operations(db: &Database) {
rpc::proto::ChannelBufferVersion {
channel_id: buffers[0].channel_id.to_proto(),
epoch: 0,
version: serialize_version(&text_buffers[0].version())
.into_iter()
.filter(
|vector| vector.replica_id == text_buffers[0].replica_id().as_u16() as u32
)
.collect::<Vec<_>>(),
version: serialize_version(&text_buffers[0].version()),
},
rpc::proto::ChannelBufferVersion {
channel_id: buffers[1].channel_id.to_proto(),
epoch: 1,
version: serialize_version(&text_buffers[1].version())
.into_iter()
.filter(
|vector| vector.replica_id == text_buffers[1].replica_id().as_u16() as u32
)
.filter(|vector| vector.replica_id == text_buffers[1].replica_id() as u32)
.collect::<Vec<_>>(),
},
rpc::proto::ChannelBufferVersion {
channel_id: buffers[2].channel_id.to_proto(),
epoch: 0,
version: serialize_version(&text_buffers[2].version())
.into_iter()
.filter(
|vector| vector.replica_id == text_buffers[2].replica_id().as_u16() as u32
)
.collect::<Vec<_>>(),
version: serialize_version(&text_buffers[2].version()),
},
]
);

View File

@@ -3037,10 +3037,6 @@ impl Panel for CollabPanel {
"CollabPanel"
}
fn panel_key() -> &'static str {
COLLABORATION_PANEL_KEY
}
fn activation_priority(&self) -> u32 {
6
}

View File

@@ -612,10 +612,6 @@ impl Panel for NotificationPanel {
"NotificationPanel"
}
fn panel_key() -> &'static str {
NOTIFICATION_PANEL_KEY
}
fn position(&self, _: &Window, cx: &App) -> DockPosition {
NotificationPanelSettings::get_global(cx).dock
}

View File

@@ -43,8 +43,6 @@ use workspace::{
};
use zed_actions::ToggleFocus;
const DEBUG_PANEL_KEY: &str = "DebugPanel";
pub struct DebugPanel {
size: Pixels,
active_session: Option<Entity<DebugSession>>,
@@ -1416,10 +1414,6 @@ impl Panel for DebugPanel {
"DebugPanel"
}
fn panel_key() -> &'static str {
DEBUG_PANEL_KEY
}
fn position(&self, _window: &Window, cx: &App) -> DockPosition {
DebuggerSettings::get_global(cx).dock.into()
}

View File

@@ -386,7 +386,6 @@ pub(crate) fn new_debugger_pane(
Default::default(),
None,
NoAction.boxed_clone(),
true,
window,
cx,
);

View File

@@ -1341,7 +1341,7 @@ async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext)
range: Some(range),
}))
});
let delay = cx.update(|_, cx| EditorSettings::get_global(cx).hover_popover_delay.0 + 1);
let delay = cx.update(|_, cx| EditorSettings::get_global(cx).hover_popover_delay + 1);
cx.background_executor
.advance_clock(Duration::from_millis(delay));

View File

@@ -27,9 +27,9 @@ pub use predict_edits_v3::Line;
#[derive(Clone, Debug, PartialEq)]
pub struct EditPredictionContextOptions {
pub use_imports: bool,
pub use_references: bool,
pub excerpt: EditPredictionExcerptOptions,
pub score: EditPredictionScoreOptions,
pub max_retrieved_declarations: u8,
}
#[derive(Clone, Debug)]
@@ -118,7 +118,7 @@ impl EditPredictionContext {
)?;
let excerpt_text = excerpt.text(buffer);
let declarations = if options.max_retrieved_declarations > 0
let declarations = if options.use_references
&& let Some(index_state) = index_state
{
let excerpt_occurrences =
@@ -136,7 +136,7 @@ impl EditPredictionContext {
let references = get_references(&excerpt, &excerpt_text, buffer);
let mut declarations = scored_declarations(
scored_declarations(
&options.score,
&index_state,
&excerpt,
@@ -146,10 +146,7 @@ impl EditPredictionContext {
references,
cursor_offset_in_file,
buffer,
);
// TODO [zeta2] if we need this when we ship, we should probably do it in a smarter way
declarations.truncate(options.max_retrieved_declarations as usize);
declarations
)
} else {
vec![]
};
@@ -203,6 +200,7 @@ mod tests {
buffer_snapshot,
EditPredictionContextOptions {
use_imports: true,
use_references: true,
excerpt: EditPredictionExcerptOptions {
max_bytes: 60,
min_bytes: 10,
@@ -211,7 +209,6 @@ mod tests {
score: EditPredictionScoreOptions {
omit_excerpt_overlaps: true,
},
max_retrieved_declarations: u8::MAX,
},
Some(index.clone()),
cx,

View File

@@ -458,8 +458,6 @@ actions!(
/// Expands all diff hunks in the editor.
#[action(deprecated_aliases = ["editor::ExpandAllHunkDiffs"])]
ExpandAllDiffHunks,
/// Collapses all diff hunks in the editor.
CollapseAllDiffHunks,
/// Expands macros recursively at cursor position.
ExpandMacroRecursively,
/// Finds all references to the symbol at cursor.

View File

@@ -594,11 +594,7 @@ impl DisplayMap {
self.block_map.read(snapshot, edits);
}
pub fn remove_inlays_for_excerpts(
&mut self,
excerpts_removed: &[ExcerptId],
cx: &mut Context<Self>,
) {
pub fn remove_inlays_for_excerpts(&mut self, excerpts_removed: &[ExcerptId]) {
let to_remove = self
.inlay_map
.current_inlays()
@@ -610,7 +606,7 @@ impl DisplayMap {
}
})
.collect::<Vec<_>>();
self.splice_inlays(&to_remove, Vec::new(), cx);
self.inlay_map.splice(&to_remove, Vec::new());
}
fn tab_size(buffer: &Entity<MultiBuffer>, cx: &App) -> NonZeroU32 {

View File

@@ -1521,11 +1521,10 @@ impl BlockSnapshot {
}
pub(super) fn line_len(&self, row: BlockRow) -> u32 {
let (start, _, item) =
self.transforms
.find::<Dimensions<BlockRow, WrapRow>, _>((), &row, Bias::Right);
if let Some(transform) = item {
let Dimensions(output_start, input_start, _) = start;
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
cursor.seek(&BlockRow(row.0), Bias::Right);
if let Some(transform) = cursor.item() {
let Dimensions(output_start, input_start, _) = cursor.start();
let overshoot = row.0 - output_start.0;
if transform.block.is_some() {
0
@@ -1540,13 +1539,15 @@ impl BlockSnapshot {
}
pub(super) fn is_block_line(&self, row: BlockRow) -> bool {
let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
item.is_some_and(|t| t.block.is_some())
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
cursor.seek(&row, Bias::Right);
cursor.item().is_some_and(|t| t.block.is_some())
}
pub(super) fn is_folded_buffer_header(&self, row: BlockRow) -> bool {
let (_, _, item) = self.transforms.find::<BlockRow, _>((), &row, Bias::Right);
let Some(transform) = item else {
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
cursor.seek(&row, Bias::Right);
let Some(transform) = cursor.item() else {
return false;
};
matches!(transform.block, Some(Block::FoldedBuffer { .. }))
@@ -1556,10 +1557,9 @@ impl BlockSnapshot {
let wrap_point = self
.wrap_snapshot
.make_wrap_point(Point::new(row.0, 0), Bias::Left);
let (_, _, item) =
self.transforms
.find::<WrapRow, _>((), &WrapRow(wrap_point.row()), Bias::Right);
item.is_some_and(|transform| {
let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(());
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right);
cursor.item().is_some_and(|transform| {
transform
.block
.as_ref()
@@ -1627,16 +1627,13 @@ impl BlockSnapshot {
}
pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint {
let (start, _, item) = self.transforms.find::<Dimensions<WrapRow, BlockRow>, _>(
(),
&WrapRow(wrap_point.row()),
Bias::Right,
);
if let Some(transform) = item {
let mut cursor = self.transforms.cursor::<Dimensions<WrapRow, BlockRow>>(());
cursor.seek(&WrapRow(wrap_point.row()), Bias::Right);
if let Some(transform) = cursor.item() {
if transform.block.is_some() {
BlockPoint::new(start.1.0, 0)
BlockPoint::new(cursor.start().1.0, 0)
} else {
let Dimensions(input_start_row, output_start_row, _) = start;
let Dimensions(input_start_row, output_start_row, _) = cursor.start();
let input_start = Point::new(input_start_row.0, 0);
let output_start = Point::new(output_start_row.0, 0);
let input_overshoot = wrap_point.0 - input_start;
@@ -1648,29 +1645,26 @@ impl BlockSnapshot {
}
pub fn to_wrap_point(&self, block_point: BlockPoint, bias: Bias) -> WrapPoint {
let (start, end, item) = self.transforms.find::<Dimensions<BlockRow, WrapRow>, _>(
(),
&BlockRow(block_point.row),
Bias::Right,
);
if let Some(transform) = item {
let mut cursor = self.transforms.cursor::<Dimensions<BlockRow, WrapRow>>(());
cursor.seek(&BlockRow(block_point.row), Bias::Right);
if let Some(transform) = cursor.item() {
match transform.block.as_ref() {
Some(block) => {
if block.place_below() {
let wrap_row = start.1.0 - 1;
let wrap_row = cursor.start().1.0 - 1;
WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
} else if block.place_above() {
WrapPoint::new(start.1.0, 0)
WrapPoint::new(cursor.start().1.0, 0)
} else if bias == Bias::Left {
WrapPoint::new(start.1.0, 0)
WrapPoint::new(cursor.start().1.0, 0)
} else {
let wrap_row = end.1.0 - 1;
let wrap_row = cursor.end().1.0 - 1;
WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row))
}
}
None => {
let overshoot = block_point.row - start.0.0;
let wrap_row = start.1.0 + overshoot;
let overshoot = block_point.row - cursor.start().0.0;
let wrap_row = cursor.start().1.0 + overshoot;
WrapPoint::new(wrap_row, block_point.column)
}
}

View File

@@ -98,26 +98,28 @@ impl FoldPoint {
}
pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint {
let (start, _, _) = snapshot
let mut cursor = snapshot
.transforms
.find::<Dimensions<FoldPoint, InlayPoint>, _>((), &self, Bias::Right);
let overshoot = self.0 - start.0.0;
InlayPoint(start.1.0 + overshoot)
.cursor::<Dimensions<FoldPoint, InlayPoint>>(());
cursor.seek(&self, Bias::Right);
let overshoot = self.0 - cursor.start().0.0;
InlayPoint(cursor.start().1.0 + overshoot)
}
pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset {
let (start, _, item) = snapshot
let mut cursor = snapshot
.transforms
.find::<Dimensions<FoldPoint, TransformSummary>, _>((), &self, Bias::Right);
let overshoot = self.0 - start.1.output.lines;
let mut offset = start.1.output.len;
.cursor::<Dimensions<FoldPoint, TransformSummary>>(());
cursor.seek(&self, Bias::Right);
let overshoot = self.0 - cursor.start().1.output.lines;
let mut offset = cursor.start().1.output.len;
if !overshoot.is_zero() {
let transform = item.expect("display point out of range");
let transform = cursor.item().expect("display point out of range");
assert!(transform.placeholder.is_none());
let end_inlay_offset = snapshot
.inlay_snapshot
.to_offset(InlayPoint(start.1.input.lines + overshoot));
offset += end_inlay_offset.0 - start.1.input.len;
.to_offset(InlayPoint(cursor.start().1.input.lines + overshoot));
offset += end_inlay_offset.0 - cursor.start().1.input.len;
}
FoldOffset(offset)
}
@@ -704,18 +706,19 @@ impl FoldSnapshot {
}
pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint {
let (start, end, item) = self
let mut cursor = self
.transforms
.find::<Dimensions<InlayPoint, FoldPoint>, _>((), &point, Bias::Right);
if item.is_some_and(|t| t.is_fold()) {
if bias == Bias::Left || point == start.0 {
start.1
.cursor::<Dimensions<InlayPoint, FoldPoint>>(());
cursor.seek(&point, Bias::Right);
if cursor.item().is_some_and(|t| t.is_fold()) {
if bias == Bias::Left || point == cursor.start().0 {
cursor.start().1
} else {
end.1
cursor.end().1
}
} else {
let overshoot = point.0 - start.0.0;
FoldPoint(cmp::min(start.1.0 + overshoot, end.1.0))
let overshoot = point.0 - cursor.start().0.0;
FoldPoint(cmp::min(cursor.start().1.0 + overshoot, cursor.end().1.0))
}
}
@@ -784,10 +787,9 @@ impl FoldSnapshot {
{
let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer);
let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset);
let (_, _, item) = self
.transforms
.find::<InlayOffset, _>((), &inlay_offset, Bias::Right);
item.is_some_and(|t| t.placeholder.is_some())
let mut cursor = self.transforms.cursor::<InlayOffset>(());
cursor.seek(&inlay_offset, Bias::Right);
cursor.item().is_some_and(|t| t.placeholder.is_some())
}
pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool {
@@ -889,22 +891,23 @@ impl FoldSnapshot {
}
pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint {
let (start, end, item) = self
let mut cursor = self
.transforms
.find::<Dimensions<FoldPoint, InlayPoint>, _>((), &point, Bias::Right);
if let Some(transform) = item {
let transform_start = start.0.0;
.cursor::<Dimensions<FoldPoint, InlayPoint>>(());
cursor.seek(&point, Bias::Right);
if let Some(transform) = cursor.item() {
let transform_start = cursor.start().0.0;
if transform.placeholder.is_some() {
if point.0 == transform_start || matches!(bias, Bias::Left) {
FoldPoint(transform_start)
} else {
FoldPoint(end.0.0)
FoldPoint(cursor.end().0.0)
}
} else {
let overshoot = InlayPoint(point.0 - transform_start);
let inlay_point = start.1 + overshoot;
let inlay_point = cursor.start().1 + overshoot;
let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias);
FoldPoint(start.0.0 + (clipped_inlay_point - start.1).0)
FoldPoint(cursor.start().0.0 + (clipped_inlay_point - cursor.start().1).0)
}
} else {
FoldPoint(self.transforms.summary().output.lines)
@@ -1477,26 +1480,28 @@ pub struct FoldOffset(pub usize);
impl FoldOffset {
pub fn to_point(self, snapshot: &FoldSnapshot) -> FoldPoint {
let (start, _, item) = snapshot
let mut cursor = snapshot
.transforms
.find::<Dimensions<FoldOffset, TransformSummary>, _>((), &self, Bias::Right);
let overshoot = if item.is_none_or(|t| t.is_fold()) {
Point::new(0, (self.0 - start.0.0) as u32)
.cursor::<Dimensions<FoldOffset, TransformSummary>>(());
cursor.seek(&self, Bias::Right);
let overshoot = if cursor.item().is_none_or(|t| t.is_fold()) {
Point::new(0, (self.0 - cursor.start().0.0) as u32)
} else {
let inlay_offset = start.1.input.len + self.0 - start.0.0;
let inlay_offset = cursor.start().1.input.len + self.0 - cursor.start().0.0;
let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset));
inlay_point.0 - start.1.input.lines
inlay_point.0 - cursor.start().1.input.lines
};
FoldPoint(start.1.output.lines + overshoot)
FoldPoint(cursor.start().1.output.lines + overshoot)
}
#[cfg(test)]
pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset {
let (start, _, _) = snapshot
let mut cursor = snapshot
.transforms
.find::<Dimensions<FoldOffset, InlayOffset>, _>((), &self, Bias::Right);
let overshoot = self.0 - start.0.0;
InlayOffset(start.1.0 + overshoot)
.cursor::<Dimensions<FoldOffset, InlayOffset>>(());
cursor.seek(&self, Bias::Right);
let overshoot = self.0 - cursor.start().0.0;
InlayOffset(cursor.start().1.0 + overshoot)
}
}

View File

@@ -825,21 +825,22 @@ impl InlayMap {
impl InlaySnapshot {
pub fn to_point(&self, offset: InlayOffset) -> InlayPoint {
let (start, _, item) = self
let mut cursor = self
.transforms
.find::<Dimensions<InlayOffset, InlayPoint, usize>, _>((), &offset, Bias::Right);
let overshoot = offset.0 - start.0.0;
match item {
.cursor::<Dimensions<InlayOffset, InlayPoint, usize>>(());
cursor.seek(&offset, Bias::Right);
let overshoot = offset.0 - cursor.start().0.0;
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
let buffer_offset_start = start.2;
let buffer_offset_start = cursor.start().2;
let buffer_offset_end = buffer_offset_start + overshoot;
let buffer_start = self.buffer.offset_to_point(buffer_offset_start);
let buffer_end = self.buffer.offset_to_point(buffer_offset_end);
InlayPoint(start.1.0 + (buffer_end - buffer_start))
InlayPoint(cursor.start().1.0 + (buffer_end - buffer_start))
}
Some(Transform::Inlay(inlay)) => {
let overshoot = inlay.text().offset_to_point(overshoot);
InlayPoint(start.1.0 + overshoot)
InlayPoint(cursor.start().1.0 + overshoot)
}
None => self.max_point(),
}
@@ -854,48 +855,47 @@ impl InlaySnapshot {
}
pub fn to_offset(&self, point: InlayPoint) -> InlayOffset {
let (start, _, item) = self
let mut cursor = self
.transforms
.find::<Dimensions<InlayPoint, InlayOffset, Point>, _>((), &point, Bias::Right);
let overshoot = point.0 - start.0.0;
match item {
.cursor::<Dimensions<InlayPoint, InlayOffset, Point>>(());
cursor.seek(&point, Bias::Right);
let overshoot = point.0 - cursor.start().0.0;
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
let buffer_point_start = start.2;
let buffer_point_start = cursor.start().2;
let buffer_point_end = buffer_point_start + overshoot;
let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start);
let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end);
InlayOffset(start.1.0 + (buffer_offset_end - buffer_offset_start))
InlayOffset(cursor.start().1.0 + (buffer_offset_end - buffer_offset_start))
}
Some(Transform::Inlay(inlay)) => {
let overshoot = inlay.text().point_to_offset(overshoot);
InlayOffset(start.1.0 + overshoot)
InlayOffset(cursor.start().1.0 + overshoot)
}
None => self.len(),
}
}
pub fn to_buffer_point(&self, point: InlayPoint) -> Point {
let (start, _, item) =
self.transforms
.find::<Dimensions<InlayPoint, Point>, _>((), &point, Bias::Right);
match item {
let mut cursor = self.transforms.cursor::<Dimensions<InlayPoint, Point>>(());
cursor.seek(&point, Bias::Right);
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
let overshoot = point.0 - start.0.0;
start.1 + overshoot
let overshoot = point.0 - cursor.start().0.0;
cursor.start().1 + overshoot
}
Some(Transform::Inlay(_)) => start.1,
Some(Transform::Inlay(_)) => cursor.start().1,
None => self.buffer.max_point(),
}
}
pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize {
let (start, _, item) =
self.transforms
.find::<Dimensions<InlayOffset, usize>, _>((), &offset, Bias::Right);
match item {
let mut cursor = self.transforms.cursor::<Dimensions<InlayOffset, usize>>(());
cursor.seek(&offset, Bias::Right);
match cursor.item() {
Some(Transform::Isomorphic(_)) => {
let overshoot = offset - start.0;
start.1 + overshoot.0
let overshoot = offset - cursor.start().0;
cursor.start().1 + overshoot.0
}
Some(Transform::Inlay(_)) => start.1,
Some(Transform::Inlay(_)) => cursor.start().1,
None => self.buffer.len(),
}
}
@@ -1278,7 +1278,7 @@ mod tests {
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String("a".to_string()),
position: text::Anchor::MIN,
position: text::Anchor::default(),
padding_left: false,
padding_right: false,
tooltip: None,
@@ -1298,7 +1298,7 @@ mod tests {
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String("a".to_string()),
position: text::Anchor::MIN,
position: text::Anchor::default(),
padding_left: true,
padding_right: true,
tooltip: None,
@@ -1318,7 +1318,7 @@ mod tests {
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String(" a ".to_string()),
position: text::Anchor::MIN,
position: text::Anchor::default(),
padding_left: false,
padding_right: false,
tooltip: None,
@@ -1338,7 +1338,7 @@ mod tests {
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String(" a ".to_string()),
position: text::Anchor::MIN,
position: text::Anchor::default(),
padding_left: true,
padding_right: true,
tooltip: None,
@@ -1361,7 +1361,7 @@ mod tests {
Anchor::min(),
&InlayHint {
label: InlayHintLabel::String("🎨".to_string()),
position: text::Anchor::MIN,
position: text::Anchor::default(),
padding_left: true,
padding_right: true,
tooltip: None,

View File

@@ -568,17 +568,14 @@ impl WrapSnapshot {
let mut old_start = old_cursor.start().output.lines;
old_start += tab_edit.old.start.0 - old_cursor.start().input.lines;
// todo(lw): Should these be seek_forward?
old_cursor.seek(&tab_edit.old.end, Bias::Right);
let mut old_end = old_cursor.start().output.lines;
old_end += tab_edit.old.end.0 - old_cursor.start().input.lines;
// todo(lw): Should these be seek_forward?
new_cursor.seek(&tab_edit.new.start, Bias::Right);
let mut new_start = new_cursor.start().output.lines;
new_start += tab_edit.new.start.0 - new_cursor.start().input.lines;
// todo(lw): Should these be seek_forward?
new_cursor.seek(&tab_edit.new.end, Bias::Right);
let mut new_end = new_cursor.start().output.lines;
new_end += tab_edit.new.end.0 - new_cursor.start().input.lines;
@@ -631,22 +628,24 @@ impl WrapSnapshot {
}
pub fn line_len(&self, row: u32) -> u32 {
let (start, _, item) = self.transforms.find::<Dimensions<WrapPoint, TabPoint>, _>(
(),
&WrapPoint::new(row + 1, 0),
Bias::Left,
);
if item.is_some_and(|transform| transform.is_isomorphic()) {
let overshoot = row - start.0.row();
let tab_row = start.1.row() + overshoot;
let mut cursor = self
.transforms
.cursor::<Dimensions<WrapPoint, TabPoint>>(());
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left);
if cursor
.item()
.is_some_and(|transform| transform.is_isomorphic())
{
let overshoot = row - cursor.start().0.row();
let tab_row = cursor.start().1.row() + overshoot;
let tab_line_len = self.tab_snapshot.line_len(tab_row);
if overshoot == 0 {
start.0.column() + (tab_line_len - start.1.column())
cursor.start().0.column() + (tab_line_len - cursor.start().1.column())
} else {
tab_line_len
}
} else {
start.0.column()
cursor.start().0.column()
}
}
@@ -712,10 +711,9 @@ impl WrapSnapshot {
}
pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
let (.., item) =
self.transforms
.find::<WrapPoint, _>((), &WrapPoint::new(row + 1, 0), Bias::Right);
item.and_then(|transform| {
let mut cursor = self.transforms.cursor::<WrapPoint>(());
cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right);
cursor.item().and_then(|transform| {
if transform.is_isomorphic() {
None
} else {
@@ -751,12 +749,13 @@ impl WrapSnapshot {
}
pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint {
let (start, _, item) =
self.transforms
.find::<Dimensions<WrapPoint, TabPoint>, _>((), &point, Bias::Right);
let mut tab_point = start.1.0;
if item.is_some_and(|t| t.is_isomorphic()) {
tab_point += point.0 - start.0.0;
let mut cursor = self
.transforms
.cursor::<Dimensions<WrapPoint, TabPoint>>(());
cursor.seek(&point, Bias::Right);
let mut tab_point = cursor.start().1.0;
if cursor.item().is_some_and(|t| t.is_isomorphic()) {
tab_point += point.0 - cursor.start().0.0;
}
TabPoint(tab_point)
}
@@ -770,19 +769,19 @@ impl WrapSnapshot {
}
pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint {
let (start, ..) =
self.transforms
.find::<Dimensions<TabPoint, WrapPoint>, _>((), &point, Bias::Right);
WrapPoint(start.1.0 + (point.0 - start.0.0))
let mut cursor = self
.transforms
.cursor::<Dimensions<TabPoint, WrapPoint>>(());
cursor.seek(&point, Bias::Right);
WrapPoint(cursor.start().1.0 + (point.0 - cursor.start().0.0))
}
pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint {
if bias == Bias::Left {
let (start, _, item) = self
.transforms
.find::<WrapPoint, _>((), &point, Bias::Right);
if item.is_some_and(|t| !t.is_isomorphic()) {
point = start;
let mut cursor = self.transforms.cursor::<WrapPoint>(());
cursor.seek(&point, Bias::Right);
if cursor.item().is_some_and(|t| !t.is_isomorphic()) {
point = *cursor.start();
*point.column_mut() -= 1;
}
}

View File

@@ -82,7 +82,7 @@ use anyhow::{Context as _, Result, anyhow};
use blink_manager::BlinkManager;
use buffer_diff::DiffHunkStatus;
use client::{Collaborator, ParticipantIndex, parse_zed_link};
use clock::ReplicaId;
use clock::{AGENT_REPLICA_ID, ReplicaId};
use code_context_menus::{
AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
CompletionsMenu, ContextMenuOrigin,
@@ -358,7 +358,6 @@ pub fn init(cx: &mut App) {
cx.observe_new(
|workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
workspace.register_action(Editor::new_file);
workspace.register_action(Editor::new_file_split);
workspace.register_action(Editor::new_file_vertical);
workspace.register_action(Editor::new_file_horizontal);
workspace.register_action(Editor::cancel_language_server_work);
@@ -1302,7 +1301,7 @@ enum SelectionHistoryMode {
#[derive(Clone, PartialEq, Eq, Hash)]
struct HoveredCursor {
replica_id: ReplicaId,
replica_id: u16,
selection_id: usize,
}
@@ -2466,15 +2465,15 @@ impl Editor {
})
}
pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
self.key_context_internal(self.has_active_edit_prediction(), window, cx)
}
fn key_context_internal(
&self,
has_active_edit_prediction: bool,
window: &mut Window,
cx: &mut App,
window: &Window,
cx: &App,
) -> KeyContext {
let mut key_context = KeyContext::new_with_defaults();
key_context.add("Editor");
@@ -2551,17 +2550,6 @@ impl Editor {
key_context.add("selection_mode");
}
let disjoint = self.selections.disjoint_anchors();
let snapshot = self.snapshot(window, cx);
let snapshot = snapshot.buffer_snapshot();
if self.mode == EditorMode::SingleLine
&& let [selection] = disjoint
&& selection.start == selection.end
&& selection.end.to_offset(snapshot) == snapshot.len()
{
key_context.add("end_of_input");
}
key_context
}
@@ -2615,8 +2603,8 @@ impl Editor {
pub fn accept_edit_prediction_keybind(
&self,
accept_partial: bool,
window: &mut Window,
cx: &mut App,
window: &Window,
cx: &App,
) -> AcceptEditPredictionBinding {
let key_context = self.key_context_internal(true, window, cx);
let in_conflict = self.edit_prediction_in_conflict();
@@ -2695,15 +2683,6 @@ impl Editor {
Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
}
fn new_file_split(
workspace: &mut Workspace,
action: &workspace::NewFileSplit,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
Self::new_file_in_direction(workspace, action.0, window, cx)
}
fn new_file_in_direction(
workspace: &mut Workspace,
direction: SplitDirection,
@@ -2758,7 +2737,7 @@ impl Editor {
self.buffer().read(cx).title(cx)
}
pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
let git_blame_gutter_max_author_length = self
.render_git_blame_gutter(cx)
.then(|| {
@@ -5309,8 +5288,8 @@ impl Editor {
{
self.splice_inlays(&to_remove, to_insert, cx);
}
self.display_map.update(cx, |display_map, cx| {
display_map.remove_inlays_for_excerpts(&excerpts_removed, cx)
self.display_map.update(cx, |display_map, _| {
display_map.remove_inlays_for_excerpts(&excerpts_removed)
});
return;
}
@@ -6793,7 +6772,7 @@ impl Editor {
if let Some(state) = &mut self.inline_blame_popover {
state.hide_task.take();
} else {
let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
let blame_entry = blame_entry.clone();
let show_task = cx.spawn(async move |editor, cx| {
if !ignore_timeout {
@@ -6884,7 +6863,7 @@ impl Editor {
return None;
}
let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
cx.background_executor()
.timer(Duration::from_millis(debounce))
@@ -9296,7 +9275,7 @@ impl Editor {
fn render_edit_prediction_accept_keybind(
&self,
window: &mut Window,
cx: &mut App,
cx: &App,
) -> Option<AnyElement> {
let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
let accept_keystroke = accept_binding.keystroke()?;
@@ -9342,7 +9321,7 @@ impl Editor {
label: impl Into<SharedString>,
icon: Option<IconName>,
window: &mut Window,
cx: &mut App,
cx: &App,
) -> Stateful<Div> {
let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
@@ -18803,17 +18782,6 @@ impl Editor {
});
}
pub fn collapse_all_diff_hunks(
&mut self,
_: &CollapseAllDiffHunks,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.buffer.update(cx, |buffer, cx| {
buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
});
}
pub fn toggle_selected_diff_hunks(
&mut self,
_: &ToggleSelectedDiffHunks,
@@ -21062,7 +21030,6 @@ impl Editor {
}
multi_buffer::Event::ExcerptsExpanded { ids } => {
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
self.refresh_document_highlights(cx);
cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
}
multi_buffer::Event::Reparsed(buffer_id) => {
@@ -23515,7 +23482,7 @@ impl EditorSnapshot {
self.buffer_snapshot()
.selections_in_range(range, false)
.filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
if replica_id == ReplicaId::AGENT {
if replica_id == AGENT_REPLICA_ID {
Some(RemoteSelection {
replica_id,
selection,

View File

@@ -5,7 +5,7 @@ use language::CursorShape;
use project::project_settings::DiagnosticSeverity;
use settings::Settings;
pub use settings::{
CurrentLineHighlight, DelayMs, DisplayIn, DocumentColorsRenderMode, DoubleClickInMultibuffer,
CurrentLineHighlight, DisplayIn, DocumentColorsRenderMode, DoubleClickInMultibuffer,
GoToDefinitionFallback, HideMouseMode, MinimapThumb, MinimapThumbBorder, MultiCursorModifier,
ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap, SnippetSortOrder,
};
@@ -20,9 +20,9 @@ pub struct EditorSettings {
pub current_line_highlight: CurrentLineHighlight,
pub selection_highlight: bool,
pub rounded_selection: bool,
pub lsp_highlight_debounce: DelayMs,
pub lsp_highlight_debounce: u64,
pub hover_popover_enabled: bool,
pub hover_popover_delay: DelayMs,
pub hover_popover_delay: u64,
pub toolbar: Toolbar,
pub scrollbar: Scrollbar,
pub minimap: Minimap,
@@ -147,7 +147,7 @@ pub struct DragAndDropSelection {
/// The delay in milliseconds that must elapse before drag and drop is allowed. Otherwise, a new text selection is created.
///
/// Default: 300
pub delay: DelayMs,
pub delay: u64,
}
/// Default options for buffer and project search items.

View File

@@ -26838,24 +26838,3 @@ async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
cx.assert_editor_state("line1\nline2\nˇ");
}
#[gpui::test]
async fn test_end_of_editor_context(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
cx.set_state("line1\nline2ˇ");
cx.update_editor(|e, window, cx| {
e.set_mode(EditorMode::SingleLine);
assert!(e.key_context(window, cx).contains("end_of_input"));
});
cx.set_state("ˇline1\nline2");
cx.update_editor(|e, window, cx| {
assert!(!e.key_context(window, cx).contains("end_of_input"));
});
cx.set_state("line1ˇ\nline2");
cx.update_editor(|e, window, cx| {
assert!(!e.key_context(window, cx).contains("end_of_input"));
});
}

View File

@@ -493,7 +493,6 @@ impl EditorElement {
register_action(editor, window, Editor::stage_and_next);
register_action(editor, window, Editor::unstage_and_next);
register_action(editor, window, Editor::expand_all_diff_hunks);
register_action(editor, window, Editor::collapse_all_diff_hunks);
register_action(editor, window, Editor::go_to_previous_change);
register_action(editor, window, Editor::go_to_next_change);
@@ -1071,10 +1070,7 @@ impl EditorElement {
ref mouse_down_time,
} => {
let drag_and_drop_delay = Duration::from_millis(
EditorSettings::get_global(cx)
.drag_and_drop_selection
.delay
.0,
EditorSettings::get_global(cx).drag_and_drop_selection.delay,
);
if mouse_down_time.elapsed() >= drag_and_drop_delay {
let drop_cursor = Selection {
@@ -6176,10 +6172,7 @@ impl EditorElement {
} = &editor.selection_drag_state
{
let drag_and_drop_delay = Duration::from_millis(
EditorSettings::get_global(cx)
.drag_and_drop_selection
.delay
.0,
EditorSettings::get_global(cx).drag_and_drop_selection.delay,
);
if mouse_down_time.elapsed() >= drag_and_drop_delay {
window.set_cursor_style(

View File

@@ -154,7 +154,7 @@ pub fn hover_at_inlay(
hide_hover(editor, cx);
}
let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
let task = cx.spawn_in(window, async move |this, cx| {
async move {
@@ -275,7 +275,7 @@ fn show_hover(
return None;
}
let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
let all_diagnostics_active = editor.active_diagnostics == ActiveDiagnostic::All;
let active_group_id = if let ActiveDiagnostic::Group(group) = &editor.active_diagnostics {
Some(group.group_id)
@@ -1004,7 +1004,7 @@ mod tests {
use text::Bias;
fn get_hover_popover_delay(cx: &gpui::TestAppContext) -> u64 {
cx.read(|cx: &App| -> u64 { EditorSettings::get_global(cx).hover_popover_delay.0 })
cx.read(|cx: &App| -> u64 { EditorSettings::get_global(cx).hover_popover_delay })
}
impl InfoPopover {

View File

@@ -21,9 +21,7 @@ use gpui::{
};
use open_path_prompt::OpenPathPrompt;
use picker::{Picker, PickerDelegate};
use project::{
PathMatchCandidateSet, Project, ProjectPath, WorktreeId, worktree_store::WorktreeStore,
};
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
use search::ToggleIncludeIgnored;
use settings::Settings;
use std::{
@@ -540,14 +538,11 @@ impl Matches {
fn push_new_matches<'a>(
&'a mut self,
worktree_store: Entity<WorktreeStore>,
cx: &'a App,
history_items: impl IntoIterator<Item = &'a FoundPath> + Clone,
currently_opened: Option<&'a FoundPath>,
query: Option<&FileSearchQuery>,
new_search_matches: impl Iterator<Item = ProjectPanelOrdMatch>,
extend_old_matches: bool,
path_style: PathStyle,
) {
let Some(query) = query else {
// assuming that if there's no query, then there's no search matches.
@@ -561,25 +556,8 @@ impl Matches {
.extend(history_items.into_iter().map(path_to_entry));
return;
};
// If several worktress are open we have to set the worktree root names in path prefix
let several_worktrees = worktree_store.read(cx).worktrees().count() > 1;
let worktree_name_by_id = several_worktrees.then(|| {
worktree_store
.read(cx)
.worktrees()
.map(|worktree| {
let snapshot = worktree.read(cx).snapshot();
(snapshot.id(), snapshot.root_name().into())
})
.collect()
});
let new_history_matches = matching_history_items(
history_items,
currently_opened,
worktree_name_by_id,
query,
path_style,
);
let new_history_matches = matching_history_items(history_items, currently_opened, query);
let new_search_matches: Vec<Match> = new_search_matches
.filter(|path_match| {
!new_history_matches.contains_key(&ProjectPath {
@@ -716,9 +694,7 @@ impl Matches {
fn matching_history_items<'a>(
history_items: impl IntoIterator<Item = &'a FoundPath>,
currently_opened: Option<&'a FoundPath>,
worktree_name_by_id: Option<HashMap<WorktreeId, Arc<RelPath>>>,
query: &FileSearchQuery,
path_style: PathStyle,
) -> HashMap<ProjectPath, Match> {
let mut candidates_paths = HashMap::default();
@@ -758,18 +734,13 @@ fn matching_history_items<'a>(
let mut matching_history_paths = HashMap::default();
for (worktree, candidates) in history_items_by_worktrees {
let max_results = candidates.len() + 1;
let worktree_root_name = worktree_name_by_id
.as_ref()
.and_then(|w| w.get(&worktree).cloned());
matching_history_paths.extend(
fuzzy::match_fixed_path_set(
candidates,
worktree.to_usize(),
worktree_root_name,
query.path_query(),
false,
max_results,
path_style,
)
.into_iter()
.filter_map(|path_match| {
@@ -966,18 +937,15 @@ impl FileFinderDelegate {
self.matches.get(self.selected_index).cloned()
};
let path_style = self.project.read(cx).path_style(cx);
self.matches.push_new_matches(
self.project.read(cx).worktree_store(),
cx,
&self.history_items,
self.currently_opened_path.as_ref(),
Some(&query),
matches.into_iter(),
extend_old_matches,
path_style,
);
let path_style = self.project.read(cx).path_style(cx);
let query_path = query.raw_query.as_str();
if let Ok(mut query_path) = RelPath::new(Path::new(query_path), path_style) {
let available_worktree = self
@@ -1397,11 +1365,7 @@ impl PickerDelegate for FileFinderDelegate {
separate_history: self.separate_history,
..Matches::default()
};
let path_style = self.project.read(cx).path_style(cx);
self.matches.push_new_matches(
project.worktree_store(),
cx,
self.history_items.iter().filter(|history_item| {
project
.worktree_for_id(history_item.project.worktree_id, cx)
@@ -1413,7 +1377,6 @@ impl PickerDelegate for FileFinderDelegate {
None,
None.into_iter(),
false,
path_style,
);
self.first_update = false;

View File

@@ -18,11 +18,7 @@ impl Settings for FileFinderSettings {
file_icons: file_finder.file_icons.unwrap(),
modal_max_width: file_finder.modal_max_width.unwrap().into(),
skip_focus_for_active_in_search: file_finder.skip_focus_for_active_in_search.unwrap(),
include_ignored: match file_finder.include_ignored.unwrap() {
settings::IncludeIgnoredContent::All => Some(true),
settings::IncludeIgnoredContent::Indexed => Some(false),
settings::IncludeIgnoredContent::Smart => None,
},
include_ignored: file_finder.include_ignored,
}
}
}

View File

@@ -2503,147 +2503,6 @@ async fn test_search_results_refreshed_on_adding_and_removing_worktrees(
});
}
#[gpui::test]
async fn test_history_items_uniqueness_for_multiple_worktree_open_all_files(
cx: &mut TestAppContext,
) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
path!("/repo1"),
json!({
"package.json": r#"{"name": "repo1"}"#,
"src": {
"index.js": "// Repo 1 index",
}
}),
)
.await;
app_state
.fs
.as_fake()
.insert_tree(
path!("/repo2"),
json!({
"package.json": r#"{"name": "repo2"}"#,
"src": {
"index.js": "// Repo 2 index",
}
}),
)
.await;
let project = Project::test(
app_state.fs.clone(),
[path!("/repo1").as_ref(), path!("/repo2").as_ref()],
cx,
)
.await;
let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx));
let (worktree_id1, worktree_id2) = cx.read(|cx| {
let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
(worktrees[0].read(cx).id(), worktrees[1].read(cx).id())
});
workspace
.update_in(cx, |workspace, window, cx| {
workspace.open_path(
ProjectPath {
worktree_id: worktree_id1,
path: rel_path("package.json").into(),
},
None,
true,
window,
cx,
)
})
.await
.unwrap();
cx.dispatch_action(workspace::CloseActiveItem {
save_intent: None,
close_pinned: false,
});
workspace
.update_in(cx, |workspace, window, cx| {
workspace.open_path(
ProjectPath {
worktree_id: worktree_id2,
path: rel_path("package.json").into(),
},
None,
true,
window,
cx,
)
})
.await
.unwrap();
cx.dispatch_action(workspace::CloseActiveItem {
save_intent: None,
close_pinned: false,
});
let picker = open_file_picker(&workspace, cx);
cx.simulate_input("package.json");
picker.update(cx, |finder, _| {
let matches = &finder.delegate.matches.matches;
assert_eq!(
matches.len(),
2,
"Expected 1 history match + 1 search matches, but got {} matches: {:?}",
matches.len(),
matches
);
assert_matches!(matches[0], Match::History { .. });
let search_matches = collect_search_matches(finder);
assert_eq!(
search_matches.history.len(),
2,
"Should have exactly 2 history match"
);
assert_eq!(
search_matches.search.len(),
0,
"Should have exactly 0 search match (because we already opened the 2 package.json)"
);
if let Match::History { path, panel_match } = &matches[0] {
assert_eq!(path.project.worktree_id, worktree_id2);
assert_eq!(path.project.path.as_ref(), rel_path("package.json"));
let panel_match = panel_match.as_ref().unwrap();
assert_eq!(panel_match.0.path_prefix, rel_path("repo2").into());
assert_eq!(panel_match.0.path, rel_path("package.json").into());
assert_eq!(
panel_match.0.positions,
vec![6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
);
}
if let Match::History { path, panel_match } = &matches[1] {
assert_eq!(path.project.worktree_id, worktree_id1);
assert_eq!(path.project.path.as_ref(), rel_path("package.json"));
let panel_match = panel_match.as_ref().unwrap();
assert_eq!(panel_match.0.path_prefix, rel_path("repo1").into());
assert_eq!(panel_match.0.path, rel_path("package.json").into());
assert_eq!(
panel_match.0.positions,
vec![6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
);
}
});
}
#[gpui::test]
async fn test_selected_match_stays_selected_after_matches_refreshed(cx: &mut gpui::TestAppContext) {
let app_state = init_test(cx);

View File

@@ -1,12 +0,0 @@
[package]
name = "fs_benchmarks"
version = "0.1.0"
publish.workspace = true
edition.workspace = true
[dependencies]
fs.workspace = true
gpui = {workspace = true, features = ["windows-manifest"]}
[lints]
workspace = true

View File

@@ -88,11 +88,9 @@ impl Ord for PathMatch {
pub fn match_fixed_path_set(
candidates: Vec<PathMatchCandidate>,
worktree_id: usize,
worktree_root_name: Option<Arc<RelPath>>,
query: &str,
smart_case: bool,
max_results: usize,
path_style: PathStyle,
) -> Vec<PathMatch> {
let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
let query = query.chars().collect::<Vec<_>>();
@@ -100,31 +98,10 @@ pub fn match_fixed_path_set(
let mut matcher = Matcher::new(&query, &lowercase_query, query_char_bag, smart_case, true);
let mut results = Vec::with_capacity(candidates.len());
let (path_prefix, path_prefix_chars, lowercase_prefix) = match worktree_root_name {
Some(worktree_root_name) => {
let mut path_prefix_chars = worktree_root_name
.display(path_style)
.chars()
.collect::<Vec<_>>();
path_prefix_chars.extend(path_style.separator().chars());
let lowercase_pfx = path_prefix_chars
.iter()
.map(|c| c.to_ascii_lowercase())
.collect::<Vec<_>>();
(worktree_root_name, path_prefix_chars, lowercase_pfx)
}
None => (
RelPath::empty().into(),
Default::default(),
Default::default(),
),
};
let mut results = Vec::new();
matcher.match_candidates(
&path_prefix_chars,
&lowercase_prefix,
&[],
&[],
candidates.into_iter(),
&mut results,
&AtomicBool::new(false),
@@ -134,7 +111,7 @@ pub fn match_fixed_path_set(
positions: positions.clone(),
is_dir: candidate.is_dir,
path: candidate.path.into(),
path_prefix: path_prefix.clone(),
path_prefix: RelPath::empty().into(),
distance_to_relative_ancestor: usize::MAX,
},
);

View File

@@ -693,11 +693,10 @@ impl GitRepository for RealGitRepository {
.args([
"--no-optional-locks",
"show",
"--format=",
"--format=%P",
"-z",
"--no-renames",
"--name-status",
"--first-parent",
])
.arg(&commit)
.stdin(Stdio::null())
@@ -708,8 +707,9 @@ impl GitRepository for RealGitRepository {
.context("starting git show process")?;
let show_stdout = String::from_utf8_lossy(&show_output.stdout);
let changes = parse_git_diff_name_status(&show_stdout);
let parent_sha = format!("{}^", commit);
let mut lines = show_stdout.split('\n');
let parent_sha = lines.next().unwrap().trim().trim_end_matches('\0');
let changes = parse_git_diff_name_status(lines.next().unwrap_or(""));
let mut cat_file_process = util::command::new_smol_command(&git_binary_path)
.current_dir(&working_directory)

View File

@@ -98,10 +98,25 @@ impl BlameRenderer for GitBlameRenderer {
let workspace = workspace.clone();
move |_, window, cx| {
CommitView::open(
blame_entry.sha.to_string(),
CommitSummary {
sha: blame_entry.sha.to_string().into(),
subject: blame_entry
.summary
.clone()
.unwrap_or_default()
.into(),
commit_timestamp: blame_entry
.committer_time
.unwrap_or_default(),
author_name: blame_entry
.committer_name
.clone()
.unwrap_or_default()
.into(),
has_parent: true,
},
repository.downgrade(),
workspace.clone(),
None,
window,
cx,
)
@@ -320,10 +335,9 @@ impl BlameRenderer for GitBlameRenderer {
.icon_size(IconSize::Small)
.on_click(move |_, window, cx| {
CommitView::open(
commit_summary.sha.clone().into(),
commit_summary.clone(),
repository.downgrade(),
workspace.clone(),
None,
window,
cx,
);
@@ -360,10 +374,15 @@ impl BlameRenderer for GitBlameRenderer {
cx: &mut App,
) {
CommitView::open(
blame_entry.sha.to_string(),
CommitSummary {
sha: blame_entry.sha.to_string().into(),
subject: blame_entry.summary.clone().unwrap_or_default().into(),
commit_timestamp: blame_entry.committer_time.unwrap_or_default(),
author_name: blame_entry.committer_name.unwrap_or_default().into(),
has_parent: true,
},
repository.downgrade(),
workspace,
None,
window,
cx,
)

View File

@@ -137,13 +137,13 @@ impl BranchList {
})
.await;
let _ = this.update_in(cx, |this, window, cx| {
this.update_in(cx, |this, window, cx| {
this.picker.update(cx, |picker, cx| {
picker.delegate.default_branch = default_branch;
picker.delegate.all_branches = Some(all_branches);
picker.refresh(window, cx);
})
});
})?;
anyhow::Ok(())
})
@@ -410,20 +410,37 @@ impl PickerDelegate for BranchListDelegate {
return;
}
let Some(repo) = self.repo.clone() else {
return;
};
cx.spawn_in(window, {
let branch = entry.branch.clone();
async move |picker, cx| {
let branch_change_task = picker.update(cx, |this, cx| {
let repo = this
.delegate
.repo
.as_ref()
.context("No active repository")?
.clone();
let branch = entry.branch.clone();
cx.spawn(async move |_, cx| {
repo.update(cx, |repo, _| repo.change_branch(branch.name().to_string()))?
.await??;
let mut cx = cx.to_async();
anyhow::Ok(())
anyhow::Ok(async move {
repo.update(&mut cx, |repo, _| {
repo.change_branch(branch.name().to_string())
})?
.await?
})
})??;
branch_change_task.await?;
picker.update(cx, |_, cx| {
cx.emit(DismissEvent);
anyhow::Ok(())
})
}
})
.detach_and_prompt_err("Failed to change branch", window, cx, |_, _, _| None);
cx.emit(DismissEvent);
}
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {

View File

@@ -318,10 +318,9 @@ impl Render for CommitTooltip {
.on_click(
move |_, window, cx| {
CommitView::open(
commit_summary.sha.to_string(),
commit_summary.clone(),
repo.downgrade(),
workspace.clone(),
None,
window,
cx,
);

View File

@@ -1,15 +1,14 @@
use anyhow::{Context as _, Result};
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
use editor::{Editor, EditorEvent, MultiBuffer, SelectionEffects, multibuffer_context_lines};
use git::repository::{CommitDetails, CommitDiff, RepoPath};
use git::repository::{CommitDetails, CommitDiff, CommitSummary, RepoPath};
use gpui::{
Action, AnyElement, AnyView, App, AppContext as _, AsyncApp, AsyncWindowContext, Context,
Entity, EventEmitter, FocusHandle, Focusable, IntoElement, PromptLevel, Render, WeakEntity,
Window, actions,
AnyElement, AnyView, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter,
FocusHandle, Focusable, IntoElement, Render, WeakEntity, Window,
};
use language::{
Anchor, Buffer, Capability, DiskState, File, LanguageRegistry, LineEnding, OffsetRangeExt as _,
Point, ReplicaId, Rope, TextBuffer,
Point, Rope, TextBuffer,
};
use multi_buffer::PathKey;
use project::{Project, WorktreeId, git_store::Repository};
@@ -19,42 +18,17 @@ use std::{
path::PathBuf,
sync::Arc,
};
use ui::{
Button, Color, Icon, IconName, Label, LabelCommon as _, SharedString, Tooltip, prelude::*,
};
use ui::{Color, Icon, IconName, Label, LabelCommon as _, SharedString};
use util::{ResultExt, paths::PathStyle, rel_path::RelPath, truncate_and_trailoff};
use workspace::{
Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
Workspace,
Item, ItemHandle as _, ItemNavHistory, ToolbarItemLocation, Workspace,
item::{BreadcrumbText, ItemEvent, TabContentParams},
notifications::NotifyTaskExt,
pane::SaveIntent,
searchable::SearchableItemHandle,
};
use crate::git_panel::GitPanel;
actions!(git, [ApplyCurrentStash, PopCurrentStash, DropCurrentStash,]);
pub fn init(cx: &mut App) {
cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
register_workspace_action(workspace, |toolbar, _: &ApplyCurrentStash, window, cx| {
toolbar.apply_stash(window, cx);
});
register_workspace_action(workspace, |toolbar, _: &DropCurrentStash, window, cx| {
toolbar.remove_stash(window, cx);
});
register_workspace_action(workspace, |toolbar, _: &PopCurrentStash, window, cx| {
toolbar.pop_stash(window, cx);
});
})
.detach();
}
pub struct CommitView {
commit: CommitDetails,
editor: Entity<Editor>,
stash: Option<usize>,
multibuffer: Entity<MultiBuffer>,
}
@@ -74,18 +48,17 @@ const FILE_NAMESPACE_SORT_PREFIX: u64 = 1;
impl CommitView {
pub fn open(
commit_sha: String,
commit: CommitSummary,
repo: WeakEntity<Repository>,
workspace: WeakEntity<Workspace>,
stash: Option<usize>,
window: &mut Window,
cx: &mut App,
) {
let commit_diff = repo
.update(cx, |repo, _| repo.load_commit_diff(commit_sha.clone()))
.update(cx, |repo, _| repo.load_commit_diff(commit.sha.to_string()))
.ok();
let commit_details = repo
.update(cx, |repo, _| repo.show(commit_sha.clone()))
.update(cx, |repo, _| repo.show(commit.sha.to_string()))
.ok();
window
@@ -104,7 +77,6 @@ impl CommitView {
commit_diff,
repo,
project.clone(),
stash,
window,
cx,
)
@@ -115,7 +87,7 @@ impl CommitView {
let ix = pane.items().position(|item| {
let commit_view = item.downcast::<CommitView>();
commit_view
.is_some_and(|view| view.read(cx).commit.sha == commit_sha)
.is_some_and(|view| view.read(cx).commit.sha == commit.sha)
});
if let Some(ix) = ix {
pane.activate_item(ix, true, true, window, cx);
@@ -134,7 +106,6 @@ impl CommitView {
commit_diff: CommitDiff,
repository: Entity<Repository>,
project: Entity<Project>,
stash: Option<usize>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
@@ -156,21 +127,18 @@ impl CommitView {
let mut metadata_buffer_id = None;
if let Some(worktree_id) = first_worktree_id {
let title = if let Some(stash) = stash {
format!("stash@{{{}}}", stash)
} else {
format!("commit {}", commit.sha)
};
let file = Arc::new(CommitMetadataFile {
title: RelPath::unix(&title).unwrap().into(),
title: RelPath::unix(&format!("commit {}", commit.sha))
.unwrap()
.into(),
worktree_id,
});
let buffer = cx.new(|cx| {
let buffer = TextBuffer::new_normalized(
ReplicaId::LOCAL,
0,
cx.entity_id().as_non_zero_u64().into(),
LineEnding::default(),
format_commit(&commit, stash.is_some()).into(),
format_commit(&commit).into(),
);
metadata_buffer_id = Some(buffer.remote_id());
Buffer::build(buffer, Some(file.clone()), Capability::ReadWrite)
@@ -243,7 +211,6 @@ impl CommitView {
commit,
editor,
multibuffer,
stash,
}
}
}
@@ -349,7 +316,7 @@ async fn build_buffer(
};
let buffer = cx.new(|cx| {
let buffer = TextBuffer::new_normalized(
ReplicaId::LOCAL,
0,
cx.entity_id().as_non_zero_u64().into(),
line_ending,
text,
@@ -402,13 +369,9 @@ async fn build_buffer_diff(
})
}
fn format_commit(commit: &CommitDetails, is_stash: bool) -> String {
fn format_commit(commit: &CommitDetails) -> String {
let mut result = String::new();
if is_stash {
writeln!(&mut result, "stash commit {}", commit.sha).unwrap();
} else {
writeln!(&mut result, "commit {}", commit.sha).unwrap();
}
writeln!(&mut result, "commit {}", commit.sha).unwrap();
writeln!(
&mut result,
"Author: {} <{}>",
@@ -575,296 +538,13 @@ impl Item for CommitView {
editor,
multibuffer,
commit: self.commit.clone(),
stash: self.stash,
}
}))
}
}
impl Render for CommitView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let is_stash = self.stash.is_some();
div()
.key_context(if is_stash { "StashDiff" } else { "CommitDiff" })
.bg(cx.theme().colors().editor_background)
.flex()
.items_center()
.justify_center()
.size_full()
.child(self.editor.clone())
}
}
pub struct CommitViewToolbar {
commit_view: Option<WeakEntity<CommitView>>,
workspace: WeakEntity<Workspace>,
}
impl CommitViewToolbar {
pub fn new(workspace: &Workspace, _: &mut Context<Self>) -> Self {
Self {
commit_view: None,
workspace: workspace.weak_handle(),
}
}
fn commit_view(&self, _: &App) -> Option<Entity<CommitView>> {
self.commit_view.as_ref()?.upgrade()
}
async fn close_commit_view(
commit_view: Entity<CommitView>,
workspace: WeakEntity<Workspace>,
cx: &mut AsyncWindowContext,
) -> anyhow::Result<()> {
workspace
.update_in(cx, |workspace, window, cx| {
let active_pane = workspace.active_pane();
let commit_view_id = commit_view.entity_id();
active_pane.update(cx, |pane, cx| {
pane.close_item_by_id(commit_view_id, SaveIntent::Skip, window, cx)
})
})?
.await?;
anyhow::Ok(())
}
fn apply_stash(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.stash_action(
"Apply",
window,
cx,
async move |repository, sha, stash, commit_view, workspace, cx| {
let result = repository.update(cx, |repo, cx| {
if !stash_matches_index(&sha, stash, repo) {
return Err(anyhow::anyhow!("Stash has changed, not applying"));
}
Ok(repo.stash_apply(Some(stash), cx))
})?;
match result {
Ok(task) => task.await?,
Err(err) => {
Self::close_commit_view(commit_view, workspace, cx).await?;
return Err(err);
}
};
Self::close_commit_view(commit_view, workspace, cx).await?;
anyhow::Ok(())
},
);
}
fn pop_stash(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.stash_action(
"Pop",
window,
cx,
async move |repository, sha, stash, commit_view, workspace, cx| {
let result = repository.update(cx, |repo, cx| {
if !stash_matches_index(&sha, stash, repo) {
return Err(anyhow::anyhow!("Stash has changed, pop aborted"));
}
Ok(repo.stash_pop(Some(stash), cx))
})?;
match result {
Ok(task) => task.await?,
Err(err) => {
Self::close_commit_view(commit_view, workspace, cx).await?;
return Err(err);
}
};
Self::close_commit_view(commit_view, workspace, cx).await?;
anyhow::Ok(())
},
);
}
fn remove_stash(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.stash_action(
"Drop",
window,
cx,
async move |repository, sha, stash, commit_view, workspace, cx| {
let result = repository.update(cx, |repo, cx| {
if !stash_matches_index(&sha, stash, repo) {
return Err(anyhow::anyhow!("Stash has changed, drop aborted"));
}
Ok(repo.stash_drop(Some(stash), cx))
})?;
match result {
Ok(task) => task.await??,
Err(err) => {
Self::close_commit_view(commit_view, workspace, cx).await?;
return Err(err);
}
};
Self::close_commit_view(commit_view, workspace, cx).await?;
anyhow::Ok(())
},
);
}
fn stash_action<AsyncFn>(
&mut self,
str_action: &str,
window: &mut Window,
cx: &mut Context<Self>,
callback: AsyncFn,
) where
AsyncFn: AsyncFnOnce(
Entity<Repository>,
&SharedString,
usize,
Entity<CommitView>,
WeakEntity<Workspace>,
&mut AsyncWindowContext,
) -> anyhow::Result<()>
+ 'static,
{
let Some(commit_view) = self.commit_view(cx) else {
return;
};
let Some(stash) = commit_view.read(cx).stash else {
return;
};
let sha = commit_view.read(cx).commit.sha.clone();
let answer = window.prompt(
PromptLevel::Info,
&format!("{} stash@{{{}}}?", str_action, stash),
None,
&[str_action, "Cancel"],
cx,
);
let workspace = self.workspace.clone();
cx.spawn_in(window, async move |_, cx| {
if answer.await != Ok(0) {
return anyhow::Ok(());
}
let repo = workspace.update(cx, |workspace, cx| {
workspace
.panel::<GitPanel>(cx)
.and_then(|p| p.read(cx).active_repository.clone())
})?;
let Some(repo) = repo else {
return Ok(());
};
callback(repo, &sha, stash, commit_view, workspace, cx).await?;
anyhow::Ok(())
})
.detach_and_notify_err(window, cx);
}
}
impl EventEmitter<ToolbarItemEvent> for CommitViewToolbar {}
impl ToolbarItemView for CommitViewToolbar {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
_: &mut Window,
cx: &mut Context<Self>,
) -> ToolbarItemLocation {
if let Some(entity) = active_pane_item.and_then(|i| i.act_as::<CommitView>(cx))
&& entity.read(cx).stash.is_some()
{
self.commit_view = Some(entity.downgrade());
return ToolbarItemLocation::PrimaryRight;
}
ToolbarItemLocation::Hidden
}
fn pane_focus_update(
&mut self,
_pane_focused: bool,
_window: &mut Window,
_cx: &mut Context<Self>,
) {
}
}
impl Render for CommitViewToolbar {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let Some(commit_view) = self.commit_view(cx) else {
return div();
};
let is_stash = commit_view.read(cx).stash.is_some();
if !is_stash {
return div();
}
let focus_handle = commit_view.focus_handle(cx);
h_group_xl().my_neg_1().py_1().items_center().child(
h_group_sm()
.child(
Button::new("apply-stash", "Apply")
.tooltip(Tooltip::for_action_title_in(
"Apply current stash",
&ApplyCurrentStash,
&focus_handle,
))
.on_click(cx.listener(|this, _, window, cx| this.apply_stash(window, cx))),
)
.child(
Button::new("pop-stash", "Pop")
.tooltip(Tooltip::for_action_title_in(
"Pop current stash",
&PopCurrentStash,
&focus_handle,
))
.on_click(cx.listener(|this, _, window, cx| this.pop_stash(window, cx))),
)
.child(
Button::new("remove-stash", "Remove")
.icon(IconName::Trash)
.tooltip(Tooltip::for_action_title_in(
"Remove current stash",
&DropCurrentStash,
&focus_handle,
))
.on_click(cx.listener(|this, _, window, cx| this.remove_stash(window, cx))),
),
)
}
}
fn register_workspace_action<A: Action>(
workspace: &mut Workspace,
callback: fn(&mut CommitViewToolbar, &A, &mut Window, &mut Context<CommitViewToolbar>),
) {
workspace.register_action(move |workspace, action: &A, window, cx| {
if workspace.has_active_modal(window, cx) {
cx.propagate();
return;
}
workspace.active_pane().update(cx, |pane, cx| {
pane.toolbar().update(cx, move |workspace, cx| {
if let Some(toolbar) = workspace.item_of_type::<CommitViewToolbar>() {
toolbar.update(cx, move |toolbar, cx| {
callback(toolbar, action, window, cx);
cx.notify();
});
}
});
})
});
}
fn stash_matches_index(sha: &str, index: usize, repo: &mut Repository) -> bool {
match repo
.cached_stash()
.entries
.iter()
.find(|entry| entry.index == index)
{
Some(entry) => entry.oid.to_string() == sha,
None => false,
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
self.editor.clone()
}
}

View File

@@ -3611,10 +3611,9 @@ impl GitPanel {
let repo = active_repository.downgrade();
move |_, window, cx| {
CommitView::open(
commit.sha.to_string(),
commit.clone(),
repo.clone(),
workspace.clone(),
None,
window,
cx,
);
@@ -4420,10 +4419,6 @@ impl Panel for GitPanel {
"GitPanel"
}
fn panel_key() -> &'static str {
GIT_PANEL_KEY
}
fn position(&self, _: &Window, cx: &App) -> DockPosition {
GitPanelSettings::get_global(cx).dock
}

View File

@@ -34,7 +34,7 @@ mod askpass_modal;
pub mod branch_picker;
mod commit_modal;
pub mod commit_tooltip;
pub mod commit_view;
mod commit_view;
mod conflict_view;
pub mod file_diff_view;
pub mod git_panel;
@@ -59,7 +59,6 @@ pub fn init(cx: &mut App) {
GitPanelSettings::register(cx);
editor::set_blame_renderer(blame_ui::GitBlameRenderer, cx);
commit_view::init(cx);
cx.observe_new(|editor: &mut Editor, _, cx| {
conflict_view::register_editor(editor, editor.buffer().clone(), cx);

View File

@@ -5,21 +5,18 @@ use git::stash::StashEntry;
use gpui::{
Action, AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, Render,
SharedString, Styled, Subscription, Task, WeakEntity, Window, actions, rems, svg,
SharedString, Styled, Subscription, Task, Window, actions, rems,
};
use picker::{Picker, PickerDelegate};
use project::git_store::{Repository, RepositoryEvent};
use std::sync::Arc;
use time::{OffsetDateTime, UtcOffset};
use time_format;
use ui::{
ButtonLike, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*,
};
use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, Tooltip, prelude::*};
use util::ResultExt;
use workspace::notifications::DetachAndPromptErr;
use workspace::{ModalView, Workspace};
use crate::commit_view::CommitView;
use crate::stash_picker;
actions!(
@@ -27,8 +24,6 @@ actions!(
[
/// Drop the selected stash entry.
DropStashItem,
/// Show the diff view of the selected stash entry.
ShowStashItem,
]
);
@@ -43,9 +38,8 @@ pub fn open(
cx: &mut Context<Workspace>,
) {
let repository = workspace.project().read(cx).active_repository(cx);
let weak_workspace = workspace.weak_handle();
workspace.toggle_modal(window, cx, |window, cx| {
StashList::new(repository, weak_workspace, rems(34.), window, cx)
StashList::new(repository, rems(34.), window, cx)
})
}
@@ -59,7 +53,6 @@ pub struct StashList {
impl StashList {
fn new(
repository: Option<Entity<Repository>>,
workspace: WeakEntity<Workspace>,
width: Rems,
window: &mut Window,
cx: &mut Context<Self>,
@@ -105,7 +98,7 @@ impl StashList {
})
.detach_and_log_err(cx);
let delegate = StashListDelegate::new(repository, workspace, window, cx);
let delegate = StashListDelegate::new(repository, window, cx);
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
let picker_focus_handle = picker.focus_handle(cx);
picker.update(cx, |picker, _| {
@@ -138,20 +131,6 @@ impl StashList {
cx.notify();
}
fn handle_show_stash(
&mut self,
_: &ShowStashItem,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.picker.update(cx, |picker, cx| {
picker
.delegate
.show_stash_at(picker.delegate.selected_index(), window, cx);
});
cx.notify();
}
fn handle_modifiers_changed(
&mut self,
ev: &ModifiersChangedEvent,
@@ -178,7 +157,6 @@ impl Render for StashList {
.w(self.width)
.on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
.on_action(cx.listener(Self::handle_drop_stash))
.on_action(cx.listener(Self::handle_show_stash))
.child(self.picker.clone())
}
}
@@ -194,7 +172,6 @@ pub struct StashListDelegate {
matches: Vec<StashEntryMatch>,
all_stash_entries: Option<Vec<StashEntry>>,
repo: Option<Entity<Repository>>,
workspace: WeakEntity<Workspace>,
selected_index: usize,
last_query: String,
modifiers: Modifiers,
@@ -205,7 +182,6 @@ pub struct StashListDelegate {
impl StashListDelegate {
fn new(
repo: Option<Entity<Repository>>,
workspace: WeakEntity<Workspace>,
_window: &mut Window,
cx: &mut Context<StashList>,
) -> Self {
@@ -216,7 +192,6 @@ impl StashListDelegate {
Self {
matches: vec![],
repo,
workspace,
all_stash_entries: None,
selected_index: 0,
last_query: Default::default(),
@@ -260,25 +235,6 @@ impl StashListDelegate {
});
}
fn show_stash_at(&self, ix: usize, window: &mut Window, cx: &mut Context<Picker<Self>>) {
let Some(entry_match) = self.matches.get(ix) else {
return;
};
let stash_sha = entry_match.entry.oid.to_string();
let stash_index = entry_match.entry.index;
let Some(repo) = self.repo.clone() else {
return;
};
CommitView::open(
stash_sha,
repo.downgrade(),
self.workspace.clone(),
Some(stash_index),
window,
cx,
);
}
fn pop_stash(&self, stash_index: usize, window: &mut Window, cx: &mut Context<Picker<Self>>) {
let Some(repo) = self.repo.clone() else {
return;
@@ -434,7 +390,7 @@ impl PickerDelegate for StashListDelegate {
ix: usize,
selected: bool,
_window: &mut Window,
cx: &mut Context<Picker<Self>>,
_cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let entry_match = &self.matches[ix];
@@ -476,35 +432,11 @@ impl PickerDelegate for StashListDelegate {
.size(LabelSize::Small),
);
let show_button = div()
.group("show-button-hover")
.child(
ButtonLike::new("show-button")
.child(
svg()
.size(IconSize::Medium.rems())
.flex_none()
.path(IconName::Eye.path())
.text_color(Color::Default.color(cx))
.group_hover("show-button-hover", |this| {
this.text_color(Color::Accent.color(cx))
})
.hover(|this| this.text_color(Color::Accent.color(cx))),
)
.tooltip(Tooltip::for_action_title("Show Stash", &ShowStashItem))
.on_click(cx.listener(move |picker, _, window, cx| {
cx.stop_propagation();
picker.delegate.show_stash_at(ix, window, cx);
})),
)
.into_any_element();
Some(
ListItem::new(SharedString::from(format!("stash-{ix}")))
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.end_slot(show_button)
.child(
v_flex()
.w_full()

View File

@@ -553,7 +553,7 @@ pub struct App {
pub(crate) entities: EntityMap,
pub(crate) window_update_stack: Vec<WindowId>,
pub(crate) new_entity_observers: SubscriberSet<TypeId, NewEntityListener>,
pub(crate) windows: SlotMap<WindowId, Option<Box<Window>>>,
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
pub(crate) window_handles: FxHashMap<WindowId, AnyWindowHandle>,
pub(crate) focus_handles: Arc<FocusMap>,
pub(crate) keymap: Rc<RefCell<Keymap>>,
@@ -964,7 +964,7 @@ impl App {
clear.clear();
cx.window_handles.insert(id, window.handle);
cx.windows.get_mut(id).unwrap().replace(Box::new(window));
cx.windows.get_mut(id).unwrap().replace(window);
Ok(handle)
}
Err(e) => {
@@ -1239,7 +1239,7 @@ impl App {
.windows
.values()
.filter_map(|window| {
let window = window.as_deref()?;
let window = window.as_ref()?;
window.invalidator.is_dirty().then_some(window.handle)
})
.collect::<Vec<_>>()
@@ -1320,7 +1320,7 @@ impl App {
fn apply_refresh_effect(&mut self) {
for window in self.windows.values_mut() {
if let Some(window) = window.as_deref_mut() {
if let Some(window) = window.as_mut() {
window.refreshing = true;
window.invalidator.set_dirty(true);
}
@@ -2199,7 +2199,7 @@ impl AppContext for App {
.windows
.get(window.id)
.context("window not found")?
.as_deref()
.as_ref()
.expect("attempted to read a window that is already on the stack");
let root_view = window.root.clone().unwrap();

View File

@@ -455,7 +455,7 @@ impl TestAppContext {
.windows
.get_mut(window.id)
.unwrap()
.as_deref_mut()
.as_mut()
.unwrap()
.platform_window
.as_test()

View File

@@ -509,11 +509,10 @@ impl StateInner {
if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max {
self.logical_scroll_top = None;
} else {
let (start, ..) =
self.items
.find::<ListItemSummary, _>((), &Height(new_scroll_top), Bias::Right);
let item_ix = start.count;
let offset_in_item = new_scroll_top - start.height;
let mut cursor = self.items.cursor::<ListItemSummary>(());
cursor.seek(&Height(new_scroll_top), Bias::Right);
let item_ix = cursor.start().count;
let offset_in_item = new_scroll_top - cursor.start().height;
self.logical_scroll_top = Some(ListOffset {
item_ix,
offset_in_item,
@@ -551,12 +550,9 @@ impl StateInner {
}
fn scroll_top(&self, logical_scroll_top: &ListOffset) -> Pixels {
let (start, ..) = self.items.find::<ListItemSummary, _>(
(),
&Count(logical_scroll_top.item_ix),
Bias::Right,
);
start.height + logical_scroll_top.offset_in_item
let mut cursor = self.items.cursor::<ListItemSummary>(());
cursor.seek(&Count(logical_scroll_top.item_ix), Bias::Right);
cursor.start().height + logical_scroll_top.offset_in_item
}
fn layout_all_items(
@@ -886,12 +882,11 @@ impl StateInner {
if self.alignment == ListAlignment::Bottom && new_scroll_top == scroll_max {
self.logical_scroll_top = None;
} else {
let (start, _, _) =
self.items
.find::<ListItemSummary, _>((), &Height(new_scroll_top), Bias::Right);
let mut cursor = self.items.cursor::<ListItemSummary>(());
cursor.seek(&Height(new_scroll_top), Bias::Right);
let item_ix = start.count;
let offset_in_item = new_scroll_top - start.height;
let item_ix = cursor.start().count;
let offset_in_item = new_scroll_top - cursor.start().height;
self.logical_scroll_top = Some(ListOffset {
item_ix,
offset_in_item,

View File

@@ -364,7 +364,17 @@ impl Element for UniformList {
content_size,
window,
cx,
|_style, mut scroll_offset, hitbox, window, cx| {
|style, mut scroll_offset, hitbox, window, cx| {
let border = style.border_widths.to_pixels(window.rem_size());
let padding = style
.padding
.to_pixels(bounds.size.into(), window.rem_size());
let padded_bounds = Bounds::from_corners(
bounds.origin + point(border.left + padding.left, border.top),
bounds.bottom_right() - point(border.right + padding.right, border.bottom),
);
let y_flipped = if let Some(scroll_handle) = &self.scroll_handle {
let scroll_state = scroll_handle.0.borrow();
scroll_state.y_flipped

View File

@@ -289,13 +289,10 @@ pub trait PlatformDisplay: Send + Sync + Debug {
/// Get the default bounds for this display to place a window
fn default_bounds(&self) -> Bounds<Pixels> {
let bounds = self.bounds();
let center = bounds.center();
let clipped_window_size = DEFAULT_WINDOW_SIZE.min(&bounds.size);
let offset = clipped_window_size / 2.0;
let center = self.bounds().center();
let offset = DEFAULT_WINDOW_SIZE / 2.0;
let origin = point(center.x - offset.width, center.y - offset.height);
Bounds::new(origin, clipped_window_size)
Bounds::new(origin, DEFAULT_WINDOW_SIZE)
}
}

View File

@@ -951,30 +951,17 @@ fn file_save_dialog(
) -> Result<Option<PathBuf>> {
let dialog: IFileSaveDialog = unsafe { CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)? };
if !directory.to_string_lossy().is_empty()
&& let Some(full_path) = directory
.canonicalize()
.context("failed to canonicalize directory")
.log_err()
&& let Some(full_path) = directory.canonicalize().log_err()
{
let full_path = SanitizedPath::new(&full_path);
let full_path_string = full_path.to_string();
let path_item: IShellItem =
unsafe { SHCreateItemFromParsingName(&HSTRING::from(full_path_string), None)? };
unsafe {
dialog
.SetFolder(&path_item)
.context("failed to set dialog folder")
.log_err()
};
unsafe { dialog.SetFolder(&path_item).log_err() };
}
if let Some(suggested_name) = suggested_name {
unsafe {
dialog
.SetFileName(&HSTRING::from(suggested_name))
.context("failed to set file name")
.log_err()
};
unsafe { dialog.SetFileName(&HSTRING::from(suggested_name)).log_err() };
}
unsafe {

View File

@@ -169,9 +169,7 @@ impl WindowsWindowState {
length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
..Default::default()
};
GetWindowPlacement(self.hwnd, &mut placement)
.context("failed to get window placement")
.log_err();
GetWindowPlacement(self.hwnd, &mut placement).log_err();
placement
};
(
@@ -256,9 +254,7 @@ impl WindowsWindowInner {
lock.fullscreen_restore_bounds = window_bounds;
let style = WINDOW_STYLE(unsafe { get_window_long(this.hwnd, GWL_STYLE) } as _);
let mut rc = RECT::default();
unsafe { GetWindowRect(this.hwnd, &mut rc) }
.context("failed to get window rect")
.log_err();
unsafe { GetWindowRect(this.hwnd, &mut rc) }.log_err();
let _ = lock.fullscreen.insert(StyleAndBounds {
style,
x: rc.left,
@@ -305,20 +301,15 @@ impl WindowsWindowInner {
};
match open_status.state {
WindowOpenState::Maximized => unsafe {
SetWindowPlacement(self.hwnd, &open_status.placement)
.context("failed to set window placement")?;
SetWindowPlacement(self.hwnd, &open_status.placement)?;
ShowWindowAsync(self.hwnd, SW_MAXIMIZE).ok()?;
},
WindowOpenState::Fullscreen => {
unsafe {
SetWindowPlacement(self.hwnd, &open_status.placement)
.context("failed to set window placement")?
};
unsafe { SetWindowPlacement(self.hwnd, &open_status.placement)? };
self.toggle_fullscreen();
}
WindowOpenState::Windowed => unsafe {
SetWindowPlacement(self.hwnd, &open_status.placement)
.context("failed to set window placement")?;
SetWindowPlacement(self.hwnd, &open_status.placement)?;
},
}
Ok(())

View File

@@ -4313,14 +4313,14 @@ impl Window {
}
/// Returns a generic handler that invokes the given handler with the view and context associated with the given view handle.
pub fn handler_for<E: 'static, Callback: Fn(&mut E, &mut Window, &mut Context<E>) + 'static>(
pub fn handler_for<V: Render, Callback: Fn(&mut V, &mut Window, &mut Context<V>) + 'static>(
&self,
entity: &Entity<E>,
view: &Entity<V>,
f: Callback,
) -> impl Fn(&mut Window, &mut App) + 'static {
let entity = entity.downgrade();
) -> impl Fn(&mut Window, &mut App) + use<V, Callback> {
let view = view.downgrade();
move |window: &mut Window, cx: &mut App| {
entity.update(cx, |entity, cx| f(entity, window, cx)).ok();
view.update(cx, |view, cx| f(view, window, cx)).ok();
}
}
@@ -4718,7 +4718,7 @@ impl<V: 'static + Render> WindowHandle<V> {
.get(self.id)
.and_then(|window| {
window
.as_deref()
.as_ref()
.and_then(|window| window.root.clone())
.map(|root_view| root_view.downcast::<V>())
})

View File

@@ -18,8 +18,8 @@ pub use crate::{
proto,
};
use anyhow::{Context as _, Result};
use clock::Lamport;
pub use clock::ReplicaId;
use clock::{AGENT_REPLICA_ID, Lamport};
use collections::HashMap;
use fs::MTime;
use futures::channel::oneshot;
@@ -828,11 +828,7 @@ impl Buffer {
/// Create a new buffer with the given base text.
pub fn local<T: Into<String>>(base_text: T, cx: &Context<Self>) -> Self {
Self::build(
TextBuffer::new(
ReplicaId::LOCAL,
cx.entity_id().as_non_zero_u64().into(),
base_text.into(),
),
TextBuffer::new(0, cx.entity_id().as_non_zero_u64().into(), base_text.into()),
None,
Capability::ReadWrite,
)
@@ -846,7 +842,7 @@ impl Buffer {
) -> Self {
Self::build(
TextBuffer::new_normalized(
ReplicaId::LOCAL,
0,
cx.entity_id().as_non_zero_u64().into(),
line_ending,
base_text_normalized,
@@ -995,10 +991,10 @@ impl Buffer {
language: None,
remote_selections: Default::default(),
diagnostics: Default::default(),
diagnostics_timestamp: Lamport::MIN,
diagnostics_timestamp: Default::default(),
completion_triggers: Default::default(),
completion_triggers_per_language_server: Default::default(),
completion_triggers_timestamp: Lamport::MIN,
completion_triggers_timestamp: Default::default(),
deferred_ops: OperationQueue::new(),
has_conflict: false,
change_bits: Default::default(),
@@ -1016,8 +1012,7 @@ impl Buffer {
let buffer_id = entity_id.as_non_zero_u64().into();
async move {
let text =
TextBuffer::new_normalized(ReplicaId::LOCAL, buffer_id, Default::default(), text)
.snapshot();
TextBuffer::new_normalized(0, buffer_id, Default::default(), text).snapshot();
let mut syntax = SyntaxMap::new(&text).snapshot();
if let Some(language) = language.clone() {
let language_registry = language_registry.clone();
@@ -1038,13 +1033,8 @@ impl Buffer {
pub fn build_empty_snapshot(cx: &mut App) -> BufferSnapshot {
let entity_id = cx.reserve_entity::<Self>().entity_id();
let buffer_id = entity_id.as_non_zero_u64().into();
let text = TextBuffer::new_normalized(
ReplicaId::LOCAL,
buffer_id,
Default::default(),
Rope::new(),
)
.snapshot();
let text =
TextBuffer::new_normalized(0, buffer_id, Default::default(), Rope::new()).snapshot();
let syntax = SyntaxMap::new(&text).snapshot();
BufferSnapshot {
text,
@@ -1066,9 +1056,7 @@ impl Buffer {
) -> BufferSnapshot {
let entity_id = cx.reserve_entity::<Self>().entity_id();
let buffer_id = entity_id.as_non_zero_u64().into();
let text =
TextBuffer::new_normalized(ReplicaId::LOCAL, buffer_id, Default::default(), text)
.snapshot();
let text = TextBuffer::new_normalized(0, buffer_id, Default::default(), text).snapshot();
let mut syntax = SyntaxMap::new(&text).snapshot();
if let Some(language) = language.clone() {
syntax.reparse(&text, language_registry, language);
@@ -2272,7 +2260,7 @@ impl Buffer {
) {
let lamport_timestamp = self.text.lamport_clock.tick();
self.remote_selections.insert(
ReplicaId::AGENT,
AGENT_REPLICA_ID,
SelectionSet {
selections,
lamport_timestamp,
@@ -2929,7 +2917,7 @@ impl Buffer {
edits.push((range, new_text));
}
log::info!("mutating buffer {:?} with {:?}", self.replica_id(), edits);
log::info!("mutating buffer {} with {:?}", self.replica_id(), edits);
self.edit(edits, None, cx);
}

View File

@@ -70,13 +70,7 @@ fn test_line_endings(cx: &mut gpui::App) {
fn test_set_line_ending(cx: &mut TestAppContext) {
let base = cx.new(|cx| Buffer::local("one\ntwo\nthree\n", cx));
let base_replica = cx.new(|cx| {
Buffer::from_proto(
ReplicaId::new(1),
Capability::ReadWrite,
base.read(cx).to_proto(cx),
None,
)
.unwrap()
Buffer::from_proto(1, Capability::ReadWrite, base.read(cx).to_proto(cx), None).unwrap()
});
base.update(cx, |_buffer, cx| {
cx.subscribe(&base_replica, |this, _, event, cx| {
@@ -403,7 +397,7 @@ fn test_edit_events(cx: &mut gpui::App) {
let buffer2 = cx.new(|cx| {
Buffer::remote(
BufferId::from(cx.entity_id().as_non_zero_u64()),
ReplicaId::new(1),
1,
Capability::ReadWrite,
"abcdef",
)
@@ -2781,8 +2775,7 @@ fn test_serialization(cx: &mut gpui::App) {
.background_executor()
.block(buffer1.read(cx).serialize_ops(None, cx));
let buffer2 = cx.new(|cx| {
let mut buffer =
Buffer::from_proto(ReplicaId::new(1), Capability::ReadWrite, state, None).unwrap();
let mut buffer = Buffer::from_proto(1, Capability::ReadWrite, state, None).unwrap();
buffer.apply_ops(
ops.into_iter()
.map(|op| proto::deserialize_operation(op).unwrap()),
@@ -2801,13 +2794,7 @@ fn test_branch_and_merge(cx: &mut TestAppContext) {
// Create a remote replica of the base buffer.
let base_replica = cx.new(|cx| {
Buffer::from_proto(
ReplicaId::new(1),
Capability::ReadWrite,
base.read(cx).to_proto(cx),
None,
)
.unwrap()
Buffer::from_proto(1, Capability::ReadWrite, base.read(cx).to_proto(cx), None).unwrap()
});
base.update(cx, |_buffer, cx| {
cx.subscribe(&base_replica, |this, _, event, cx| {
@@ -3121,8 +3108,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
.background_executor()
.block(base_buffer.read(cx).serialize_ops(None, cx));
let mut buffer =
Buffer::from_proto(ReplicaId::new(i as u16), Capability::ReadWrite, state, None)
.unwrap();
Buffer::from_proto(i as ReplicaId, Capability::ReadWrite, state, None).unwrap();
buffer.apply_ops(
ops.into_iter()
.map(|op| proto::deserialize_operation(op).unwrap()),
@@ -3147,9 +3133,9 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
});
buffers.push(buffer);
replica_ids.push(ReplicaId::new(i as u16));
network.lock().add_peer(ReplicaId::new(i as u16));
log::info!("Adding initial peer with replica id {:?}", replica_ids[i]);
replica_ids.push(i as ReplicaId);
network.lock().add_peer(i as ReplicaId);
log::info!("Adding initial peer with replica id {}", i);
}
log::info!("initial text: {:?}", base_text);
@@ -3169,14 +3155,14 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
buffer.start_transaction_at(now);
buffer.randomly_edit(&mut rng, 5, cx);
buffer.end_transaction_at(now, cx);
log::info!("buffer {:?} text: {:?}", buffer.replica_id(), buffer.text());
log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
});
mutation_count -= 1;
}
30..=39 if mutation_count != 0 => {
buffer.update(cx, |buffer, cx| {
if rng.random_bool(0.2) {
log::info!("peer {:?} clearing active selections", replica_id);
log::info!("peer {} clearing active selections", replica_id);
active_selections.remove(&replica_id);
buffer.remove_active_selections(cx);
} else {
@@ -3193,7 +3179,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
}
let selections: Arc<[Selection<Anchor>]> = selections.into();
log::info!(
"peer {:?} setting active selections: {:?}",
"peer {} setting active selections: {:?}",
replica_id,
selections
);
@@ -3203,7 +3189,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
});
mutation_count -= 1;
}
40..=49 if mutation_count != 0 && replica_id == ReplicaId::REMOTE_SERVER => {
40..=49 if mutation_count != 0 && replica_id == 0 => {
let entry_count = rng.random_range(1..=5);
buffer.update(cx, |buffer, cx| {
let diagnostics = DiagnosticSet::new(
@@ -3221,11 +3207,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
}),
buffer,
);
log::info!(
"peer {:?} setting diagnostics: {:?}",
replica_id,
diagnostics
);
log::info!("peer {} setting diagnostics: {:?}", replica_id, diagnostics);
buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
});
mutation_count -= 1;
@@ -3235,13 +3217,12 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
let old_buffer_ops = cx
.background_executor()
.block(buffer.read(cx).serialize_ops(None, cx));
let new_replica_id = (0..=replica_ids.len() as u16)
.map(ReplicaId::new)
let new_replica_id = (0..=replica_ids.len() as ReplicaId)
.filter(|replica_id| *replica_id != buffer.read(cx).replica_id())
.choose(&mut rng)
.unwrap();
log::info!(
"Adding new replica {:?} (replicating from {:?})",
"Adding new replica {} (replicating from {})",
new_replica_id,
replica_id
);
@@ -3260,7 +3241,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
cx,
);
log::info!(
"New replica {:?} text: {:?}",
"New replica {} text: {:?}",
new_buffer.replica_id(),
new_buffer.text()
);
@@ -3283,7 +3264,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
}));
network.lock().replicate(replica_id, new_replica_id);
if new_replica_id.as_u16() as usize == replica_ids.len() {
if new_replica_id as usize == replica_ids.len() {
replica_ids.push(new_replica_id);
} else {
let new_buffer = new_buffer.take().unwrap();
@@ -3295,7 +3276,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
.map(|op| proto::deserialize_operation(op).unwrap());
if ops.len() > 0 {
log::info!(
"peer {:?} (version: {:?}) applying {} ops from the network. {:?}",
"peer {} (version: {:?}) applying {} ops from the network. {:?}",
new_replica_id,
buffer.read(cx).version(),
ops.len(),
@@ -3306,13 +3287,13 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
});
}
}
buffers[new_replica_id.as_u16() as usize] = new_buffer;
buffers[new_replica_id as usize] = new_buffer;
}
}
60..=69 if mutation_count != 0 => {
buffer.update(cx, |buffer, cx| {
buffer.randomly_undo_redo(&mut rng, cx);
log::info!("buffer {:?} text: {:?}", buffer.replica_id(), buffer.text());
log::info!("buffer {} text: {:?}", buffer.replica_id(), buffer.text());
});
mutation_count -= 1;
}
@@ -3324,7 +3305,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
.map(|op| proto::deserialize_operation(op).unwrap());
if ops.len() > 0 {
log::info!(
"peer {:?} (version: {:?}) applying {} ops from the network. {:?}",
"peer {} (version: {:?}) applying {} ops from the network. {:?}",
replica_id,
buffer.read(cx).version(),
ops.len(),
@@ -3354,13 +3335,13 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
assert_eq!(
buffer.version(),
first_buffer.version(),
"Replica {:?} version != Replica 0 version",
"Replica {} version != Replica 0 version",
buffer.replica_id()
);
assert_eq!(
buffer.text(),
first_buffer.text(),
"Replica {:?} text != Replica 0 text",
"Replica {} text != Replica 0 text",
buffer.replica_id()
);
assert_eq!(
@@ -3370,7 +3351,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
first_buffer
.diagnostics_in_range::<_, usize>(0..first_buffer.len(), false)
.collect::<Vec<_>>(),
"Replica {:?} diagnostics != Replica 0 diagnostics",
"Replica {} diagnostics != Replica 0 diagnostics",
buffer.replica_id()
);
}
@@ -3389,7 +3370,7 @@ fn test_random_collaboration(cx: &mut App, mut rng: StdRng) {
assert_eq!(
actual_remote_selections,
expected_remote_selections,
"Replica {:?} remote selections != expected selections",
"Replica {} remote selections != expected selections",
buffer.replica_id()
);
}

View File

@@ -39,14 +39,14 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
crate::Operation::Buffer(text::Operation::Undo(undo)) => {
proto::operation::Variant::Undo(proto::operation::Undo {
replica_id: undo.timestamp.replica_id.as_u16() as u32,
replica_id: undo.timestamp.replica_id as u32,
lamport_timestamp: undo.timestamp.value,
version: serialize_version(&undo.version),
counts: undo
.counts
.iter()
.map(|(edit_id, count)| proto::UndoCount {
replica_id: edit_id.replica_id.as_u16() as u32,
replica_id: edit_id.replica_id as u32,
lamport_timestamp: edit_id.value,
count: *count,
})
@@ -60,7 +60,7 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
lamport_timestamp,
cursor_shape,
} => proto::operation::Variant::UpdateSelections(proto::operation::UpdateSelections {
replica_id: lamport_timestamp.replica_id.as_u16() as u32,
replica_id: lamport_timestamp.replica_id as u32,
lamport_timestamp: lamport_timestamp.value,
selections: serialize_selections(selections),
line_mode: *line_mode,
@@ -72,7 +72,7 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
server_id,
diagnostics,
} => proto::operation::Variant::UpdateDiagnostics(proto::UpdateDiagnostics {
replica_id: lamport_timestamp.replica_id.as_u16() as u32,
replica_id: lamport_timestamp.replica_id as u32,
lamport_timestamp: lamport_timestamp.value,
server_id: server_id.0 as u64,
diagnostics: serialize_diagnostics(diagnostics.iter()),
@@ -84,7 +84,7 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
server_id,
} => proto::operation::Variant::UpdateCompletionTriggers(
proto::operation::UpdateCompletionTriggers {
replica_id: lamport_timestamp.replica_id.as_u16() as u32,
replica_id: lamport_timestamp.replica_id as u32,
lamport_timestamp: lamport_timestamp.value,
triggers: triggers.clone(),
language_server_id: server_id.to_proto(),
@@ -95,7 +95,7 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
line_ending,
lamport_timestamp,
} => proto::operation::Variant::UpdateLineEnding(proto::operation::UpdateLineEnding {
replica_id: lamport_timestamp.replica_id.as_u16() as u32,
replica_id: lamport_timestamp.replica_id as u32,
lamport_timestamp: lamport_timestamp.value,
line_ending: serialize_line_ending(*line_ending) as i32,
}),
@@ -106,7 +106,7 @@ pub fn serialize_operation(operation: &crate::Operation) -> proto::Operation {
/// Serializes an [`EditOperation`] to be sent over RPC.
pub fn serialize_edit_operation(operation: &EditOperation) -> proto::operation::Edit {
proto::operation::Edit {
replica_id: operation.timestamp.replica_id.as_u16() as u32,
replica_id: operation.timestamp.replica_id as u32,
lamport_timestamp: operation.timestamp.value,
version: serialize_version(&operation.version),
ranges: operation.ranges.iter().map(serialize_range).collect(),
@@ -123,12 +123,12 @@ pub fn serialize_undo_map_entry(
(edit_id, counts): (&clock::Lamport, &[(clock::Lamport, u32)]),
) -> proto::UndoMapEntry {
proto::UndoMapEntry {
replica_id: edit_id.replica_id.as_u16() as u32,
replica_id: edit_id.replica_id as u32,
local_timestamp: edit_id.value,
counts: counts
.iter()
.map(|(undo_id, count)| proto::UndoCount {
replica_id: undo_id.replica_id.as_u16() as u32,
replica_id: undo_id.replica_id as u32,
lamport_timestamp: undo_id.value,
count: *count,
})
@@ -246,7 +246,7 @@ pub fn serialize_diagnostics<'a>(
/// Serializes an [`Anchor`] to be sent over RPC.
pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor {
proto::Anchor {
replica_id: anchor.timestamp.replica_id.as_u16() as u32,
replica_id: anchor.timestamp.replica_id as u32,
timestamp: anchor.timestamp.value,
offset: anchor.offset as u64,
bias: match anchor.bias {
@@ -283,7 +283,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operati
proto::operation::Variant::Undo(undo) => {
crate::Operation::Buffer(text::Operation::Undo(UndoOperation {
timestamp: clock::Lamport {
replica_id: ReplicaId::new(undo.replica_id as u16),
replica_id: undo.replica_id as ReplicaId,
value: undo.lamport_timestamp,
},
version: deserialize_version(&undo.version),
@@ -293,7 +293,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operati
.map(|c| {
(
clock::Lamport {
replica_id: ReplicaId::new(c.replica_id as u16),
replica_id: c.replica_id as ReplicaId,
value: c.lamport_timestamp,
},
c.count,
@@ -319,7 +319,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operati
crate::Operation::UpdateSelections {
lamport_timestamp: clock::Lamport {
replica_id: ReplicaId::new(message.replica_id as u16),
replica_id: message.replica_id as ReplicaId,
value: message.lamport_timestamp,
},
selections: Arc::from(selections),
@@ -333,7 +333,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operati
proto::operation::Variant::UpdateDiagnostics(message) => {
crate::Operation::UpdateDiagnostics {
lamport_timestamp: clock::Lamport {
replica_id: ReplicaId::new(message.replica_id as u16),
replica_id: message.replica_id as ReplicaId,
value: message.lamport_timestamp,
},
server_id: LanguageServerId(message.server_id as usize),
@@ -344,7 +344,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operati
crate::Operation::UpdateCompletionTriggers {
triggers: message.triggers,
lamport_timestamp: clock::Lamport {
replica_id: ReplicaId::new(message.replica_id as u16),
replica_id: message.replica_id as ReplicaId,
value: message.lamport_timestamp,
},
server_id: LanguageServerId::from_proto(message.language_server_id),
@@ -353,7 +353,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operati
proto::operation::Variant::UpdateLineEnding(message) => {
crate::Operation::UpdateLineEnding {
lamport_timestamp: clock::Lamport {
replica_id: ReplicaId::new(message.replica_id as u16),
replica_id: message.replica_id as ReplicaId,
value: message.lamport_timestamp,
},
line_ending: deserialize_line_ending(
@@ -370,7 +370,7 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<crate::Operati
pub fn deserialize_edit_operation(edit: proto::operation::Edit) -> EditOperation {
EditOperation {
timestamp: clock::Lamport {
replica_id: ReplicaId::new(edit.replica_id as u16),
replica_id: edit.replica_id as ReplicaId,
value: edit.lamport_timestamp,
},
version: deserialize_version(&edit.version),
@@ -385,7 +385,7 @@ pub fn deserialize_undo_map_entry(
) -> (clock::Lamport, Vec<(clock::Lamport, u32)>) {
(
clock::Lamport {
replica_id: ReplicaId::new(entry.replica_id as u16),
replica_id: entry.replica_id as u16,
value: entry.local_timestamp,
},
entry
@@ -394,7 +394,7 @@ pub fn deserialize_undo_map_entry(
.map(|undo_count| {
(
clock::Lamport {
replica_id: ReplicaId::new(undo_count.replica_id as u16),
replica_id: undo_count.replica_id as u16,
value: undo_count.lamport_timestamp,
},
undo_count.count,
@@ -480,7 +480,7 @@ pub fn deserialize_anchor(anchor: proto::Anchor) -> Option<Anchor> {
};
Some(Anchor {
timestamp: clock::Lamport {
replica_id: ReplicaId::new(anchor.replica_id as u16),
replica_id: anchor.replica_id as ReplicaId,
value: anchor.timestamp,
},
offset: anchor.offset as usize,
@@ -524,7 +524,7 @@ pub fn lamport_timestamp_for_operation(operation: &proto::Operation) -> Option<c
}
Some(clock::Lamport {
replica_id: ReplicaId::new(replica_id as u16),
replica_id: replica_id as ReplicaId,
value,
})
}
@@ -559,7 +559,7 @@ pub fn deserialize_transaction(transaction: proto::Transaction) -> Result<Transa
/// Serializes a [`clock::Lamport`] timestamp to be sent over RPC.
pub fn serialize_timestamp(timestamp: clock::Lamport) -> proto::LamportTimestamp {
proto::LamportTimestamp {
replica_id: timestamp.replica_id.as_u16() as u32,
replica_id: timestamp.replica_id as u32,
value: timestamp.value,
}
}
@@ -567,7 +567,7 @@ pub fn serialize_timestamp(timestamp: clock::Lamport) -> proto::LamportTimestamp
/// Deserializes a [`clock::Lamport`] timestamp from the RPC representation.
pub fn deserialize_timestamp(timestamp: proto::LamportTimestamp) -> clock::Lamport {
clock::Lamport {
replica_id: ReplicaId::new(timestamp.replica_id as u16),
replica_id: timestamp.replica_id as ReplicaId,
value: timestamp.value,
}
}
@@ -590,7 +590,7 @@ pub fn deserialize_version(message: &[proto::VectorClockEntry]) -> clock::Global
let mut version = clock::Global::new();
for entry in message {
version.observe(clock::Lamport {
replica_id: ReplicaId::new(entry.replica_id as u16),
replica_id: entry.replica_id as ReplicaId,
value: entry.timestamp,
});
}
@@ -602,7 +602,7 @@ pub fn serialize_version(version: &clock::Global) -> Vec<proto::VectorClockEntry
version
.iter()
.map(|entry| proto::VectorClockEntry {
replica_id: entry.replica_id.as_u16() as u32,
replica_id: entry.replica_id as u32,
timestamp: entry.value,
})
.collect()

View File

@@ -6,7 +6,7 @@ use crate::{
use gpui::App;
use rand::rngs::StdRng;
use std::{env, ops::Range, sync::Arc};
use text::{Buffer, BufferId, ReplicaId};
use text::{Buffer, BufferId};
use tree_sitter::Node;
use unindent::Unindent as _;
use util::test::marked_text_ranges;
@@ -88,7 +88,7 @@ fn test_syntax_map_layers_for_range(cx: &mut App) {
registry.add(language.clone());
let mut buffer = Buffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
r#"
fn a() {
@@ -189,7 +189,7 @@ fn test_dynamic_language_injection(cx: &mut App) {
registry.add(Arc::new(ruby_lang()));
let mut buffer = Buffer::new(
ReplicaId::LOCAL,
0,
BufferId::new(1).unwrap(),
r#"
This is a code block:
@@ -811,7 +811,7 @@ fn test_syntax_map_languages_loading_with_erb(cx: &mut App) {
.unindent();
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text);
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), text);
let mut syntax_map = SyntaxMap::new(&buffer);
syntax_map.set_language_registry(registry.clone());
@@ -978,7 +978,7 @@ fn test_random_edits(
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
.unwrap_or(10);
let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), text);
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), text);
let mut syntax_map = SyntaxMap::new(&buffer);
syntax_map.set_language_registry(registry.clone());
@@ -1159,7 +1159,7 @@ fn test_edit_sequence(language_name: &str, steps: &[&str], cx: &mut App) -> (Buf
.now_or_never()
.unwrap()
.unwrap();
let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "");
let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), "");
let mut mutated_syntax_map = SyntaxMap::new(&buffer);
mutated_syntax_map.set_language_registry(registry.clone());

View File

@@ -1,3 +0,0 @@
((comment) @injection.content
(#set! injection.language "comment")
)

View File

@@ -1,30 +1,27 @@
[
"const"
"enum"
"extern"
"inline"
"sizeof"
"static"
"struct"
"typedef"
"union"
"volatile"
] @keyword
[
"break"
"case"
"const"
"continue"
"default"
"do"
"else"
"enum"
"extern"
"for"
"goto"
"if"
"inline"
"return"
"sizeof"
"static"
"struct"
"switch"
"typedef"
"union"
"volatile"
"while"
] @keyword.control
] @keyword
[
"#define"

View File

@@ -1,7 +1,3 @@
((comment) @injection.content
(#set! injection.language "comment")
)
(preproc_def
value: (preproc_arg) @injection.content
(#set! injection.language "c"))

View File

@@ -106,19 +106,32 @@ type: (primitive_type) @type.builtin
[
"alignas"
"alignof"
"break"
"case"
"catch"
"class"
"co_await"
"co_return"
"co_yield"
"concept"
"consteval"
"constexpr"
"constinit"
"continue"
"decltype"
"default"
"delete"
"do"
"else"
"enum"
"explicit"
"export"
"extern"
"final"
"for"
"friend"
"goto"
"if"
"import"
"inline"
"module"
@@ -131,40 +144,24 @@ type: (primitive_type) @type.builtin
"protected"
"public"
"requires"
"return"
"sizeof"
"struct"
"switch"
"template"
"thread_local"
"throw"
"try"
"typedef"
"typename"
"union"
"using"
"virtual"
"while"
(storage_class_specifier)
(type_qualifier)
] @keyword
[
"break"
"case"
"catch"
"co_await"
"co_return"
"co_yield"
"continue"
"default"
"do"
"else"
"for"
"goto"
"if"
"return"
"switch"
"throw"
"try"
"while"
] @keyword.control
[
"#define"
"#elif"

View File

@@ -1,7 +1,3 @@
((comment) @injection.content
(#set! injection.language "comment")
)
(preproc_def
value: (preproc_arg) @injection.content
(#set! injection.language "c++"))

View File

@@ -1,7 +1,3 @@
((comment) @content
(#set! injection.language "comment")
)
((scissors) @content
(#set! "language" "diff"))

View File

@@ -1,8 +1,4 @@
; Refer to https://github.com/nvim-treesitter/nvim-treesitter/blob/master/queries/go/injections.scm#L4C1-L16C41
((comment) @injection.content
(#set! injection.language "comment")
)
(call_expression
(selector_expression) @_function
(#any-of? @_function

View File

@@ -171,51 +171,46 @@
"as"
"async"
"await"
"break"
"case"
"catch"
"class"
"const"
"continue"
"debugger"
"default"
"delete"
"do"
"else"
"export"
"extends"
"finally"
"for"
"from"
"function"
"get"
"if"
"import"
"in"
"instanceof"
"let"
"new"
"of"
"return"
"set"
"static"
"switch"
"target"
"throw"
"try"
"typeof"
"using"
"var"
"void"
"with"
] @keyword
[
"break"
"case"
"catch"
"continue"
"do"
"else"
"finally"
"for"
"if"
"return"
"switch"
"throw"
"try"
"while"
"with"
"yield"
] @keyword.control
(switch_default "default" @keyword.control)
] @keyword
(template_substitution
"${" @punctuation.special

View File

@@ -1,7 +1,3 @@
((comment) @injection.content
(#set! injection.language "comment")
)
(((comment) @_jsdoc_comment
(#match? @_jsdoc_comment "(?s)^/[*][*][^*].*[*]/$")) @injection.content
(#set! injection.language "jsdoc"))

View File

@@ -31,103 +31,38 @@
(export_statement
(lexical_declaration
["let" "const"] @context
; Multiple names may be exported - @item is on the declarator to keep
; ranges distinct.
(variable_declarator
name: (identifier) @name) @item)))
; Exported array destructuring
(program
(export_statement
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (array_pattern
[
(identifier) @name @item
(assignment_pattern left: (identifier) @name @item)
(rest_pattern (identifier) @name @item)
])))))
; Exported object destructuring
(program
(export_statement
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (object_pattern
[(shorthand_property_identifier_pattern) @name @item
(pair_pattern
value: (identifier) @name @item)
(pair_pattern
value: (assignment_pattern left: (identifier) @name @item))
(rest_pattern (identifier) @name @item)])))))
name: (_) @name) @item)))
(program
(lexical_declaration
["let" "const"] @context
; Multiple names may be defined - @item is on the declarator to keep
; ranges distinct.
(variable_declarator
name: (identifier) @name) @item))
; Top-level array destructuring
(program
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (array_pattern
[
(identifier) @name @item
(assignment_pattern left: (identifier) @name @item)
(rest_pattern (identifier) @name @item)
]))))
; Top-level object destructuring
(program
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (object_pattern
[(shorthand_property_identifier_pattern) @name @item
(pair_pattern
value: (identifier) @name @item)
(pair_pattern
value: (assignment_pattern left: (identifier) @name @item))
(rest_pattern (identifier) @name @item)]))))
name: (_) @name) @item))
(class_declaration
"class" @context
name: (_) @name) @item
; Method definitions in classes (not in object literals)
(class_body
(method_definition
[
"get"
"set"
"async"
"*"
"readonly"
"static"
(override_modifier)
(accessibility_modifier)
]* @context
name: (_) @name
parameters: (formal_parameters
"(" @context
")" @context)) @item)
; Object literal methods
(variable_declarator
value: (object
(method_definition
[
"get"
"set"
"async"
"*"
]* @context
name: (_) @name
parameters: (formal_parameters
"(" @context
")" @context)) @item))
(method_definition
[
"get"
"set"
"async"
"*"
"readonly"
"static"
(override_modifier)
(accessibility_modifier)
]* @context
name: (_) @name
parameters: (formal_parameters
"(" @context
")" @context)) @item
(public_field_definition
[
@@ -181,43 +116,4 @@
)
) @item
; Object properties
(pair
key: [
(property_identifier) @name
(string (string_fragment) @name)
(number) @name
(computed_property_name) @name
]) @item
; Nested variables in function bodies
(statement_block
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (identifier) @name) @item))
; Nested array destructuring in functions
(statement_block
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (array_pattern
[
(identifier) @name @item
(assignment_pattern left: (identifier) @name @item)
(rest_pattern (identifier) @name @item)
]))))
; Nested object destructuring in functions
(statement_block
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (object_pattern
[(shorthand_property_identifier_pattern) @name @item
(pair_pattern value: (identifier) @name @item)
(pair_pattern value: (assignment_pattern left: (identifier) @name @item))
(rest_pattern (identifier) @name @item)]))))
(comment) @annotation

View File

@@ -1,3 +0,0 @@
((comment) @injection.content
(#set! injection.language "comment")
)

View File

@@ -83,20 +83,29 @@
"as"
"async"
"await"
"break"
"const"
"continue"
"default"
"dyn"
"else"
"enum"
"extern"
"fn"
"for"
"if"
"impl"
"in"
"let"
"loop"
"macro_rules!"
"match"
"mod"
"move"
"pub"
"raw"
"ref"
"return"
"static"
"struct"
"trait"
@@ -105,25 +114,13 @@
"unsafe"
"use"
"where"
"while"
"yield"
(crate)
(mutable_specifier)
(super)
] @keyword
[
"break"
"continue"
"else"
"for"
"if"
"in"
"loop"
"match"
"return"
"while"
"yield"
] @keyword.control
[
(string_literal)
(raw_string_literal)

View File

@@ -171,16 +171,25 @@
"as"
"async"
"await"
"break"
"case"
"catch"
"class"
"const"
"continue"
"debugger"
"default"
"delete"
"do"
"else"
"export"
"extends"
"finally"
"for"
"from"
"function"
"get"
"if"
"import"
"in"
"instanceof"
@@ -188,36 +197,22 @@
"let"
"new"
"of"
"return"
"satisfies"
"set"
"static"
"switch"
"target"
"throw"
"try"
"typeof"
"using"
"var"
"void"
"with"
] @keyword
[
"break"
"case"
"catch"
"continue"
"do"
"else"
"finally"
"for"
"if"
"return"
"switch"
"throw"
"try"
"while"
"with"
"yield"
] @keyword.control
(switch_default "default" @keyword.control)
] @keyword
(template_substitution
"${" @punctuation.special

View File

@@ -1,7 +1,3 @@
((comment) @injection.content
(#set! injection.language "comment")
)
(((comment) @_jsdoc_comment
(#match? @_jsdoc_comment "(?s)^/[*][*][^*].*[*]/$")) @injection.content
(#set! injection.language "jsdoc"))

View File

@@ -1110,7 +1110,7 @@ mod tests {
let text = r#"
function a() {
// local variables are included
// local variables are omitted
let a1 = 1;
// all functions are included
async function a2() {}
@@ -1133,7 +1133,6 @@ mod tests {
.collect::<Vec<_>>(),
&[
("function a()", 0),
("let a1", 1),
("async function a2()", 1),
("let b", 0),
("function getB()", 0),
@@ -1142,223 +1141,6 @@ mod tests {
);
}
#[gpui::test]
async fn test_outline_with_destructuring(cx: &mut TestAppContext) {
let language = crate::language(
"typescript",
tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
);
let text = r#"
// Top-level destructuring
const { a1, a2 } = a;
const [b1, b2] = b;
// Defaults and rest
const [c1 = 1, , c2, ...rest1] = c;
const { d1, d2: e1, f1 = 2, g1: h1 = 3, ...rest2 } = d;
function processData() {
// Nested object destructuring
const { c1, c2 } = c;
// Nested array destructuring
const [d1, d2, d3] = d;
// Destructuring with renaming
const { f1: g1 } = f;
// With defaults
const [x = 10, y] = xy;
}
class DataHandler {
method() {
// Destructuring in class method
const { a1, a2 } = a;
const [b1, ...b2] = b;
}
}
"#
.unindent();
let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None));
assert_eq!(
outline
.items
.iter()
.map(|item| (item.text.as_str(), item.depth))
.collect::<Vec<_>>(),
&[
("const a1", 0),
("const a2", 0),
("const b1", 0),
("const b2", 0),
("const c1", 0),
("const c2", 0),
("const rest1", 0),
("const d1", 0),
("const e1", 0),
("const h1", 0),
("const rest2", 0),
("function processData()", 0),
("const c1", 1),
("const c2", 1),
("const d1", 1),
("const d2", 1),
("const d3", 1),
("const g1", 1),
("const x", 1),
("const y", 1),
("class DataHandler", 0),
("method()", 1),
("const a1", 2),
("const a2", 2),
("const b1", 2),
("const b2", 2),
]
);
}
#[gpui::test]
async fn test_outline_with_object_properties(cx: &mut TestAppContext) {
let language = crate::language(
"typescript",
tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
);
let text = r#"
// Object with function properties
const o = { m() {}, async n() {}, g: function* () {}, h: () => {}, k: function () {} };
// Object with primitive properties
const p = { p1: 1, p2: "hello", p3: true };
// Nested objects
const q = {
r: {
// won't be included due to one-level depth limit
s: 1
},
t: 2
};
function getData() {
const local = { x: 1, y: 2 };
return local;
}
"#
.unindent();
let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None));
assert_eq!(
outline
.items
.iter()
.map(|item| (item.text.as_str(), item.depth))
.collect::<Vec<_>>(),
&[
("const o", 0),
("m()", 1),
("async n()", 1),
("g", 1),
("h", 1),
("k", 1),
("const p", 0),
("p1", 1),
("p2", 1),
("p3", 1),
("const q", 0),
("r", 1),
("s", 2),
("t", 1),
("function getData()", 0),
("const local", 1),
("x", 2),
("y", 2),
]
);
}
#[gpui::test]
async fn test_outline_with_computed_property_names(cx: &mut TestAppContext) {
let language = crate::language(
"typescript",
tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
);
let text = r#"
// Symbols as object keys
const sym = Symbol("test");
const obj1 = {
[sym]: 1,
[Symbol("inline")]: 2,
normalKey: 3
};
// Enums as object keys
enum Color { Red, Blue, Green }
const obj2 = {
[Color.Red]: "red value",
[Color.Blue]: "blue value",
regularProp: "normal"
};
// Mixed computed properties
const key = "dynamic";
const obj3 = {
[key]: 1,
["string" + "concat"]: 2,
[1 + 1]: 3,
static: 4
};
// Nested objects with computed properties
const obj4 = {
[sym]: {
nested: 1
},
regular: {
[key]: 2
}
};
"#
.unindent();
let buffer = cx.new(|cx| language::Buffer::local(text, cx).with_language(language, cx));
let outline = buffer.read_with(cx, |buffer, _| buffer.snapshot().outline(None));
assert_eq!(
outline
.items
.iter()
.map(|item| (item.text.as_str(), item.depth))
.collect::<Vec<_>>(),
&[
("const sym", 0),
("const obj1", 0),
("[sym]", 1),
("[Symbol(\"inline\")]", 1),
("normalKey", 1),
("enum Color", 0),
("const obj2", 0),
("[Color.Red]", 1),
("[Color.Blue]", 1),
("regularProp", 1),
("const key", 0),
("const obj3", 0),
("[key]", 1),
("[\"string\" + \"concat\"]", 1),
("[1 + 1]", 1),
("static", 1),
("const obj4", 0),
("[sym]", 1),
("nested", 2),
("regular", 1),
("[key]", 2),
]
);
}
#[gpui::test]
async fn test_generator_function_outline(cx: &mut TestAppContext) {
let language = crate::language("javascript", tree_sitter_typescript::LANGUAGE_TSX.into());

View File

@@ -218,18 +218,27 @@
"as"
"async"
"await"
"break"
"case"
"catch"
"class"
"const"
"continue"
"debugger"
"declare"
"default"
"delete"
"do"
"else"
"enum"
"export"
"extends"
"finally"
"for"
"from"
"function"
"get"
"if"
"implements"
"import"
"in"
@@ -248,34 +257,20 @@
"protected"
"public"
"readonly"
"return"
"satisfies"
"set"
"static"
"switch"
"target"
"throw"
"try"
"type"
"typeof"
"using"
"var"
"void"
"with"
] @keyword
[
"break"
"case"
"catch"
"continue"
"do"
"else"
"finally"
"for"
"if"
"return"
"switch"
"throw"
"try"
"while"
"with"
"yield"
] @keyword.control
(switch_default "default" @keyword.control)
] @keyword

View File

@@ -1,7 +1,3 @@
((comment) @injection.content
(#set! injection.language "comment")
)
(((comment) @_jsdoc_comment
(#match? @_jsdoc_comment "(?s)^/[*][*][^*].*[*]/$")) @injection.content
(#set! injection.language "jsdoc"))

View File

@@ -34,64 +34,18 @@
(export_statement
(lexical_declaration
["let" "const"] @context
; Multiple names may be exported - @item is on the declarator to keep
; ranges distinct.
(variable_declarator
name: (identifier) @name) @item))
; Exported array destructuring
(export_statement
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (array_pattern
[
(identifier) @name @item
(assignment_pattern left: (identifier) @name @item)
(rest_pattern (identifier) @name @item)
]))))
; Exported object destructuring
(export_statement
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (object_pattern
[(shorthand_property_identifier_pattern) @name @item
(pair_pattern
value: (identifier) @name @item)
(pair_pattern
value: (assignment_pattern left: (identifier) @name @item))
(rest_pattern (identifier) @name @item)]))))
name: (_) @name) @item))
(program
(lexical_declaration
["let" "const"] @context
; Multiple names may be defined - @item is on the declarator to keep
; ranges distinct.
(variable_declarator
name: (identifier) @name) @item))
; Top-level array destructuring
(program
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (array_pattern
[
(identifier) @name @item
(assignment_pattern left: (identifier) @name @item)
(rest_pattern (identifier) @name @item)
]))))
; Top-level object destructuring
(program
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (object_pattern
[(shorthand_property_identifier_pattern) @name @item
(pair_pattern
value: (identifier) @name @item)
(pair_pattern
value: (assignment_pattern left: (identifier) @name @item))
(rest_pattern (identifier) @name @item)]))))
name: (_) @name) @item))
(class_declaration
"class" @context
@@ -102,38 +56,21 @@
"class" @context
name: (_) @name) @item
; Method definitions in classes (not in object literals)
(class_body
(method_definition
[
"get"
"set"
"async"
"*"
"readonly"
"static"
(override_modifier)
(accessibility_modifier)
]* @context
name: (_) @name
parameters: (formal_parameters
"(" @context
")" @context)) @item)
; Object literal methods
(variable_declarator
value: (object
(method_definition
[
"get"
"set"
"async"
"*"
]* @context
name: (_) @name
parameters: (formal_parameters
"(" @context
")" @context)) @item))
(method_definition
[
"get"
"set"
"async"
"*"
"readonly"
"static"
(override_modifier)
(accessibility_modifier)
]* @context
name: (_) @name
parameters: (formal_parameters
"(" @context
")" @context)) @item
(public_field_definition
[
@@ -187,44 +124,4 @@
)
) @item
; Object properties
(pair
key: [
(property_identifier) @name
(string (string_fragment) @name)
(number) @name
(computed_property_name) @name
]) @item
; Nested variables in function bodies
(statement_block
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (identifier) @name) @item))
; Nested array destructuring in functions
(statement_block
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (array_pattern
[
(identifier) @name @item
(assignment_pattern left: (identifier) @name @item)
(rest_pattern (identifier) @name @item)
]))))
; Nested object destructuring in functions
(statement_block
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (object_pattern
[(shorthand_property_identifier_pattern) @name @item
(pair_pattern value: (identifier) @name @item)
(pair_pattern value: (assignment_pattern left: (identifier) @name @item))
(rest_pattern (identifier) @name @item)]))))
(comment) @annotation

View File

@@ -12,7 +12,7 @@ use std::{
path::{Path, PathBuf},
sync::Arc,
};
use util::{ResultExt, maybe, merge_json_value_into};
use util::{ResultExt, maybe, merge_json_value_into, rel_path::RelPath};
fn typescript_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![server_path.into(), "--stdio".into()]
@@ -29,19 +29,19 @@ impl VtslsLspAdapter {
const TYPESCRIPT_PACKAGE_NAME: &'static str = "typescript";
const TYPESCRIPT_TSDK_PATH: &'static str = "node_modules/typescript/lib";
const TYPESCRIPT_YARN_TSDK_PATH: &'static str = ".yarn/sdks/typescript/lib";
pub fn new(node: NodeRuntime, fs: Arc<dyn Fs>) -> Self {
VtslsLspAdapter { node, fs }
}
async fn tsdk_path(&self, adapter: &Arc<dyn LspAdapterDelegate>) -> Option<&'static str> {
let yarn_sdk = adapter
.worktree_root_path()
.join(Self::TYPESCRIPT_YARN_TSDK_PATH);
let is_yarn = adapter
.read_text_file(RelPath::unix(".yarn/sdks/typescript/lib/typescript.js").unwrap())
.await
.is_ok();
let tsdk_path = if self.fs.is_dir(&yarn_sdk).await {
Self::TYPESCRIPT_YARN_TSDK_PATH
let tsdk_path = if is_yarn {
".yarn/sdks/typescript/lib"
} else {
Self::TYPESCRIPT_TSDK_PATH
};

View File

@@ -1,3 +0,0 @@
((comment) @injection.content
(#set! injection.language "comment")
)

View File

@@ -1,70 +0,0 @@
use editor::Editor;
use gpui::{Entity, Subscription, WeakEntity};
use language::LineEnding;
use ui::{Tooltip, prelude::*};
use workspace::{StatusBarSettings, StatusItemView, item::ItemHandle, item::Settings};
use crate::{LineEndingSelector, Toggle};
#[derive(Default)]
pub struct LineEndingIndicator {
line_ending: Option<LineEnding>,
active_editor: Option<WeakEntity<Editor>>,
_observe_active_editor: Option<Subscription>,
}
impl LineEndingIndicator {
fn update(&mut self, editor: Entity<Editor>, _: &mut Window, cx: &mut Context<Self>) {
self.line_ending = None;
self.active_editor = None;
if let Some((_, buffer, _)) = editor.read(cx).active_excerpt(cx) {
let line_ending = buffer.read(cx).line_ending();
self.line_ending = Some(line_ending);
self.active_editor = Some(editor.downgrade());
}
cx.notify();
}
}
impl Render for LineEndingIndicator {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if !StatusBarSettings::get_global(cx).line_endings_button {
return div();
}
div().when_some(self.line_ending.as_ref(), |el, line_ending| {
el.child(
Button::new("change-line-ending", line_ending.label())
.label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, window, cx| {
if let Some(editor) = this.active_editor.as_ref() {
LineEndingSelector::toggle(editor, window, cx);
}
}))
.tooltip(|window, cx| {
Tooltip::for_action("Select Line Ending", &Toggle, window, cx)
}),
)
})
}
}
impl StatusItemView for LineEndingIndicator {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
self._observe_active_editor = Some(cx.observe_in(&editor, window, Self::update));
self.update(editor, window, cx);
} else {
self.line_ending = None;
self._observe_active_editor = None;
}
cx.notify();
}
}

View File

@@ -1,9 +1,6 @@
mod line_ending_indicator;
use editor::Editor;
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity, actions};
use language::{Buffer, LineEnding};
pub use line_ending_indicator::LineEndingIndicator;
use picker::{Picker, PickerDelegate};
use project::Project;
use std::sync::Arc;
@@ -12,7 +9,7 @@ use util::ResultExt;
use workspace::ModalView;
actions!(
line_ending_selector,
line_ending,
[
/// Toggles the line ending selector modal.
Toggle
@@ -175,7 +172,10 @@ impl PickerDelegate for LineEndingSelectorDelegate {
_: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let line_ending = self.matches.get(ix)?;
let label = line_ending.label();
let label = match line_ending {
LineEnding::Unix => "LF",
LineEnding::Windows => "CRLF",
};
let mut list_item = ListItem::new(ix)
.inset(true)

View File

@@ -19,8 +19,10 @@ use std::{
};
use theme::{ActiveTheme, SyntaxTheme, ThemeSettings};
use ui::{
Clickable, FluentBuilder, LinkPreview, StatefulInteractiveElement, StyledExt, StyledImage,
ToggleState, Tooltip, VisibleOnHover, prelude::*, tooltip_container,
ButtonCommon, Clickable, Color, FluentBuilder, IconButton, IconName, IconSize,
InteractiveElement, Label, LabelCommon, LabelSize, LinkPreview, Pixels, Rems,
StatefulInteractiveElement, StyledExt, StyledImage, ToggleState, Tooltip, VisibleOnHover,
h_flex, relative, tooltip_container, v_flex,
};
use workspace::{OpenOptions, OpenVisible, Workspace};
@@ -49,8 +51,7 @@ pub struct RenderContext {
buffer_text_style: TextStyle,
text_style: TextStyle,
border_color: Hsla,
title_bar_background_color: Hsla,
panel_background_color: Hsla,
element_background_color: Hsla,
text_color: Hsla,
link_color: Hsla,
window_rem_size: Pixels,
@@ -86,8 +87,7 @@ impl RenderContext {
text_style: window.text_style(),
syntax_theme: theme.syntax().clone(),
border_color: theme.colors().border,
title_bar_background_color: theme.colors().title_bar_background,
panel_background_color: theme.colors().panel_background,
element_background_color: theme.colors().element_background,
text_color: theme.colors().text,
link_color: theme.colors().text_accent,
window_rem_size: window.rem_size(),
@@ -511,27 +511,28 @@ fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) -
&parsed.column_alignments,
&max_column_widths,
true,
0,
cx,
);
let body: Vec<AnyElement> = parsed
.body
.iter()
.enumerate()
.map(|(index, row)| {
.map(|row| {
render_markdown_table_row(
row,
&parsed.column_alignments,
&max_column_widths,
false,
index,
cx,
)
})
.collect();
div().child(header).children(body).into_any()
cx.with_common_p(v_flex())
.w_full()
.child(header)
.children(body)
.into_any()
}
fn render_markdown_table_row(
@@ -539,7 +540,6 @@ fn render_markdown_table_row(
alignments: &Vec<ParsedMarkdownTableAlignment>,
max_column_widths: &Vec<f32>,
is_header: bool,
row_index: usize,
cx: &mut RenderContext,
) -> AnyElement {
let mut items = Vec::with_capacity(parsed.children.len());
@@ -574,7 +574,7 @@ fn render_markdown_table_row(
}
if is_header {
cell = cell.bg(cx.title_bar_background_color).opacity(0.6)
cell = cell.bg(cx.element_background_color)
}
items.push(cell);
@@ -588,10 +588,6 @@ fn render_markdown_table_row(
row = row.border_b_1();
}
if row_index % 2 == 1 {
row = row.bg(cx.panel_background_color)
}
row.children(items).into_any_element()
}

View File

@@ -123,9 +123,3 @@ pub(crate) mod m_2025_10_16 {
pub(crate) use settings::restore_code_actions_on_format;
}
pub(crate) mod m_2025_10_17 {
mod settings;
pub(crate) use settings::make_file_finder_include_ignored_an_enum;
}

View File

@@ -1,23 +0,0 @@
use anyhow::Result;
use serde_json::Value;
pub fn make_file_finder_include_ignored_an_enum(value: &mut Value) -> Result<()> {
let Some(file_finder) = value.get_mut("file_finder") else {
return Ok(());
};
let Some(file_finder_obj) = file_finder.as_object_mut() else {
anyhow::bail!("Expected file_finder to be an object");
};
let Some(include_ignored) = file_finder_obj.get_mut("include_ignored") else {
return Ok(());
};
*include_ignored = match include_ignored {
Value::Bool(true) => Value::String("all".to_string()),
Value::Bool(false) => Value::String("indexed".to_string()),
Value::Null => Value::String("smart".to_string()),
_ => anyhow::bail!("Expected include_ignored to be a boolean or null"),
};
Ok(())
}

Some files were not shown because too many files have changed in this diff Show More