From a0bd25f218d1046b4a1ed5a9e433c42430c40f46 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 26 Jun 2025 18:41:42 +0300 Subject: [PATCH 001/107] Feature gate the LSP button (#33463) Follow-up of https://github.com/zed-industries/zed/pull/32490 The tool still looks like designed by professional developers, and still may change its UX based on the internal feedback. Release Notes: - N/A --- Cargo.lock | 1 + crates/language_tools/Cargo.toml | 1 + crates/language_tools/src/lsp_tool.rs | 9 +++++++++ crates/project/src/lsp_store.rs | 4 ++-- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26fce3c46b8b6c7f754f9f82803097769d70b409..8642aaa58d9abed9a289e979b46919a162e0daba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9015,6 +9015,7 @@ dependencies = [ "collections", "copilot", "editor", + "feature_flags", "futures 0.3.31", "gpui", "itertools 0.14.0", diff --git a/crates/language_tools/Cargo.toml b/crates/language_tools/Cargo.toml index 3a0f487f7a17ddc3a43550a998590c5aa937a19a..ffdc939809145b319d5421adf5b8a923604e74fe 100644 --- a/crates/language_tools/Cargo.toml +++ b/crates/language_tools/Cargo.toml @@ -18,6 +18,7 @@ client.workspace = true collections.workspace = true copilot.workspace = true editor.workspace = true +feature_flags.workspace = true futures.workspace = true gpui.workspace = true itertools.workspace = true diff --git a/crates/language_tools/src/lsp_tool.rs b/crates/language_tools/src/lsp_tool.rs index fc1efc7794eb33986cb26ecbd0941075111da700..a7bfe70aaf7055173b6d7ce27ebde46570bf5493 100644 --- a/crates/language_tools/src/lsp_tool.rs +++ b/crates/language_tools/src/lsp_tool.rs @@ -3,6 +3,7 @@ use std::{collections::hash_map, path::PathBuf, sync::Arc, time::Duration}; use client::proto; use collections::{HashMap, HashSet}; use editor::{Editor, EditorEvent}; +use feature_flags::FeatureFlagAppExt as _; use gpui::{Corner, DismissEvent, Entity, Focusable as _, Subscription, Task, WeakEntity, actions}; use language::{BinaryStatus, BufferId, LocalFile, ServerHealth}; use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector}; @@ -244,6 +245,10 @@ impl LanguageServers { ); } } + + fn is_empty(&self) -> bool { + self.binary_statuses.is_empty() && self.health_statuses.is_empty() + } } #[derive(Debug)] @@ -865,6 +870,10 @@ impl StatusItemView for LspTool { impl Render for LspTool { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl ui::IntoElement { + if !cx.is_staff() || self.state.read(cx).language_servers.is_empty() { + return div(); + } + let Some(lsp_picker) = self.lsp_picker.clone() else { return div(); }; diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index d6f5d7a3cc98a872a1ce6822c88b6fee8599540e..950e391a1d3c7aed91e00b99da314d1f36a069d0 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -2484,11 +2484,11 @@ impl LocalLspStore { } } }; - let lsp_tool = self.weak.clone(); + let lsp_store = self.weak.clone(); let server_name = server_node.name(); let buffer_abs_path = abs_path.to_string_lossy().to_string(); cx.defer(move |cx| { - lsp_tool.update(cx, |_, cx| cx.emit(LspStoreEvent::LanguageServerUpdate { + lsp_store.update(cx, |_, cx| cx.emit(LspStoreEvent::LanguageServerUpdate { language_server_id: server_id, name: server_name, message: proto::update_language_server::Variant::RegisteredForBuffer(proto::RegisteredForBuffer { From 35863c430297f7ae04c67102b3be1f60863084f8 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Thu, 26 Jun 2025 14:02:09 -0400 Subject: [PATCH 002/107] debugger: Fix treatment of node-terminal scenarios (#33432) - Normalize `node-terminal` to `pwa-node` before sending to DAP - Split `command` into `program` and `args` - Run in external console Release Notes: - debugger: Fixed debugging JavaScript tasks that used `"type": "node-terminal"`. --- Cargo.lock | 1 + crates/dap_adapters/Cargo.toml | 1 + crates/dap_adapters/src/javascript.rs | 23 ++++++++++++++++++++--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8642aaa58d9abed9a289e979b46919a162e0daba..f3bb4c11d241564a0ed07b4b0b0318b1b1089502 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4156,6 +4156,7 @@ dependencies = [ "paths", "serde", "serde_json", + "shlex", "task", "util", "workspace-hack", diff --git a/crates/dap_adapters/Cargo.toml b/crates/dap_adapters/Cargo.toml index e2e922bd56ca3edcede5184358bfca443905b50e..07356c20849918f9ee5b8bbd426f672af3d888f2 100644 --- a/crates/dap_adapters/Cargo.toml +++ b/crates/dap_adapters/Cargo.toml @@ -33,6 +33,7 @@ log.workspace = true paths.workspace = true serde.workspace = true serde_json.workspace = true +shlex.workspace = true task.workspace = true util.workspace = true workspace-hack.workspace = true diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index d5d78186acc9c76fc2dda5d096b099bd52aaf2a4..da81e0d06df7580e989d8d4e923302793d1144e4 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -5,7 +5,7 @@ use gpui::AsyncApp; use serde_json::Value; use std::{collections::HashMap, path::PathBuf, sync::OnceLock}; use task::DebugRequest; -use util::ResultExt; +use util::{ResultExt, maybe}; use crate::*; @@ -72,6 +72,24 @@ impl JsDebugAdapter { let mut configuration = task_definition.config.clone(); if let Some(configuration) = configuration.as_object_mut() { + maybe!({ + configuration + .get("type") + .filter(|value| value == &"node-terminal")?; + let command = configuration.get("command")?.as_str()?.to_owned(); + let mut args = shlex::split(&command)?.into_iter(); + let program = args.next()?; + configuration.insert("program".to_owned(), program.into()); + configuration.insert( + "args".to_owned(), + args.map(Value::from).collect::>().into(), + ); + configuration.insert("console".to_owned(), "externalTerminal".into()); + Some(()) + }); + + configuration.entry("type").and_modify(normalize_task_type); + if let Some(program) = configuration .get("program") .cloned() @@ -96,7 +114,6 @@ impl JsDebugAdapter { .entry("cwd") .or_insert(delegate.worktree_root_path().to_string_lossy().into()); - configuration.entry("type").and_modify(normalize_task_type); configuration .entry("console") .or_insert("externalTerminal".into()); @@ -512,7 +529,7 @@ fn normalize_task_type(task_type: &mut Value) { }; let new_name = match task_type_str { - "node" | "pwa-node" => "pwa-node", + "node" | "pwa-node" | "node-terminal" => "pwa-node", "chrome" | "pwa-chrome" => "pwa-chrome", "edge" | "msedge" | "pwa-edge" | "pwa-msedge" => "pwa-msedge", _ => task_type_str, From 4983b01c8918209bcd0e3affdd5873c2aae7f7f3 Mon Sep 17 00:00:00 2001 From: fantacell Date: Thu, 26 Jun 2025 20:25:47 +0200 Subject: [PATCH 003/107] helix: Change word motions (#33408) When starting on the newline character at the end of a line the helix word motions select that character, unlike in helix itself. This makes it easy to accidentaly join two lines together. Also, word motions that go backwards should stop at the start of a line. I added that. Release Notes: - helix: Fix edge-cases with word motions and newlines --- crates/vim/src/helix.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index 425280d58bd50ae73a39362bd635f28f1630eb44..959c53e48ef4e41bf9f8fa0df56bbb79268caf99 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -188,10 +188,10 @@ impl Vim { self.helix_find_range_forward(times, window, cx, |left, right, classifier| { let left_kind = classifier.kind_with(left, ignore_punctuation); let right_kind = classifier.kind_with(right, ignore_punctuation); - let at_newline = right == '\n'; + let at_newline = (left == '\n') ^ (right == '\n'); - let found = - left_kind != right_kind && right_kind != CharKind::Whitespace || at_newline; + let found = (left_kind != right_kind && right_kind != CharKind::Whitespace) + || at_newline; found }) @@ -200,10 +200,10 @@ impl Vim { self.helix_find_range_forward(times, window, cx, |left, right, classifier| { let left_kind = classifier.kind_with(left, ignore_punctuation); let right_kind = classifier.kind_with(right, ignore_punctuation); - let at_newline = right == '\n'; + let at_newline = (left == '\n') ^ (right == '\n'); - let found = left_kind != right_kind - && (left_kind != CharKind::Whitespace || at_newline); + let found = (left_kind != right_kind && left_kind != CharKind::Whitespace) + || at_newline; found }) @@ -212,10 +212,10 @@ impl Vim { self.helix_find_range_backward(times, window, cx, |left, right, classifier| { let left_kind = classifier.kind_with(left, ignore_punctuation); let right_kind = classifier.kind_with(right, ignore_punctuation); - let at_newline = right == '\n'; + let at_newline = (left == '\n') ^ (right == '\n'); - let found = left_kind != right_kind - && (left_kind != CharKind::Whitespace || at_newline); + let found = (left_kind != right_kind && left_kind != CharKind::Whitespace) + || at_newline; found }) @@ -224,11 +224,10 @@ impl Vim { self.helix_find_range_backward(times, window, cx, |left, right, classifier| { let left_kind = classifier.kind_with(left, ignore_punctuation); let right_kind = classifier.kind_with(right, ignore_punctuation); - let at_newline = right == '\n'; + let at_newline = (left == '\n') ^ (right == '\n'); - let found = left_kind != right_kind - && right_kind != CharKind::Whitespace - && !at_newline; + let found = (left_kind != right_kind && right_kind != CharKind::Whitespace) + || at_newline; found }) From b0798714282d19ac6211fddcd39505b6523562a1 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 26 Jun 2025 12:38:54 -0600 Subject: [PATCH 004/107] Fix subtraction with overflow (#33471) Release Notes: - N/A --- crates/vim/src/normal/convert.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/vim/src/normal/convert.rs b/crates/vim/src/normal/convert.rs index 31aac771c232cf082a9f63331acf787449cffc10..5295e79edb4c08c1b7ee869d0014168df2f40787 100644 --- a/crates/vim/src/normal/convert.rs +++ b/crates/vim/src/normal/convert.rs @@ -220,7 +220,9 @@ impl Vim { } ranges.push(start..end); - if end.column == snapshot.line_len(MultiBufferRow(end.row)) { + if end.column == snapshot.line_len(MultiBufferRow(end.row)) + && end.column > 0 + { end = snapshot.clip_point(end - Point::new(0, 1), Bias::Left); } cursor_positions.push(end..end) From 985dcf75230e0f12513e4e89d211618e5314c11d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 26 Jun 2025 20:54:19 +0200 Subject: [PATCH 005/107] chore: Bump Rust version to 1.88 (#33439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Goodies in this version: - if-let chains 🎉 - Better compiler perf for Zed (https://github.com/rust-lang/rust/pull/138522) For more, see: https://releases.rs/docs/1.88.0/ Release Notes: - N/A --------- Co-authored-by: Junkui Zhang <364772080@qq.com> --- Cargo.lock | 1 - Dockerfile-collab | 2 +- crates/agent_ui/src/buffer_codegen.rs | 6 -- crates/agent_ui/src/text_thread_editor.rs | 101 +----------------- .../src/delta_command.rs | 2 +- crates/buffer_diff/src/buffer_diff.rs | 2 +- crates/collab/src/db/tests/embedding_tests.rs | 5 +- crates/collab/src/rpc.rs | 4 +- crates/editor/src/editor.rs | 13 ++- crates/fs/src/fake_git_repo.rs | 55 +++++----- crates/git/src/repository.rs | 54 +++++----- crates/gpui/src/arena.rs | 26 ----- .../gpui/src/platform/blade/apple_compat.rs | 4 +- .../gpui/src/platform/linux/wayland/window.rs | 4 +- crates/gpui/src/platform/linux/x11/window.rs | 28 +---- crates/gpui/src/platform/windows/events.rs | 6 +- crates/gpui/src/platform/windows/window.rs | 12 +-- crates/gpui/src/text_system/line_layout.rs | 2 +- crates/gpui/src/util.rs | 28 ----- crates/multi_buffer/src/position.rs | 6 +- crates/project/src/git_store.rs | 4 +- crates/project/src/git_store/conflict_set.rs | 2 +- crates/project/src/lsp_store.rs | 5 +- crates/project/src/project_tests.rs | 4 +- crates/search/src/project_search.rs | 2 +- crates/terminal/src/terminal_hyperlinks.rs | 4 +- crates/theme_importer/Cargo.toml | 1 - crates/theme_importer/src/assets.rs | 27 ----- crates/theme_importer/src/main.rs | 1 - crates/worktree/src/worktree.rs | 2 +- rust-toolchain.toml | 2 +- 31 files changed, 112 insertions(+), 303 deletions(-) delete mode 100644 crates/theme_importer/src/assets.rs diff --git a/Cargo.lock b/Cargo.lock index f3bb4c11d241564a0ed07b4b0b0318b1b1089502..473666a1a192e620c8684f5e6cb0705dc83c5da1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16037,7 +16037,6 @@ dependencies = [ "indexmap", "log", "palette", - "rust-embed", "serde", "serde_json", "serde_json_lenient", diff --git a/Dockerfile-collab b/Dockerfile-collab index 48854af4dad4b1d19f8060582f19f187a8112b97..2dafe296c7c8bb46c758d6c5f67ce6feed055d2b 100644 --- a/Dockerfile-collab +++ b/Dockerfile-collab @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.2 -FROM rust:1.87-bookworm as builder +FROM rust:1.88-bookworm as builder WORKDIR app COPY . . diff --git a/crates/agent_ui/src/buffer_codegen.rs b/crates/agent_ui/src/buffer_codegen.rs index f3919a958f8cdcc7f1114406350de4cec5afd77a..117dcf4f8e17bc99c4bd6ed75af070d84e5b1015 100644 --- a/crates/agent_ui/src/buffer_codegen.rs +++ b/crates/agent_ui/src/buffer_codegen.rs @@ -1094,15 +1094,9 @@ mod tests { }; use language_model::{LanguageModelRegistry, TokenUsage}; use rand::prelude::*; - use serde::Serialize; use settings::SettingsStore; use std::{future, sync::Arc}; - #[derive(Serialize)] - pub struct DummyCompletionRequest { - pub name: String, - } - #[gpui::test(iterations = 10)] async fn test_transform_autoindent(cx: &mut TestAppContext, mut rng: StdRng) { init_test(cx); diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 0a1013a6f29f86ede4580565d2f5df67ac9e263d..c035282c9270ecdb786d221e76739b5886d3a092 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -69,7 +69,7 @@ use workspace::{ searchable::{Direction, SearchableItemHandle}, }; use workspace::{ - Save, Toast, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, + Save, Toast, Workspace, item::{self, FollowableItem, Item, ItemHandle}, notifications::NotificationId, pane, @@ -2924,13 +2924,6 @@ impl FollowableItem for TextThreadEditor { } } -pub struct ContextEditorToolbarItem { - active_context_editor: Option>, - model_summary_editor: Entity, -} - -impl ContextEditorToolbarItem {} - pub fn render_remaining_tokens( context_editor: &Entity, cx: &App, @@ -2983,98 +2976,6 @@ pub fn render_remaining_tokens( ) } -impl Render for ContextEditorToolbarItem { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let left_side = h_flex() - .group("chat-title-group") - .gap_1() - .items_center() - .flex_grow() - .child( - div() - .w_full() - .when(self.active_context_editor.is_some(), |left_side| { - left_side.child(self.model_summary_editor.clone()) - }), - ) - .child( - div().visible_on_hover("chat-title-group").child( - IconButton::new("regenerate-context", IconName::RefreshTitle) - .shape(ui::IconButtonShape::Square) - .tooltip(Tooltip::text("Regenerate Title")) - .on_click(cx.listener(move |_, _, _window, cx| { - cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary) - })), - ), - ); - - let right_side = h_flex() - .gap_2() - // TODO display this in a nicer way, once we have a design for it. - // .children({ - // let project = self - // .workspace - // .upgrade() - // .map(|workspace| workspace.read(cx).project().downgrade()); - // - // let scan_items_remaining = cx.update_global(|db: &mut SemanticDb, cx| { - // project.and_then(|project| db.remaining_summaries(&project, cx)) - // }); - // scan_items_remaining - // .map(|remaining_items| format!("Files to scan: {}", remaining_items)) - // }) - .children( - self.active_context_editor - .as_ref() - .and_then(|editor| editor.upgrade()) - .and_then(|editor| render_remaining_tokens(&editor, cx)), - ); - - h_flex() - .px_0p5() - .size_full() - .gap_2() - .justify_between() - .child(left_side) - .child(right_side) - } -} - -impl ToolbarItemView for ContextEditorToolbarItem { - fn set_active_pane_item( - &mut self, - active_pane_item: Option<&dyn ItemHandle>, - _window: &mut Window, - cx: &mut Context, - ) -> ToolbarItemLocation { - self.active_context_editor = active_pane_item - .and_then(|item| item.act_as::(cx)) - .map(|editor| editor.downgrade()); - cx.notify(); - if self.active_context_editor.is_none() { - ToolbarItemLocation::Hidden - } else { - ToolbarItemLocation::PrimaryRight - } - } - - fn pane_focus_update( - &mut self, - _pane_focused: bool, - _window: &mut Window, - cx: &mut Context, - ) { - cx.notify(); - } -} - -impl EventEmitter for ContextEditorToolbarItem {} - -pub enum ContextEditorToolbarItemEvent { - RegenerateSummary, -} -impl EventEmitter for ContextEditorToolbarItem {} - enum PendingSlashCommand {} fn invoked_slash_command_fold_placeholder( diff --git a/crates/assistant_slash_commands/src/delta_command.rs b/crates/assistant_slash_commands/src/delta_command.rs index 047d2899082891ad5e1cfc5e8ec9188dd1aa4e4f..8c840c17b2c7fe9d8c8995b21c35cb35980dd71b 100644 --- a/crates/assistant_slash_commands/src/delta_command.rs +++ b/crates/assistant_slash_commands/src/delta_command.rs @@ -74,7 +74,7 @@ impl SlashCommand for DeltaSlashCommand { .slice(section.range.to_offset(&context_buffer)), ); file_command_new_outputs.push(Arc::new(FileSlashCommand).run( - &[metadata.path.clone()], + std::slice::from_ref(&metadata.path), context_slash_command_output_sections, context_buffer.clone(), workspace.clone(), diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index 3812d48bf7a064017b654ea82cdf3e19a7810fb2..ee09fda46e008c903120eb0430ff18fae57dc3da 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -1867,7 +1867,7 @@ mod tests { let hunk = diff.hunks(&buffer, cx).next().unwrap(); let new_index_text = diff - .stage_or_unstage_hunks(true, &[hunk.clone()], &buffer, true, cx) + .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx) .unwrap() .to_string(); assert_eq!(new_index_text, buffer_text); diff --git a/crates/collab/src/db/tests/embedding_tests.rs b/crates/collab/src/db/tests/embedding_tests.rs index 8659d4b4a1165ab9d3a2ed591fc9a6dfbd727c56..bfc238dd9ab7027cb2506b4c2d7130e070da8a04 100644 --- a/crates/collab/src/db/tests/embedding_tests.rs +++ b/crates/collab/src/db/tests/embedding_tests.rs @@ -76,7 +76,10 @@ async fn test_purge_old_embeddings(cx: &mut gpui::TestAppContext) { db.purge_old_embeddings().await.unwrap(); // Try to retrieve the purged embeddings - let retrieved_embeddings = db.get_embeddings(model, &[digest.clone()]).await.unwrap(); + let retrieved_embeddings = db + .get_embeddings(model, std::slice::from_ref(&digest)) + .await + .unwrap(); assert!( retrieved_embeddings.is_empty(), "Old embeddings should have been purged" diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 22daab491c499bf568f155cd6e049868c58192ce..753e591914f45ea962367130d1ecce9a4fd2620f 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -179,7 +179,7 @@ struct Session { } impl Session { - async fn db(&self) -> tokio::sync::MutexGuard { + async fn db(&self) -> tokio::sync::MutexGuard<'_, DbHandle> { #[cfg(test)] tokio::task::yield_now().await; let guard = self.db.lock().await; @@ -1037,7 +1037,7 @@ impl Server { } } - pub async fn snapshot(self: &Arc) -> ServerSnapshot { + pub async fn snapshot(self: &Arc) -> ServerSnapshot<'_> { ServerSnapshot { connection_pool: ConnectionPoolGuard { guard: self.connection_pool.lock(), diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ea30cc6fab94d7a80e8855efd3832b21a945b6c1..6244e7a4c3fbaaacea3b81d86b4ed521744e2eb1 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3897,8 +3897,10 @@ impl Editor { bracket_pair_matching_end = Some(pair.clone()); } } - if bracket_pair.is_none() && bracket_pair_matching_end.is_some() { - bracket_pair = Some(bracket_pair_matching_end.unwrap()); + if let Some(end) = bracket_pair_matching_end + && bracket_pair.is_none() + { + bracket_pair = Some(end); is_bracket_pair_end = true; } } @@ -13381,7 +13383,12 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx); + self.unfold_ranges( + std::slice::from_ref(&range), + false, + auto_scroll.is_some(), + cx, + ); self.change_selections(auto_scroll, window, cx, |s| { if replace_newest { s.delete(s.newest_anchor().id); diff --git a/crates/fs/src/fake_git_repo.rs b/crates/fs/src/fake_git_repo.rs index c6e0afe2948386b44d831475debe85d1d7f5e5f5..40a292e0401df931cc17d04ed71219917292ab1f 100644 --- a/crates/fs/src/fake_git_repo.rs +++ b/crates/fs/src/fake_git_repo.rs @@ -74,7 +74,7 @@ impl FakeGitRepository { impl GitRepository for FakeGitRepository { fn reload_index(&self) {} - fn load_index_text(&self, path: RepoPath) -> BoxFuture> { + fn load_index_text(&self, path: RepoPath) -> BoxFuture<'_, Option> { async { self.with_state_async(false, move |state| { state @@ -89,7 +89,7 @@ impl GitRepository for FakeGitRepository { .boxed() } - fn load_committed_text(&self, path: RepoPath) -> BoxFuture> { + fn load_committed_text(&self, path: RepoPath) -> BoxFuture<'_, Option> { async { self.with_state_async(false, move |state| { state @@ -108,7 +108,7 @@ impl GitRepository for FakeGitRepository { &self, _commit: String, _cx: AsyncApp, - ) -> BoxFuture> { + ) -> BoxFuture<'_, Result> { unimplemented!() } @@ -117,7 +117,7 @@ impl GitRepository for FakeGitRepository { path: RepoPath, content: Option, _env: Arc>, - ) -> BoxFuture> { + ) -> BoxFuture<'_, anyhow::Result<()>> { self.with_state_async(true, move |state| { if let Some(message) = &state.simulated_index_write_error_message { anyhow::bail!("{message}"); @@ -134,7 +134,7 @@ impl GitRepository for FakeGitRepository { None } - fn revparse_batch(&self, revs: Vec) -> BoxFuture>>> { + fn revparse_batch(&self, revs: Vec) -> BoxFuture<'_, Result>>> { self.with_state_async(false, |state| { Ok(revs .into_iter() @@ -143,7 +143,7 @@ impl GitRepository for FakeGitRepository { }) } - fn show(&self, commit: String) -> BoxFuture> { + fn show(&self, commit: String) -> BoxFuture<'_, Result> { async { Ok(CommitDetails { sha: commit.into(), @@ -158,7 +158,7 @@ impl GitRepository for FakeGitRepository { _commit: String, _mode: ResetMode, _env: Arc>, - ) -> BoxFuture> { + ) -> BoxFuture<'_, Result<()>> { unimplemented!() } @@ -167,7 +167,7 @@ impl GitRepository for FakeGitRepository { _commit: String, _paths: Vec, _env: Arc>, - ) -> BoxFuture> { + ) -> BoxFuture<'_, Result<()>> { unimplemented!() } @@ -179,11 +179,11 @@ impl GitRepository for FakeGitRepository { self.common_dir_path.clone() } - fn merge_message(&self) -> BoxFuture> { + fn merge_message(&self) -> BoxFuture<'_, Option> { async move { None }.boxed() } - fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture> { + fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<'_, Result> { let workdir_path = self.dot_git_path.parent().unwrap(); // Load gitignores @@ -314,7 +314,7 @@ impl GitRepository for FakeGitRepository { async move { result? }.boxed() } - fn branches(&self) -> BoxFuture>> { + fn branches(&self) -> BoxFuture<'_, Result>> { self.with_state_async(false, move |state| { let current_branch = &state.current_branch_name; Ok(state @@ -330,21 +330,21 @@ impl GitRepository for FakeGitRepository { }) } - fn change_branch(&self, name: String) -> BoxFuture> { + fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>> { self.with_state_async(true, |state| { state.current_branch_name = Some(name); Ok(()) }) } - fn create_branch(&self, name: String) -> BoxFuture> { + fn create_branch(&self, name: String) -> BoxFuture<'_, Result<()>> { self.with_state_async(true, move |state| { state.branches.insert(name.to_owned()); Ok(()) }) } - fn blame(&self, path: RepoPath, _content: Rope) -> BoxFuture> { + fn blame(&self, path: RepoPath, _content: Rope) -> BoxFuture<'_, Result> { self.with_state_async(false, move |state| { state .blames @@ -358,7 +358,7 @@ impl GitRepository for FakeGitRepository { &self, _paths: Vec, _env: Arc>, - ) -> BoxFuture> { + ) -> BoxFuture<'_, Result<()>> { unimplemented!() } @@ -366,7 +366,7 @@ impl GitRepository for FakeGitRepository { &self, _paths: Vec, _env: Arc>, - ) -> BoxFuture> { + ) -> BoxFuture<'_, Result<()>> { unimplemented!() } @@ -376,7 +376,7 @@ impl GitRepository for FakeGitRepository { _name_and_email: Option<(gpui::SharedString, gpui::SharedString)>, _options: CommitOptions, _env: Arc>, - ) -> BoxFuture> { + ) -> BoxFuture<'_, Result<()>> { unimplemented!() } @@ -388,7 +388,7 @@ impl GitRepository for FakeGitRepository { _askpass: AskPassDelegate, _env: Arc>, _cx: AsyncApp, - ) -> BoxFuture> { + ) -> BoxFuture<'_, Result> { unimplemented!() } @@ -399,7 +399,7 @@ impl GitRepository for FakeGitRepository { _askpass: AskPassDelegate, _env: Arc>, _cx: AsyncApp, - ) -> BoxFuture> { + ) -> BoxFuture<'_, Result> { unimplemented!() } @@ -409,19 +409,19 @@ impl GitRepository for FakeGitRepository { _askpass: AskPassDelegate, _env: Arc>, _cx: AsyncApp, - ) -> BoxFuture> { + ) -> BoxFuture<'_, Result> { unimplemented!() } - fn get_remotes(&self, _branch: Option) -> BoxFuture>> { + fn get_remotes(&self, _branch: Option) -> BoxFuture<'_, Result>> { unimplemented!() } - fn check_for_pushed_commit(&self) -> BoxFuture>> { + fn check_for_pushed_commit(&self) -> BoxFuture<'_, Result>> { future::ready(Ok(Vec::new())).boxed() } - fn diff(&self, _diff: git::repository::DiffType) -> BoxFuture> { + fn diff(&self, _diff: git::repository::DiffType) -> BoxFuture<'_, Result> { unimplemented!() } @@ -429,7 +429,10 @@ impl GitRepository for FakeGitRepository { unimplemented!() } - fn restore_checkpoint(&self, _checkpoint: GitRepositoryCheckpoint) -> BoxFuture> { + fn restore_checkpoint( + &self, + _checkpoint: GitRepositoryCheckpoint, + ) -> BoxFuture<'_, Result<()>> { unimplemented!() } @@ -437,7 +440,7 @@ impl GitRepository for FakeGitRepository { &self, _left: GitRepositoryCheckpoint, _right: GitRepositoryCheckpoint, - ) -> BoxFuture> { + ) -> BoxFuture<'_, Result> { unimplemented!() } @@ -445,7 +448,7 @@ impl GitRepository for FakeGitRepository { &self, _base_checkpoint: GitRepositoryCheckpoint, _target_checkpoint: GitRepositoryCheckpoint, - ) -> BoxFuture> { + ) -> BoxFuture<'_, Result> { unimplemented!() } } diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 1254e451fdaed13f07862ec3a379b254da3ee373..7b07cb62a1ee66f8aa6d11b9be969741458b0785 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -303,25 +303,25 @@ pub trait GitRepository: Send + Sync { /// Returns the contents of an entry in the repository's index, or None if there is no entry for the given path. /// /// Also returns `None` for symlinks. - fn load_index_text(&self, path: RepoPath) -> BoxFuture>; + fn load_index_text(&self, path: RepoPath) -> BoxFuture<'_, Option>; /// Returns the contents of an entry in the repository's HEAD, or None if HEAD does not exist or has no entry for the given path. /// /// Also returns `None` for symlinks. - fn load_committed_text(&self, path: RepoPath) -> BoxFuture>; + fn load_committed_text(&self, path: RepoPath) -> BoxFuture<'_, Option>; fn set_index_text( &self, path: RepoPath, content: Option, env: Arc>, - ) -> BoxFuture>; + ) -> BoxFuture<'_, anyhow::Result<()>>; /// Returns the URL of the remote with the given name. fn remote_url(&self, name: &str) -> Option; /// Resolve a list of refs to SHAs. - fn revparse_batch(&self, revs: Vec) -> BoxFuture>>>; + fn revparse_batch(&self, revs: Vec) -> BoxFuture<'_, Result>>>; fn head_sha(&self) -> BoxFuture<'_, Option> { async move { @@ -335,33 +335,33 @@ pub trait GitRepository: Send + Sync { .boxed() } - fn merge_message(&self) -> BoxFuture>; + fn merge_message(&self) -> BoxFuture<'_, Option>; - fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture>; + fn status(&self, path_prefixes: &[RepoPath]) -> BoxFuture<'_, Result>; - fn branches(&self) -> BoxFuture>>; + fn branches(&self) -> BoxFuture<'_, Result>>; - fn change_branch(&self, name: String) -> BoxFuture>; - fn create_branch(&self, name: String) -> BoxFuture>; + fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>>; + fn create_branch(&self, name: String) -> BoxFuture<'_, Result<()>>; fn reset( &self, commit: String, mode: ResetMode, env: Arc>, - ) -> BoxFuture>; + ) -> BoxFuture<'_, Result<()>>; fn checkout_files( &self, commit: String, paths: Vec, env: Arc>, - ) -> BoxFuture>; + ) -> BoxFuture<'_, Result<()>>; - fn show(&self, commit: String) -> BoxFuture>; + fn show(&self, commit: String) -> BoxFuture<'_, Result>; - fn load_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture>; - fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture>; + fn load_commit(&self, commit: String, cx: AsyncApp) -> BoxFuture<'_, Result>; + fn blame(&self, path: RepoPath, content: Rope) -> BoxFuture<'_, Result>; /// Returns the absolute path to the repository. For worktrees, this will be the path to the /// worktree's gitdir within the main repository (typically `.git/worktrees/`). @@ -376,7 +376,7 @@ pub trait GitRepository: Send + Sync { &self, paths: Vec, env: Arc>, - ) -> BoxFuture>; + ) -> BoxFuture<'_, Result<()>>; /// Updates the index to match HEAD at the given paths. /// /// If any of the paths were previously staged but do not exist in HEAD, they will be removed from the index. @@ -384,7 +384,7 @@ pub trait GitRepository: Send + Sync { &self, paths: Vec, env: Arc>, - ) -> BoxFuture>; + ) -> BoxFuture<'_, Result<()>>; fn commit( &self, @@ -392,7 +392,7 @@ pub trait GitRepository: Send + Sync { name_and_email: Option<(SharedString, SharedString)>, options: CommitOptions, env: Arc>, - ) -> BoxFuture>; + ) -> BoxFuture<'_, Result<()>>; fn push( &self, @@ -404,7 +404,7 @@ pub trait GitRepository: Send + Sync { // This method takes an AsyncApp to ensure it's invoked on the main thread, // otherwise git-credentials-manager won't work. cx: AsyncApp, - ) -> BoxFuture>; + ) -> BoxFuture<'_, Result>; fn pull( &self, @@ -415,7 +415,7 @@ pub trait GitRepository: Send + Sync { // This method takes an AsyncApp to ensure it's invoked on the main thread, // otherwise git-credentials-manager won't work. cx: AsyncApp, - ) -> BoxFuture>; + ) -> BoxFuture<'_, Result>; fn fetch( &self, @@ -425,35 +425,35 @@ pub trait GitRepository: Send + Sync { // This method takes an AsyncApp to ensure it's invoked on the main thread, // otherwise git-credentials-manager won't work. cx: AsyncApp, - ) -> BoxFuture>; + ) -> BoxFuture<'_, Result>; - fn get_remotes(&self, branch_name: Option) -> BoxFuture>>; + fn get_remotes(&self, branch_name: Option) -> BoxFuture<'_, Result>>; /// returns a list of remote branches that contain HEAD - fn check_for_pushed_commit(&self) -> BoxFuture>>; + fn check_for_pushed_commit(&self) -> BoxFuture<'_, Result>>; /// Run git diff - fn diff(&self, diff: DiffType) -> BoxFuture>; + fn diff(&self, diff: DiffType) -> BoxFuture<'_, Result>; /// Creates a checkpoint for the repository. fn checkpoint(&self) -> BoxFuture<'static, Result>; /// Resets to a previously-created checkpoint. - fn restore_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture>; + fn restore_checkpoint(&self, checkpoint: GitRepositoryCheckpoint) -> BoxFuture<'_, Result<()>>; /// Compares two checkpoints, returning true if they are equal fn compare_checkpoints( &self, left: GitRepositoryCheckpoint, right: GitRepositoryCheckpoint, - ) -> BoxFuture>; + ) -> BoxFuture<'_, Result>; /// Computes a diff between two checkpoints. fn diff_checkpoints( &self, base_checkpoint: GitRepositoryCheckpoint, target_checkpoint: GitRepositoryCheckpoint, - ) -> BoxFuture>; + ) -> BoxFuture<'_, Result>; } pub enum DiffType { @@ -2268,7 +2268,7 @@ mod tests { impl RealGitRepository { /// Force a Git garbage collection on the repository. - fn gc(&self) -> BoxFuture> { + fn gc(&self) -> BoxFuture<'_, Result<()>> { let working_directory = self.working_directory(); let git_binary_path = self.git_binary_path.clone(); let executor = self.executor.clone(); diff --git a/crates/gpui/src/arena.rs b/crates/gpui/src/arena.rs index 2448746a8867b88cc7e6b22b27a6ef5eae6c40aa..ee72d0e96425816220094f4cbff86315153afb74 100644 --- a/crates/gpui/src/arena.rs +++ b/crates/gpui/src/arena.rs @@ -214,32 +214,6 @@ impl DerefMut for ArenaBox { } } -pub struct ArenaRef(ArenaBox); - -impl From> for ArenaRef { - fn from(value: ArenaBox) -> Self { - ArenaRef(value) - } -} - -impl Clone for ArenaRef { - fn clone(&self) -> Self { - Self(ArenaBox { - ptr: self.0.ptr, - valid: self.0.valid.clone(), - }) - } -} - -impl Deref for ArenaRef { - type Target = T; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - self.0.deref() - } -} - #[cfg(test)] mod tests { use std::{cell::Cell, rc::Rc}; diff --git a/crates/gpui/src/platform/blade/apple_compat.rs b/crates/gpui/src/platform/blade/apple_compat.rs index b1baab8854aca67dd25b70c3f03e288edeeab6dc..a75ddfa69a3daa2e43eaf00673a34d8c22e1cd25 100644 --- a/crates/gpui/src/platform/blade/apple_compat.rs +++ b/crates/gpui/src/platform/blade/apple_compat.rs @@ -29,14 +29,14 @@ pub unsafe fn new_renderer( } impl rwh::HasWindowHandle for RawWindow { - fn window_handle(&self) -> Result { + fn window_handle(&self) -> Result, rwh::HandleError> { let view = NonNull::new(self.view).unwrap(); let handle = rwh::AppKitWindowHandle::new(view); Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) }) } } impl rwh::HasDisplayHandle for RawWindow { - fn display_handle(&self) -> Result { + fn display_handle(&self) -> Result, rwh::HandleError> { let handle = rwh::AppKitDisplayHandle::new(); Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) }) } diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 9d602130d4c545213175b2bbbd088ec5f9062c1c..36e070b0b0fc03d1dd6cd3402eedd228dbc909e3 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -252,11 +252,11 @@ impl Drop for WaylandWindow { } impl WaylandWindow { - fn borrow(&self) -> Ref { + fn borrow(&self) -> Ref<'_, WaylandWindowState> { self.0.state.borrow() } - fn borrow_mut(&self) -> RefMut { + fn borrow_mut(&self) -> RefMut<'_, WaylandWindowState> { self.0.state.borrow_mut() } diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index 248911a5b97d08d2ceaf48db22c215480ea68db0..1a3c323c35129b9ea56595b7f81775de4b036454 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -288,7 +288,7 @@ pub(crate) struct X11WindowStatePtr { } impl rwh::HasWindowHandle for RawWindow { - fn window_handle(&self) -> Result { + fn window_handle(&self) -> Result, rwh::HandleError> { let Some(non_zero) = NonZeroU32::new(self.window_id) else { log::error!("RawWindow.window_id zero when getting window handle."); return Err(rwh::HandleError::Unavailable); @@ -299,7 +299,7 @@ impl rwh::HasWindowHandle for RawWindow { } } impl rwh::HasDisplayHandle for RawWindow { - fn display_handle(&self) -> Result { + fn display_handle(&self) -> Result, rwh::HandleError> { let Some(non_zero) = NonNull::new(self.connection) else { log::error!("Null RawWindow.connection when getting display handle."); return Err(rwh::HandleError::Unavailable); @@ -310,12 +310,12 @@ impl rwh::HasDisplayHandle for RawWindow { } impl rwh::HasWindowHandle for X11Window { - fn window_handle(&self) -> Result { + fn window_handle(&self) -> Result, rwh::HandleError> { unimplemented!() } } impl rwh::HasDisplayHandle for X11Window { - fn display_handle(&self) -> Result { + fn display_handle(&self) -> Result, rwh::HandleError> { unimplemented!() } } @@ -679,26 +679,6 @@ impl X11WindowState { } } -/// A handle to an X11 window which destroys it on Drop. -pub struct X11WindowHandle { - id: xproto::Window, - xcb: Rc, -} - -impl Drop for X11WindowHandle { - fn drop(&mut self) { - maybe!({ - check_reply( - || "X11 DestroyWindow failed while dropping X11WindowHandle.", - self.xcb.destroy_window(self.id), - )?; - xcb_flush(&self.xcb); - anyhow::Ok(()) - }) - .log_err(); - } -} - pub(crate) struct X11Window(pub X11WindowStatePtr); impl Drop for X11Window { diff --git a/crates/gpui/src/platform/windows/events.rs b/crates/gpui/src/platform/windows/events.rs index 65565c6b3fc2e43ee9e8ef29cc131cc8c42c1355..d7205580cdc133fccbf97f1287651521ff7bb06b 100644 --- a/crates/gpui/src/platform/windows/events.rs +++ b/crates/gpui/src/platform/windows/events.rs @@ -1074,8 +1074,10 @@ fn handle_nc_mouse_up_msg( } let last_pressed = state_ptr.state.borrow_mut().nc_button_pressed.take(); - if button == MouseButton::Left && last_pressed.is_some() { - let handled = match (wparam.0 as u32, last_pressed.unwrap()) { + if button == MouseButton::Left + && let Some(last_pressed) = last_pressed + { + let handled = match (wparam.0 as u32, last_pressed) { (HTMINBUTTON, HTMINBUTTON) => { unsafe { ShowWindowAsync(handle, SW_MINIMIZE).ok().log_err() }; true diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index e84840fb25591ed0d25ab257b7db59eb0b7dd1b5..c363d5854deccbb6d0f29391b2d47316f228b57d 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -1250,11 +1250,13 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option, state: u32 type SetWindowCompositionAttributeType = unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL; let module_name = PCSTR::from_raw(c"user32.dll".as_ptr() as *const u8); - let user32 = GetModuleHandleA(module_name); - if user32.is_ok() { + if let Some(user32) = GetModuleHandleA(module_name) + .context("Unable to get user32.dll handle") + .log_err() + { let func_name = PCSTR::from_raw(c"SetWindowCompositionAttribute".as_ptr() as *const u8); let set_window_composition_attribute: SetWindowCompositionAttributeType = - std::mem::transmute(GetProcAddress(user32.unwrap(), func_name)); + std::mem::transmute(GetProcAddress(user32, func_name)); let mut color = color.unwrap_or_default(); let is_acrylic = state == 4; if is_acrylic && color.3 == 0 { @@ -1275,10 +1277,6 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option, state: u32 cb_data: std::mem::size_of::(), }; let _ = set_window_composition_attribute(hwnd, &mut data as *mut _ as _); - } else { - let _ = user32 - .inspect_err(|e| log::error!("Error getting module: {e}")) - .ok(); } } } diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 5e5c2eff1e02e57b69726394722a99b63f35b1d2..5a72080e4809663679483b41b70cf84a69cc5a06 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -582,7 +582,7 @@ pub struct FontRun { } trait AsCacheKeyRef { - fn as_cache_key_ref(&self) -> CacheKeyRef; + fn as_cache_key_ref(&self) -> CacheKeyRef<'_>; } #[derive(Clone, Debug, Eq)] diff --git a/crates/gpui/src/util.rs b/crates/gpui/src/util.rs index fda5e81333817b9ce7fc79311b1dc4a628208f58..5e92335fdc86e331d3a469c4384043fd9799b00a 100644 --- a/crates/gpui/src/util.rs +++ b/crates/gpui/src/util.rs @@ -83,34 +83,6 @@ where timer.race(future).await } -#[cfg(any(test, feature = "test-support"))] -pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace); - -#[cfg(any(test, feature = "test-support"))] -impl std::fmt::Debug for CwdBacktrace<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use backtrace::{BacktraceFmt, BytesOrWideString}; - - let cwd = std::env::current_dir().unwrap(); - let cwd = cwd.parent().unwrap(); - let mut print_path = |fmt: &mut std::fmt::Formatter<'_>, path: BytesOrWideString<'_>| { - std::fmt::Display::fmt(&path, fmt) - }; - let mut fmt = BacktraceFmt::new(f, backtrace::PrintFmt::Full, &mut print_path); - for frame in self.0.frames() { - let mut formatted_frame = fmt.frame(); - if frame - .symbols() - .iter() - .any(|s| s.filename().map_or(false, |f| f.starts_with(cwd))) - { - formatted_frame.backtrace_frame(frame)?; - } - } - fmt.finish() - } -} - /// Increment the given atomic counter if it is not zero. /// Return the new value of the counter. pub(crate) fn atomic_incr_if_not_zero(counter: &AtomicUsize) -> usize { diff --git a/crates/multi_buffer/src/position.rs b/crates/multi_buffer/src/position.rs index 1ed2fe56e4d775a8b55d311262c77ec2397592b6..06508750597b97d7275b964114bcdad0d0e34c79 100644 --- a/crates/multi_buffer/src/position.rs +++ b/crates/multi_buffer/src/position.rs @@ -126,17 +126,17 @@ impl Default for TypedRow { impl PartialOrd for TypedOffset { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.value.cmp(&other.value)) + Some(self.cmp(&other)) } } impl PartialOrd for TypedPoint { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.value.cmp(&other.value)) + Some(self.cmp(&other)) } } impl PartialOrd for TypedRow { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.value.cmp(&other.value)) + Some(self.cmp(&other)) } } diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 7002f83ab35bc9f9aa500fd1d96aded03df072c9..9ff3823e0f13a87fdcff944db7ad2d52350a7cce 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -4556,7 +4556,9 @@ async fn compute_snapshot( let mut events = Vec::new(); let branches = backend.branches().await?; let branch = branches.into_iter().find(|branch| branch.is_head); - let statuses = backend.status(&[WORK_DIRECTORY_REPO_PATH.clone()]).await?; + let statuses = backend + .status(std::slice::from_ref(&WORK_DIRECTORY_REPO_PATH)) + .await?; let statuses_by_path = SumTree::from_iter( statuses .entries diff --git a/crates/project/src/git_store/conflict_set.rs b/crates/project/src/git_store/conflict_set.rs index e78a70f2754a905ca465ad07ad365b04638e7c5f..27b191f65f896e6488a4d9c52f37e9426cac1c46 100644 --- a/crates/project/src/git_store/conflict_set.rs +++ b/crates/project/src/git_store/conflict_set.rs @@ -565,7 +565,7 @@ mod tests { conflict_set.snapshot().conflicts[0].clone() }); cx.update(|cx| { - conflict.resolve(buffer.clone(), &[conflict.theirs.clone()], cx); + conflict.resolve(buffer.clone(), std::slice::from_ref(&conflict.theirs), cx); }); cx.run_until_parked(); diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 950e391a1d3c7aed91e00b99da314d1f36a069d0..6e56dec99944264df93528fefeb9db5f51c43844 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -5743,7 +5743,10 @@ impl LspStore { match language { Some(language) => { adapter - .labels_for_completions(&[completion_item.clone()], language) + .labels_for_completions( + std::slice::from_ref(&completion_item), + language, + ) .await? } None => Vec::new(), diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 19b88c069554a483c0412bc313dec6f4f0350055..54a013bc4168f09aead0561a962a0255088f76dd 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -7502,13 +7502,13 @@ async fn test_staging_random_hunks( if hunk.status().has_secondary_hunk() { log::info!("staging hunk at {row}"); uncommitted_diff.update(cx, |diff, cx| { - diff.stage_or_unstage_hunks(true, &[hunk.clone()], &snapshot, true, cx); + diff.stage_or_unstage_hunks(true, std::slice::from_ref(hunk), &snapshot, true, cx); }); hunk.secondary_status = SecondaryHunkRemovalPending; } else { log::info!("unstaging hunk at {row}"); uncommitted_diff.update(cx, |diff, cx| { - diff.stage_or_unstage_hunks(false, &[hunk.clone()], &snapshot, true, cx); + diff.stage_or_unstage_hunks(false, std::slice::from_ref(hunk), &snapshot, true, cx); }); hunk.secondary_status = SecondaryHunkAdditionPending; } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 79ae18fcfe1dd6ddb443b0afe00153a7ec472e31..8e1ea3d7733cd18412b1330551301864df981ec8 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1302,7 +1302,7 @@ impl ProjectSearchView { let range_to_select = match_ranges[new_index].clone(); self.results_editor.update(cx, |editor, cx| { let range_to_select = editor.range_for_match(&range_to_select); - editor.unfold_ranges(&[range_to_select.clone()], false, true, cx); + editor.unfold_ranges(std::slice::from_ref(&range_to_select), false, true, cx); editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges([range_to_select]) }); diff --git a/crates/terminal/src/terminal_hyperlinks.rs b/crates/terminal/src/terminal_hyperlinks.rs index 18675bbe02f94fc20995e90ba98799fbaf0fc92a..e318ae21bdcb755646e069c93ec8786f8197ad6a 100644 --- a/crates/terminal/src/terminal_hyperlinks.rs +++ b/crates/terminal/src/terminal_hyperlinks.rs @@ -52,7 +52,7 @@ pub(super) fn find_from_grid_point( ) -> Option<(String, bool, Match)> { let grid = term.grid(); let link = grid.index(point).hyperlink(); - let found_word = if link.is_some() { + let found_word = if let Some(ref url) = link { let mut min_index = point; loop { let new_min_index = min_index.sub(term, Boundary::Cursor, 1); @@ -73,7 +73,7 @@ pub(super) fn find_from_grid_point( } } - let url = link.unwrap().uri().to_owned(); + let url = url.uri().to_owned(); let url_match = min_index..=max_index; Some((url, true, url_match)) diff --git a/crates/theme_importer/Cargo.toml b/crates/theme_importer/Cargo.toml index 0fc3206d5c1152c82bdbe09c8ec2e0949dbce6ec..f9f7daa5b3bd7d48ce0631d26d6a3c21767e5d5e 100644 --- a/crates/theme_importer/Cargo.toml +++ b/crates/theme_importer/Cargo.toml @@ -15,7 +15,6 @@ gpui.workspace = true indexmap.workspace = true log.workspace = true palette.workspace = true -rust-embed.workspace = true serde.workspace = true serde_json.workspace = true serde_json_lenient.workspace = true diff --git a/crates/theme_importer/src/assets.rs b/crates/theme_importer/src/assets.rs deleted file mode 100644 index 56e6ed46ed5677ff6d82354316b826166dc6f048..0000000000000000000000000000000000000000 --- a/crates/theme_importer/src/assets.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::borrow::Cow; - -use anyhow::{Context as _, Result}; -use gpui::{AssetSource, SharedString}; -use rust_embed::RustEmbed; - -#[derive(RustEmbed)] -#[folder = "../../assets"] -#[include = "fonts/**/*"] -#[exclude = "*.DS_Store"] -pub struct Assets; - -impl AssetSource for Assets { - fn load(&self, path: &str) -> Result>> { - Self::get(path) - .map(|f| f.data) - .with_context(|| format!("could not find asset at path {path:?}")) - .map(Some) - } - - fn list(&self, path: &str) -> Result> { - Ok(Self::iter() - .filter(|p| p.starts_with(path)) - .map(SharedString::from) - .collect()) - } -} diff --git a/crates/theme_importer/src/main.rs b/crates/theme_importer/src/main.rs index c2ceee4cfcc0318ce1ab0efda4784f7930631737..ebb2840d0401d05b2bcac4e8c001dc30424f0fe5 100644 --- a/crates/theme_importer/src/main.rs +++ b/crates/theme_importer/src/main.rs @@ -1,4 +1,3 @@ -mod assets; mod color; mod vscode; diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 6b3a0b855f9221c34d2c534cd6f02853d937a8ce..8c407fdd3eab5a6b7189f67ff46b8ce76d1a428d 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -3911,7 +3911,7 @@ impl BackgroundScanner { let Ok(request) = path_prefix_request else { break }; log::trace!("adding path prefix {:?}", request.path); - let did_scan = self.forcibly_load_paths(&[request.path.clone()]).await; + let did_scan = self.forcibly_load_paths(std::slice::from_ref(&request.path)).await; if did_scan { let abs_path = { diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a7a9ac8295b3aaed6dabc2be50452493f7233f69..f80eab8fbcbd78e5bbf3bf4e8757bc6872146e1b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.87" +channel = "1.88" profile = "minimal" components = [ "rustfmt", "clippy" ] targets = [ From 2dece13d835fac02e046e0feb8f12f865d967943 Mon Sep 17 00:00:00 2001 From: Julia Ryan Date: Thu, 26 Jun 2025 13:31:24 -0700 Subject: [PATCH 006/107] nix: Update to access new rust toolchain version (#33476) Needed due to #33439 Release Notes: - N/A --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index fb5206fe3c5449383b510a48da90d505b7eb438e..fa0d51d90de9a6a9929241f6be212ea32e1432a2 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "crane": { "locked": { - "lastModified": 1748047550, - "narHash": "sha256-t0qLLqb4C1rdtiY8IFRH5KIapTY/n3Lqt57AmxEv9mk=", + "lastModified": 1750266157, + "narHash": "sha256-tL42YoNg9y30u7zAqtoGDNdTyXTi8EALDeCB13FtbQA=", "owner": "ipetkov", "repo": "crane", - "rev": "b718a78696060df6280196a6f992d04c87a16aef", + "rev": "e37c943371b73ed87faf33f7583860f81f1d5a48", "type": "github" }, "original": { @@ -33,10 +33,10 @@ "nixpkgs": { "locked": { "lastModified": 315532800, - "narHash": "sha256-3c6Axl3SGIXCixGtpSJaMXLkkSRihHDlLaGewDEgha0=", - "rev": "3108eaa516ae22c2360928589731a4f1581526ef", + "narHash": "sha256-j+zO+IHQ7VwEam0pjPExdbLT2rVioyVS3iq4bLO3GEc=", + "rev": "61c0f513911459945e2cb8bf333dc849f1b976ff", "type": "tarball", - "url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre806109.3108eaa516ae/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre821324.61c0f5139114/nixexprs.tar.xz" }, "original": { "type": "tarball", @@ -58,11 +58,11 @@ ] }, "locked": { - "lastModified": 1748227081, - "narHash": "sha256-RLnN7LBxhEdCJ6+rIL9sbhjBVDaR6jG377M/CLP/fmE=", + "lastModified": 1750964660, + "narHash": "sha256-YQ6EyFetjH1uy5JhdhRdPe6cuNXlYpMAQePFfZj4W7M=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "1cbe817fd8c64a9f77ba4d7861a4839b0b15983e", + "rev": "04f0fcfb1a50c63529805a798b4b5c21610ff390", "type": "github" }, "original": { From 343f155ab92800bcc587c0ba113d689a5073a4e7 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Thu, 26 Jun 2025 17:25:03 -0400 Subject: [PATCH 007/107] Update docs for Swift debugging (#33483) Release Notes: - N/A *or* Added/Fixed/Improved ... --- docs/src/debugger.md | 17 +---------------- docs/src/languages/swift.md | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/docs/src/debugger.md b/docs/src/debugger.md index fc95fb43b5e12c646ed287d6715fc1218336d54a..37930ac560a543301ab1a79bc1bbf9eb75115cb2 100644 --- a/docs/src/debugger.md +++ b/docs/src/debugger.md @@ -18,7 +18,7 @@ Zed supports a variety of debug adapters for different programming languages out - Python ([debugpy](https://github.com/microsoft/debugpy.git)): Provides debugging capabilities for Python applications, supporting features like remote debugging, multi-threaded debugging, and Django/Flask application debugging. -- LLDB ([CodeLLDB](https://github.com/vadimcn/codelldb.git)): A powerful debugger for Rust, C, C++, and some other compiled languages, offering low-level debugging features and support for Apple platforms. (For Swift, [see below](#swift).) +- LLDB ([CodeLLDB](https://github.com/vadimcn/codelldb.git)): A powerful debugger for Rust, C, C++, and some other compiled languages, offering low-level debugging features and support for Apple platforms. - GDB ([GDB](https://sourceware.org/gdb/)): The GNU Debugger, which supports debugging for multiple programming languages including C, C++, Go, and Rust, across various platforms. @@ -376,21 +376,6 @@ You might find yourself needing to connect to an existing instance of Delve that In such case Zed won't spawn a new instance of Delve, as it opts to use an existing one. The consequence of this is that _there will be no terminal_ in Zed; you have to interact with the Delve instance directly, as it handles stdin/stdout of the debuggee. -#### Swift - -Out-of-the-box support for debugging Swift programs will be provided by the Swift extension for Zed in the near future. In the meantime, the builtin CodeLLDB adapter can be used with some customization. On macOS, you'll need to locate the `lldb-dap` binary that's part of Apple's LLVM toolchain by running `which lldb-dap`, then point Zed to it in your project's `.zed/settings.json`: - -```json -{ - "dap": { - "CodeLLDB": { - "binary": "/Applications/Xcode.app/Contents/Developer/usr/bin/lldb-dap", // example value, may vary between systems - "args": [] - } - } -} -``` - #### Ruby To run a ruby task in the debugger, you will need to configure it in the `.zed/debug.json` file in your project. We don't yet have automatic detection of ruby tasks, nor do we support connecting to an existing process. diff --git a/docs/src/languages/swift.md b/docs/src/languages/swift.md index c3d5cfaa1a579455d3ef4fb9b354df63f7471199..9b056be5bc8869b18b78e9a2e64ea43db3d8ea90 100644 --- a/docs/src/languages/swift.md +++ b/docs/src/languages/swift.md @@ -5,7 +5,34 @@ Report issues to: [https://github.com/zed-extensions/swift/issues](https://githu - Tree-sitter: [alex-pinkus/tree-sitter-swift](https://github.com/alex-pinkus/tree-sitter-swift) - Language Server: [swiftlang/sourcekit-lsp](https://github.com/swiftlang/sourcekit-lsp) +- Debug Adapter: [`lldb-dap`](https://github.com/swiftlang/llvm-project/blob/next/lldb/tools/lldb-dap/README.md) -## Configuration +## Language Server Configuration You can modify the behavior of SourceKit LSP by creating a `.sourcekit-lsp/config.json` under your home directory or in your project root. See [SourceKit-LSP configuration file](https://github.com/swiftlang/sourcekit-lsp/blob/main/Documentation/Configuration%20File.md) for complete documentation. + +## Debugging + +The Swift extension provides a debug adapter for debugging Swift code. +Zed's name for the adapter (in the UI and `debug.json`) is `Swift`, and under the hood it uses [`lldb-dap`](https://github.com/swiftlang/llvm-project/blob/next/lldb/tools/lldb-dap/README.md), as provided by the Swift toolchain. +The extension tries to find an `lldb-dap` binary using `swiftly`, using `xcrun`, and by searching `$PATH`, in that order of preference. +The extension doesn't attempt to download `lldb-dap` if it's not found. + +### Examples + +#### Build and debug a Swift binary + +```json +[ + { + "label": "Debug Swift", + "build": { + "command": "swift", + "args": ["build"] + }, + "program": "$ZED_WORKTREE_ROOT/swift-app/.build/arm64-apple-macosx/debug/swift-app", + "request": "launch", + "adapter": "Swift" + } +] +``` From 2823771c0632f4a4c73e754c9d37689ae74699be Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Thu, 26 Jun 2025 19:27:21 -0300 Subject: [PATCH 008/107] Add design improvements to the LSP popover (#33485) Not the ideal design just yet as that will probably require a different approach altogether, but am pushing here just some reasonably small UI adjustments that will make this feel slightly nicer! Release Notes: - N/A --- assets/icons/bolt_filled_alt.svg | 3 + assets/icons/lsp_debug.svg | 12 +++ assets/icons/lsp_restart.svg | 4 + assets/icons/lsp_stop.svg | 4 + crates/icons/src/icons.rs | 4 + crates/language_tools/src/lsp_tool.rs | 132 ++++++++++++-------------- crates/zed/src/zed.rs | 2 +- 7 files changed, 90 insertions(+), 71 deletions(-) create mode 100644 assets/icons/bolt_filled_alt.svg create mode 100644 assets/icons/lsp_debug.svg create mode 100644 assets/icons/lsp_restart.svg create mode 100644 assets/icons/lsp_stop.svg diff --git a/assets/icons/bolt_filled_alt.svg b/assets/icons/bolt_filled_alt.svg new file mode 100644 index 0000000000000000000000000000000000000000..3c8938736279684981b03d168b11272d4e196d24 --- /dev/null +++ b/assets/icons/bolt_filled_alt.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/lsp_debug.svg b/assets/icons/lsp_debug.svg new file mode 100644 index 0000000000000000000000000000000000000000..aa49fcb6a214de6c5361d641d8236fbe4a0f6fc0 --- /dev/null +++ b/assets/icons/lsp_debug.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/lsp_restart.svg b/assets/icons/lsp_restart.svg new file mode 100644 index 0000000000000000000000000000000000000000..dfc68e7a9ea1b431a68e82d138d404fa656c3190 --- /dev/null +++ b/assets/icons/lsp_restart.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/lsp_stop.svg b/assets/icons/lsp_stop.svg new file mode 100644 index 0000000000000000000000000000000000000000..c6311d215582d38e865d6fbc56ac01c3d27fc28d --- /dev/null +++ b/assets/icons/lsp_stop.svg @@ -0,0 +1,4 @@ + + + + diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs index 7e1d7db5753ff1517902880bda4c6c9e24cfe582..ffbe148a3bb3725cfbafff9ddab53f2b39a609d1 100644 --- a/crates/icons/src/icons.rs +++ b/crates/icons/src/icons.rs @@ -45,6 +45,7 @@ pub enum IconName { Blocks, Bolt, BoltFilled, + BoltFilledAlt, Book, BookCopy, BookPlus, @@ -163,6 +164,9 @@ pub enum IconName { ListX, LoadCircle, LockOutlined, + LspDebug, + LspRestart, + LspStop, MagnifyingGlass, MailOpen, Maximize, diff --git a/crates/language_tools/src/lsp_tool.rs b/crates/language_tools/src/lsp_tool.rs index a7bfe70aaf7055173b6d7ce27ebde46570bf5493..140f9e3fe64e74ae2760a8a7cc8b1450b7f82497 100644 --- a/crates/language_tools/src/lsp_tool.rs +++ b/crates/language_tools/src/lsp_tool.rs @@ -10,7 +10,7 @@ use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector}; use picker::{Picker, PickerDelegate, popover_menu::PickerPopoverMenu}; use project::{LspStore, LspStoreEvent, project_settings::ProjectSettings}; use settings::{Settings as _, SettingsStore}; -use ui::{Context, IconButtonShape, Indicator, Tooltip, Window, prelude::*}; +use ui::{Context, Indicator, Tooltip, Window, prelude::*}; use workspace::{StatusItemView, Workspace}; @@ -181,16 +181,19 @@ impl LspPickerDelegate { buffer_servers.sort_by_key(|data| data.name().clone()); other_servers.sort_by_key(|data| data.name().clone()); + let mut other_servers_start_index = None; let mut new_lsp_items = Vec::with_capacity(buffer_servers.len() + other_servers.len() + 2); + if !buffer_servers.is_empty() { - new_lsp_items.push(LspItem::Header(SharedString::new("Current Buffer"))); + new_lsp_items.push(LspItem::Header(SharedString::new("This Buffer"))); new_lsp_items.extend(buffer_servers.into_iter().map(ServerData::into_lsp_item)); } + if !other_servers.is_empty() { other_servers_start_index = Some(new_lsp_items.len()); - new_lsp_items.push(LspItem::Header(SharedString::new("Other Active Servers"))); + new_lsp_items.push(LspItem::Header(SharedString::new("Other Servers"))); new_lsp_items.extend(other_servers.into_iter().map(ServerData::into_lsp_item)); } @@ -346,11 +349,13 @@ impl PickerDelegate for LspPickerDelegate { let is_other_server = self .other_servers_start_index .map_or(false, |start| ix >= start); + let server_binary_status; let server_health; let server_message; let server_id; let server_name; + match self.items.get(ix)? { LspItem::WithHealthCheck( language_server_id, @@ -372,9 +377,14 @@ impl PickerDelegate for LspPickerDelegate { } LspItem::Header(header) => { return Some( - h_flex() - .justify_center() - .child(Label::new(header.clone())) + div() + .px_2p5() + .mb_1() + .child( + Label::new(header.clone()) + .size(LabelSize::Small) + .color(Color::Muted), + ) .into_any_element(), ); } @@ -389,10 +399,12 @@ impl PickerDelegate for LspPickerDelegate { let can_stop = server_binary_status.is_none_or(|status| { matches!(status.status, BinaryStatus::None | BinaryStatus::Starting) }); + // TODO currently, Zed remote does not work well with the LSP logs // https://github.com/zed-industries/zed/issues/28557 let has_logs = lsp_store.read(cx).as_local().is_some() && lsp_logs.read(cx).has_server_logs(&server_selector); + let status_color = server_binary_status .and_then(|binary_status| match binary_status.status { BinaryStatus::None => None, @@ -414,27 +426,28 @@ impl PickerDelegate for LspPickerDelegate { Some( h_flex() - .w_full() + .px_1() + .gap_1() .justify_between() - .gap_2() .child( h_flex() .id("server-status-indicator") + .px_2() .gap_2() .child(Indicator::dot().color(status_color)) .child(Label::new(server_name.0.clone())) .when_some(server_message.clone(), |div, server_message| { - div.tooltip(move |_, cx| Tooltip::simple(server_message.clone(), cx)) + div.tooltip(Tooltip::text(server_message.clone())) }), ) .child( h_flex() - .gap_1() - .when(has_logs, |div| { - div.child( - IconButton::new("debug-language-server", IconName::MessageBubbles) - .icon_size(IconSize::XSmall) - .tooltip(|_, cx| Tooltip::simple("Debug Language Server", cx)) + .when(has_logs, |button_list| { + button_list.child( + IconButton::new("debug-language-server", IconName::LspDebug) + .icon_size(IconSize::Small) + .alpha(0.8) + .tooltip(Tooltip::text("Debug Language Server")) .on_click({ let workspace = workspace.clone(); let lsp_logs = lsp_logs.downgrade(); @@ -454,11 +467,12 @@ impl PickerDelegate for LspPickerDelegate { }), ) }) - .when(can_stop, |div| { - div.child( - IconButton::new("stop-server", IconName::Stop) + .when(can_stop, |button_list| { + button_list.child( + IconButton::new("stop-server", IconName::LspStop) .icon_size(IconSize::Small) - .tooltip(|_, cx| Tooltip::simple("Stop server", cx)) + .alpha(0.8) + .tooltip(Tooltip::text("Stop Server")) .on_click({ let lsp_store = lsp_store.downgrade(); let server_selector = server_selector.clone(); @@ -479,9 +493,10 @@ impl PickerDelegate for LspPickerDelegate { ) }) .child( - IconButton::new("restart-server", IconName::Rerun) - .icon_size(IconSize::XSmall) - .tooltip(|_, cx| Tooltip::simple("Restart server", cx)) + IconButton::new("restart-server", IconName::LspRestart) + .icon_size(IconSize::Small) + .alpha(0.8) + .tooltip(Tooltip::text("Restart Server")) .on_click({ let state = self.state.clone(); let workspace = workspace.clone(); @@ -558,7 +573,6 @@ impl PickerDelegate for LspPickerDelegate { }), ), ) - .cursor_default() .into_any_element(), ) } @@ -573,49 +587,28 @@ impl PickerDelegate for LspPickerDelegate { } fn render_footer(&self, _: &mut Window, cx: &mut Context>) -> Option { - if self.items.is_empty() { - Some( - h_flex() - .w_full() - .border_color(cx.theme().colors().border_variant) - .child( - Button::new("stop-all-servers", "Stop all servers") - .disabled(true) - .on_click(move |_, _, _| {}) - .full_width(), - ) - .into_any_element(), - ) - } else { - let lsp_store = self.state.read(cx).lsp_store.clone(); - Some( - h_flex() - .w_full() - .border_color(cx.theme().colors().border_variant) - .child( - Button::new("stop-all-servers", "Stop all servers") - .on_click({ - move |_, _, cx| { - lsp_store - .update(cx, |lsp_store, cx| { - lsp_store.stop_all_language_servers(cx); - }) - .ok(); - } - }) - .full_width(), - ) - .into_any_element(), - ) - } - } + let lsp_store = self.state.read(cx).lsp_store.clone(); - fn separators_after_indices(&self) -> Vec { - if self.items.is_empty() { - Vec::new() - } else { - vec![self.items.len() - 1] - } + Some( + div() + .p_1() + .border_t_1() + .border_color(cx.theme().colors().border_variant) + .child( + Button::new("stop-all-servers", "Stop All Servers") + .disabled(self.items.is_empty()) + .on_click({ + move |_, _, cx| { + lsp_store + .update(cx, |lsp_store, cx| { + lsp_store.stop_all_language_servers(cx); + }) + .ok(); + } + }), + ) + .into_any_element(), + ) } } @@ -911,13 +904,12 @@ impl Render for LspTool { div().child( PickerPopoverMenu::new( lsp_picker.clone(), - IconButton::new("zed-lsp-tool-button", IconName::Bolt) + IconButton::new("zed-lsp-tool-button", IconName::BoltFilledAlt) .when_some(indicator, IconButton::indicator) - .shape(IconButtonShape::Square) - .icon_size(IconSize::XSmall) + .icon_size(IconSize::Small) .indicator_border_color(Some(cx.theme().colors().status_bar_background)), - move |_, cx| Tooltip::simple("Language servers", cx), - Corner::BottomRight, + move |_, cx| Tooltip::simple("Language Servers", cx), + Corner::BottomLeft, cx, ) .render(window, cx), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c57a9b576aa09139ec039de01b9569438a086f3a..5ab4b672ae4023d8a485806146992181f4ec7d7b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -332,8 +332,8 @@ pub fn initialize_workspace( cx.new(|_| go_to_line::cursor_position::CursorPosition::new(workspace)); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(search_button, window, cx); - status_bar.add_left_item(diagnostic_summary, window, cx); status_bar.add_left_item(lsp_tool, window, cx); + status_bar.add_left_item(diagnostic_summary, window, cx); status_bar.add_left_item(activity_indicator, window, cx); status_bar.add_right_item(edit_prediction_button, window, cx); status_bar.add_right_item(active_buffer_language, window, cx); From ba1c05abf27f4285fb856fe4594e10c83e6dd01e Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Thu, 26 Jun 2025 20:52:26 -0500 Subject: [PATCH 009/107] keymap: Add ability to update user keymaps (#33487) Closes #ISSUE The ability to update user keybindings in their keymap is required for #32436. This PR adds the ability to do so, reusing much of the existing infrastructure for updating settings JSON files. However, the existing JSON update functionality was intended to work only with objects, therefore, this PR simply wraps the object updating code with non-general keymap-specific array updating logic, that only works for top-level arrays and can only append or update entries in said top-level arrays. This limited API is reflected in the limited operations that the new `update_keymap` method on `KeymapFile` can take as arguments. Additionally, this PR pulls out the existing JSON updating code into its own module (where array updating code has been added) and adds a significant number of tests (hence the high line count in the diff) Release Notes: - N/A *or* Added/Fixed/Improved ... --- Cargo.lock | 2 +- crates/settings/Cargo.toml | 2 +- crates/settings/src/json_schema.rs | 75 -- crates/settings/src/keymap_file.rs | 466 ++++++- crates/settings/src/settings.rs | 6 +- crates/settings/src/settings_file.rs | 7 +- crates/settings/src/settings_json.rs | 1646 +++++++++++++++++++++++++ crates/settings/src/settings_store.rs | 313 +---- 8 files changed, 2138 insertions(+), 379 deletions(-) delete mode 100644 crates/settings/src/json_schema.rs create mode 100644 crates/settings/src/settings_json.rs diff --git a/Cargo.lock b/Cargo.lock index 473666a1a192e620c8684f5e6cb0705dc83c5da1..7778b00ee775fa49c6a6f756c215baf48294455b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14554,12 +14554,12 @@ dependencies = [ "serde_json", "serde_json_lenient", "smallvec", - "streaming-iterator", "tree-sitter", "tree-sitter-json", "unindent", "util", "workspace-hack", + "zlog", ] [[package]] diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 7817169aa299be14902ff83ef8d34b39c4e19049..892d4dea8b2daac7395bcbe273635fbb535a0e53 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -33,11 +33,11 @@ serde_derive.workspace = true serde_json.workspace = true serde_json_lenient.workspace = true smallvec.workspace = true -streaming-iterator.workspace = true tree-sitter-json.workspace = true tree-sitter.workspace = true util.workspace = true workspace-hack.workspace = true +zlog.workspace = true [dev-dependencies] fs = { workspace = true, features = ["test-support"] } diff --git a/crates/settings/src/json_schema.rs b/crates/settings/src/json_schema.rs deleted file mode 100644 index 5fd340fffa8f9d3e60d3910cfe9e1d2506fded5a..0000000000000000000000000000000000000000 --- a/crates/settings/src/json_schema.rs +++ /dev/null @@ -1,75 +0,0 @@ -use schemars::schema::{ - ArrayValidation, InstanceType, RootSchema, Schema, SchemaObject, SingleOrVec, -}; -use serde_json::Value; - -pub struct SettingsJsonSchemaParams<'a> { - pub language_names: &'a [String], - pub font_names: &'a [String], -} - -impl SettingsJsonSchemaParams<'_> { - pub fn font_family_schema(&self) -> Schema { - let available_fonts: Vec<_> = self.font_names.iter().cloned().map(Value::String).collect(); - - SchemaObject { - instance_type: Some(InstanceType::String.into()), - enum_values: Some(available_fonts), - ..Default::default() - } - .into() - } - - pub fn font_fallback_schema(&self) -> Schema { - SchemaObject { - instance_type: Some(SingleOrVec::Vec(vec![ - InstanceType::Array, - InstanceType::Null, - ])), - array: Some(Box::new(ArrayValidation { - items: Some(schemars::schema::SingleOrVec::Single(Box::new( - self.font_family_schema(), - ))), - unique_items: Some(true), - ..Default::default() - })), - ..Default::default() - } - .into() - } -} - -type PropertyName<'a> = &'a str; -type ReferencePath<'a> = &'a str; - -/// Modifies the provided [`RootSchema`] by adding references to all of the specified properties. -/// -/// # Examples -/// -/// ``` -/// # let root_schema = RootSchema::default(); -/// add_references_to_properties(&mut root_schema, &[ -/// ("property_a", "#/definitions/DefinitionA"), -/// ("property_b", "#/definitions/DefinitionB"), -/// ]) -/// ``` -pub fn add_references_to_properties( - root_schema: &mut RootSchema, - properties_with_references: &[(PropertyName, ReferencePath)], -) { - for (property, definition) in properties_with_references { - let Some(schema) = root_schema.schema.object().properties.get_mut(*property) else { - log::warn!("property '{property}' not found in JSON schema"); - continue; - }; - - match schema { - Schema::Object(schema) => { - schema.reference = Some(definition.to_string()); - } - Schema::Bool(_) => { - // Boolean schemas can't have references. - } - } - } -} diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index 551920c8a038d2b3c3ad2432bbfa0da0b857fcac..833882dd608211a54b8dab217094739864177f15 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Context as _, Result}; use collections::{BTreeMap, HashMap, IndexMap}; use fs::Fs; use gpui::{ @@ -18,7 +18,10 @@ use util::{ markdown::{MarkdownEscaped, MarkdownInlineCode, MarkdownString}, }; -use crate::{SettingsAssets, settings_store::parse_json_with_comments}; +use crate::{ + SettingsAssets, append_top_level_array_value_in_json_text, parse_json_with_comments, + replace_top_level_array_value_in_json_text, +}; pub trait KeyBindingValidator: Send + Sync { fn action_type_id(&self) -> TypeId; @@ -218,7 +221,7 @@ impl KeymapFile { key_bindings: Vec::new(), }; } - let keymap_file = match parse_json_with_comments::(content) { + let keymap_file = match Self::parse(content) { Ok(keymap_file) => keymap_file, Err(error) => { return KeymapFileLoadResult::JsonParseFailure { error }; @@ -629,9 +632,145 @@ impl KeymapFile { } } } + + pub fn update_keybinding<'a>( + mut operation: KeybindUpdateOperation<'a>, + mut keymap_contents: String, + tab_size: usize, + ) -> Result { + // if trying to replace a keybinding that is not user-defined, treat it as an add operation + match operation { + KeybindUpdateOperation::Replace { + target_source, + source, + .. + } if target_source != KeybindSource::User => { + operation = KeybindUpdateOperation::Add(source); + } + _ => {} + } + + // Sanity check that keymap contents are valid, even though we only use it for Replace. + // We don't want to modify the file if it's invalid. + let keymap = Self::parse(&keymap_contents).context("Failed to parse keymap")?; + + if let KeybindUpdateOperation::Replace { source, target, .. } = operation { + let mut found_index = None; + let target_action_value = target + .action_value() + .context("Failed to generate target action JSON value")?; + let source_action_value = source + .action_value() + .context("Failed to generate source action JSON value")?; + 'sections: for (index, section) in keymap.sections().enumerate() { + if section.context != target.context.unwrap_or("") { + continue; + } + if section.use_key_equivalents != target.use_key_equivalents { + continue; + } + let Some(bindings) = §ion.bindings else { + continue; + }; + for (keystrokes, action) in bindings { + if keystrokes != target.keystrokes { + continue; + } + if action.0 != target_action_value { + continue; + } + found_index = Some(index); + break 'sections; + } + } + + if let Some(index) = found_index { + let (replace_range, replace_value) = replace_top_level_array_value_in_json_text( + &keymap_contents, + &["bindings", target.keystrokes], + Some(&source_action_value), + Some(source.keystrokes), + index, + tab_size, + ) + .context("Failed to replace keybinding")?; + keymap_contents.replace_range(replace_range, &replace_value); + + return Ok(keymap_contents); + } else { + log::warn!( + "Failed to find keybinding to update `{:?} -> {}` creating new binding for `{:?} -> {}` instead", + target.keystrokes, + target_action_value, + source.keystrokes, + source_action_value, + ); + operation = KeybindUpdateOperation::Add(source); + } + } + + if let KeybindUpdateOperation::Add(keybinding) = operation { + let mut value = serde_json::Map::with_capacity(4); + if let Some(context) = keybinding.context { + value.insert("context".to_string(), context.into()); + } + if keybinding.use_key_equivalents { + value.insert("use_key_equivalents".to_string(), true.into()); + } + + value.insert("bindings".to_string(), { + let mut bindings = serde_json::Map::new(); + let action = keybinding.action_value()?; + bindings.insert(keybinding.keystrokes.into(), action); + bindings.into() + }); + + let (replace_range, replace_value) = append_top_level_array_value_in_json_text( + &keymap_contents, + &value.into(), + tab_size, + )?; + keymap_contents.replace_range(replace_range, &replace_value); + } + return Ok(keymap_contents); + } } -#[derive(Clone, Copy)] +pub enum KeybindUpdateOperation<'a> { + Replace { + /// Describes the keybind to create + source: KeybindUpdateTarget<'a>, + /// Describes the keybind to remove + target: KeybindUpdateTarget<'a>, + target_source: KeybindSource, + }, + Add(KeybindUpdateTarget<'a>), +} + +pub struct KeybindUpdateTarget<'a> { + context: Option<&'a str>, + keystrokes: &'a str, + action_name: &'a str, + use_key_equivalents: bool, + input: Option<&'a str>, +} + +impl<'a> KeybindUpdateTarget<'a> { + fn action_value(&self) -> Result { + let action_name: Value = self.action_name.into(); + let value = match self.input { + Some(input) => { + let input = serde_json::from_str::(input) + .context("Failed to parse action input as JSON")?; + serde_json::json!([action_name, input]) + } + None => action_name, + }; + return Ok(value); + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] pub enum KeybindSource { User, Default, @@ -688,7 +827,12 @@ impl From for KeyBindingMetaIndex { #[cfg(test)] mod tests { - use crate::KeymapFile; + use unindent::Unindent; + + use crate::{ + KeybindSource, KeymapFile, + keymap_file::{KeybindUpdateOperation, KeybindUpdateTarget}, + }; #[test] fn can_deserialize_keymap_with_trailing_comma() { @@ -704,4 +848,316 @@ mod tests { }; KeymapFile::parse(json).unwrap(); } + + #[test] + fn keymap_update() { + zlog::init_test(); + #[track_caller] + fn check_keymap_update( + input: impl ToString, + operation: KeybindUpdateOperation, + expected: impl ToString, + ) { + let result = KeymapFile::update_keybinding(operation, input.to_string(), 4) + .expect("Update succeeded"); + pretty_assertions::assert_eq!(expected.to_string(), result); + } + + check_keymap_update( + "[]", + KeybindUpdateOperation::Add(KeybindUpdateTarget { + keystrokes: "ctrl-a", + action_name: "zed::SomeAction", + context: None, + use_key_equivalents: false, + input: None, + }), + r#"[ + { + "bindings": { + "ctrl-a": "zed::SomeAction" + } + } + ]"# + .unindent(), + ); + + check_keymap_update( + r#"[ + { + "bindings": { + "ctrl-a": "zed::SomeAction" + } + } + ]"# + .unindent(), + KeybindUpdateOperation::Add(KeybindUpdateTarget { + keystrokes: "ctrl-b", + action_name: "zed::SomeOtherAction", + context: None, + use_key_equivalents: false, + input: None, + }), + r#"[ + { + "bindings": { + "ctrl-a": "zed::SomeAction" + } + }, + { + "bindings": { + "ctrl-b": "zed::SomeOtherAction" + } + } + ]"# + .unindent(), + ); + + check_keymap_update( + r#"[ + { + "bindings": { + "ctrl-a": "zed::SomeAction" + } + } + ]"# + .unindent(), + KeybindUpdateOperation::Add(KeybindUpdateTarget { + keystrokes: "ctrl-b", + action_name: "zed::SomeOtherAction", + context: None, + use_key_equivalents: false, + input: Some(r#"{"foo": "bar"}"#), + }), + r#"[ + { + "bindings": { + "ctrl-a": "zed::SomeAction" + } + }, + { + "bindings": { + "ctrl-b": [ + "zed::SomeOtherAction", + { + "foo": "bar" + } + ] + } + } + ]"# + .unindent(), + ); + + check_keymap_update( + r#"[ + { + "bindings": { + "ctrl-a": "zed::SomeAction" + } + } + ]"# + .unindent(), + KeybindUpdateOperation::Add(KeybindUpdateTarget { + keystrokes: "ctrl-b", + action_name: "zed::SomeOtherAction", + context: Some("Zed > Editor && some_condition = true"), + use_key_equivalents: true, + input: Some(r#"{"foo": "bar"}"#), + }), + r#"[ + { + "bindings": { + "ctrl-a": "zed::SomeAction" + } + }, + { + "context": "Zed > Editor && some_condition = true", + "use_key_equivalents": true, + "bindings": { + "ctrl-b": [ + "zed::SomeOtherAction", + { + "foo": "bar" + } + ] + } + } + ]"# + .unindent(), + ); + + check_keymap_update( + r#"[ + { + "bindings": { + "ctrl-a": "zed::SomeAction" + } + } + ]"# + .unindent(), + KeybindUpdateOperation::Replace { + target: KeybindUpdateTarget { + keystrokes: "ctrl-a", + action_name: "zed::SomeAction", + context: None, + use_key_equivalents: false, + input: None, + }, + source: KeybindUpdateTarget { + keystrokes: "ctrl-b", + action_name: "zed::SomeOtherAction", + context: None, + use_key_equivalents: false, + input: Some(r#"{"foo": "bar"}"#), + }, + target_source: KeybindSource::Base, + }, + r#"[ + { + "bindings": { + "ctrl-a": "zed::SomeAction" + } + }, + { + "bindings": { + "ctrl-b": [ + "zed::SomeOtherAction", + { + "foo": "bar" + } + ] + } + } + ]"# + .unindent(), + ); + + check_keymap_update( + r#"[ + { + "bindings": { + "ctrl-a": "zed::SomeAction" + } + } + ]"# + .unindent(), + KeybindUpdateOperation::Replace { + target: KeybindUpdateTarget { + keystrokes: "ctrl-a", + action_name: "zed::SomeAction", + context: None, + use_key_equivalents: false, + input: None, + }, + source: KeybindUpdateTarget { + keystrokes: "ctrl-b", + action_name: "zed::SomeOtherAction", + context: None, + use_key_equivalents: false, + input: Some(r#"{"foo": "bar"}"#), + }, + target_source: KeybindSource::User, + }, + r#"[ + { + "bindings": { + "ctrl-b": [ + "zed::SomeOtherAction", + { + "foo": "bar" + } + ] + } + } + ]"# + .unindent(), + ); + + check_keymap_update( + r#"[ + { + "bindings": { + "ctrl-a": "zed::SomeAction" + } + } + ]"# + .unindent(), + KeybindUpdateOperation::Replace { + target: KeybindUpdateTarget { + keystrokes: "ctrl-a", + action_name: "zed::SomeNonexistentAction", + context: None, + use_key_equivalents: false, + input: None, + }, + source: KeybindUpdateTarget { + keystrokes: "ctrl-b", + action_name: "zed::SomeOtherAction", + context: None, + use_key_equivalents: false, + input: None, + }, + target_source: KeybindSource::User, + }, + r#"[ + { + "bindings": { + "ctrl-a": "zed::SomeAction" + } + }, + { + "bindings": { + "ctrl-b": "zed::SomeOtherAction" + } + } + ]"# + .unindent(), + ); + + check_keymap_update( + r#"[ + { + "bindings": { + // some comment + "ctrl-a": "zed::SomeAction" + // some other comment + } + } + ]"# + .unindent(), + KeybindUpdateOperation::Replace { + target: KeybindUpdateTarget { + keystrokes: "ctrl-a", + action_name: "zed::SomeAction", + context: None, + use_key_equivalents: false, + input: None, + }, + source: KeybindUpdateTarget { + keystrokes: "ctrl-b", + action_name: "zed::SomeOtherAction", + context: None, + use_key_equivalents: false, + input: Some(r#"{"foo": "bar"}"#), + }, + target_source: KeybindSource::User, + }, + r#"[ + { + "bindings": { + // some comment + "ctrl-b": [ + "zed::SomeOtherAction", + { + "foo": "bar" + } + ] + // some other comment + } + } + ]"# + .unindent(), + ); + } } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index a01414b0b29f95dbadac88d5f577e5b0809322ff..0fe2c48e926323d1a68d872b94f9763478c4a196 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -1,8 +1,8 @@ mod editable_setting_control; -mod json_schema; mod key_equivalents; mod keymap_file; mod settings_file; +mod settings_json; mod settings_store; mod vscode_import; @@ -12,16 +12,16 @@ use std::{borrow::Cow, fmt, str}; use util::asset_str; pub use editable_setting_control::*; -pub use json_schema::*; pub use key_equivalents::*; pub use keymap_file::{ KeyBindingValidator, KeyBindingValidatorRegistration, KeybindSource, KeymapFile, KeymapFileLoadResult, }; pub use settings_file::*; +pub use settings_json::*; pub use settings_store::{ InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources, - SettingsStore, parse_json_with_comments, + SettingsStore, }; pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource}; diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 0fcdcde8ad886e6a6969d3c23e958a67396cb399..c43f3e79e8cf2edcee9d49e5dc3268295ff41439 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -9,10 +9,9 @@ pub const EMPTY_THEME_NAME: &str = "empty-theme"; #[cfg(any(test, feature = "test-support"))] pub fn test_settings() -> String { - let mut value = crate::settings_store::parse_json_with_comments::( - crate::default_settings().as_ref(), - ) - .unwrap(); + let mut value = + crate::parse_json_with_comments::(crate::default_settings().as_ref()) + .unwrap(); #[cfg(not(target_os = "windows"))] util::merge_non_null_json_value_into( serde_json::json!({ diff --git a/crates/settings/src/settings_json.rs b/crates/settings/src/settings_json.rs new file mode 100644 index 0000000000000000000000000000000000000000..1a045607e6645829c2e8a47af4c46cd0fd0b8fa7 --- /dev/null +++ b/crates/settings/src/settings_json.rs @@ -0,0 +1,1646 @@ +use std::{ops::Range, sync::LazyLock}; + +use anyhow::Result; +use schemars::schema::{ + ArrayValidation, InstanceType, RootSchema, Schema, SchemaObject, SingleOrVec, +}; +use serde::{Serialize, de::DeserializeOwned}; +use serde_json::Value; +use tree_sitter::{Query, StreamingIterator as _}; +use util::RangeExt; + +pub struct SettingsJsonSchemaParams<'a> { + pub language_names: &'a [String], + pub font_names: &'a [String], +} + +impl SettingsJsonSchemaParams<'_> { + pub fn font_family_schema(&self) -> Schema { + let available_fonts: Vec<_> = self.font_names.iter().cloned().map(Value::String).collect(); + + SchemaObject { + instance_type: Some(InstanceType::String.into()), + enum_values: Some(available_fonts), + ..Default::default() + } + .into() + } + + pub fn font_fallback_schema(&self) -> Schema { + SchemaObject { + instance_type: Some(SingleOrVec::Vec(vec![ + InstanceType::Array, + InstanceType::Null, + ])), + array: Some(Box::new(ArrayValidation { + items: Some(schemars::schema::SingleOrVec::Single(Box::new( + self.font_family_schema(), + ))), + unique_items: Some(true), + ..Default::default() + })), + ..Default::default() + } + .into() + } +} + +type PropertyName<'a> = &'a str; +type ReferencePath<'a> = &'a str; + +/// Modifies the provided [`RootSchema`] by adding references to all of the specified properties. +/// +/// # Examples +/// +/// ``` +/// # let root_schema = RootSchema::default(); +/// add_references_to_properties(&mut root_schema, &[ +/// ("property_a", "#/definitions/DefinitionA"), +/// ("property_b", "#/definitions/DefinitionB"), +/// ]) +/// ``` +pub fn add_references_to_properties( + root_schema: &mut RootSchema, + properties_with_references: &[(PropertyName, ReferencePath)], +) { + for (property, definition) in properties_with_references { + let Some(schema) = root_schema.schema.object().properties.get_mut(*property) else { + log::warn!("property '{property}' not found in JSON schema"); + continue; + }; + + match schema { + Schema::Object(schema) => { + schema.reference = Some(definition.to_string()); + } + Schema::Bool(_) => { + // Boolean schemas can't have references. + } + } + } +} + +pub fn update_value_in_json_text<'a>( + text: &mut String, + key_path: &mut Vec<&'a str>, + tab_size: usize, + old_value: &'a Value, + new_value: &'a Value, + preserved_keys: &[&str], + edits: &mut Vec<(Range, String)>, +) { + // If the old and new values are both objects, then compare them key by key, + // preserving the comments and formatting of the unchanged parts. Otherwise, + // replace the old value with the new value. + if let (Value::Object(old_object), Value::Object(new_object)) = (old_value, new_value) { + for (key, old_sub_value) in old_object.iter() { + key_path.push(key); + if let Some(new_sub_value) = new_object.get(key) { + // Key exists in both old and new, recursively update + update_value_in_json_text( + text, + key_path, + tab_size, + old_sub_value, + new_sub_value, + preserved_keys, + edits, + ); + } else { + // Key was removed from new object, remove the entire key-value pair + let (range, replacement) = + replace_value_in_json_text(text, key_path, 0, None, None); + text.replace_range(range.clone(), &replacement); + edits.push((range, replacement)); + } + key_path.pop(); + } + for (key, new_sub_value) in new_object.iter() { + key_path.push(key); + if !old_object.contains_key(key) { + update_value_in_json_text( + text, + key_path, + tab_size, + &Value::Null, + new_sub_value, + preserved_keys, + edits, + ); + } + key_path.pop(); + } + } else if key_path + .last() + .map_or(false, |key| preserved_keys.contains(key)) + || old_value != new_value + { + let mut new_value = new_value.clone(); + if let Some(new_object) = new_value.as_object_mut() { + new_object.retain(|_, v| !v.is_null()); + } + let (range, replacement) = + replace_value_in_json_text(text, key_path, tab_size, Some(&new_value), None); + text.replace_range(range.clone(), &replacement); + edits.push((range, replacement)); + } +} + +/// * `replace_key` - When an exact key match according to `key_path` is found, replace the key with `replace_key` if `Some`. +fn replace_value_in_json_text( + text: &str, + key_path: &[&str], + tab_size: usize, + new_value: Option<&Value>, + replace_key: Option<&str>, +) -> (Range, String) { + static PAIR_QUERY: LazyLock = LazyLock::new(|| { + Query::new( + &tree_sitter_json::LANGUAGE.into(), + "(pair key: (string) @key value: (_) @value)", + ) + .expect("Failed to create PAIR_QUERY") + }); + + let mut parser = tree_sitter::Parser::new(); + parser + .set_language(&tree_sitter_json::LANGUAGE.into()) + .unwrap(); + let syntax_tree = parser.parse(text, None).unwrap(); + + let mut cursor = tree_sitter::QueryCursor::new(); + + let mut depth = 0; + let mut last_value_range = 0..0; + let mut first_key_start = None; + let mut existing_value_range = 0..text.len(); + + let mut matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes()); + while let Some(mat) = matches.next() { + if mat.captures.len() != 2 { + continue; + } + + let key_range = mat.captures[0].node.byte_range(); + let value_range = mat.captures[1].node.byte_range(); + + // Don't enter sub objects until we find an exact + // match for the current keypath + if last_value_range.contains_inclusive(&value_range) { + continue; + } + + last_value_range = value_range.clone(); + + if key_range.start > existing_value_range.end { + break; + } + + first_key_start.get_or_insert(key_range.start); + + let found_key = text + .get(key_range.clone()) + .map(|key_text| { + depth < key_path.len() && key_text == format!("\"{}\"", key_path[depth]) + }) + .unwrap_or(false); + + if found_key { + existing_value_range = value_range; + // Reset last value range when increasing in depth + last_value_range = existing_value_range.start..existing_value_range.start; + depth += 1; + + if depth == key_path.len() { + break; + } + + first_key_start = None; + } + } + + // We found the exact key we want + if depth == key_path.len() { + if let Some(new_value) = new_value { + let new_val = to_pretty_json(new_value, tab_size, tab_size * depth); + if let Some(replace_key) = replace_key { + let new_key = format!("\"{}\": ", replace_key); + if let Some(key_start) = text[..existing_value_range.start].rfind('"') { + if let Some(prev_key_start) = text[..key_start].rfind('"') { + existing_value_range.start = prev_key_start; + } else { + existing_value_range.start = key_start; + } + } + (existing_value_range, new_key + &new_val) + } else { + (existing_value_range, new_val) + } + } else { + let mut removal_start = first_key_start.unwrap_or(existing_value_range.start); + let mut removal_end = existing_value_range.end; + + // Find the actual key position by looking for the key in the pair + // We need to extend the range to include the key, not just the value + if let Some(key_start) = text[..existing_value_range.start].rfind('"') { + if let Some(prev_key_start) = text[..key_start].rfind('"') { + removal_start = prev_key_start; + } else { + removal_start = key_start; + } + } + + // Look backward for a preceding comma first + let preceding_text = text.get(0..removal_start).unwrap_or(""); + if let Some(comma_pos) = preceding_text.rfind(',') { + // Check if there are only whitespace characters between the comma and our key + let between_comma_and_key = text.get(comma_pos + 1..removal_start).unwrap_or(""); + if between_comma_and_key.trim().is_empty() { + removal_start = comma_pos; + } + } + + if let Some(remaining_text) = text.get(existing_value_range.end..) { + let mut chars = remaining_text.char_indices(); + while let Some((offset, ch)) = chars.next() { + if ch == ',' { + removal_end = existing_value_range.end + offset + 1; + // Also consume whitespace after the comma + while let Some((_, next_ch)) = chars.next() { + if next_ch.is_whitespace() { + removal_end += next_ch.len_utf8(); + } else { + break; + } + } + break; + } else if !ch.is_whitespace() { + break; + } + } + } + (removal_start..removal_end, String::new()) + } + } else { + // We have key paths, construct the sub objects + let new_key = key_path[depth]; + + // We don't have the key, construct the nested objects + let mut new_value = + serde_json::to_value(new_value.unwrap_or(&serde_json::Value::Null)).unwrap(); + for key in key_path[(depth + 1)..].iter().rev() { + new_value = serde_json::json!({ key.to_string(): new_value }); + } + + if let Some(first_key_start) = first_key_start { + let mut row = 0; + let mut column = 0; + for (ix, char) in text.char_indices() { + if ix == first_key_start { + break; + } + if char == '\n' { + row += 1; + column = 0; + } else { + column += char.len_utf8(); + } + } + + if row > 0 { + // depth is 0 based, but division needs to be 1 based. + let new_val = to_pretty_json(&new_value, column / (depth + 1), column); + let space = ' '; + let content = format!("\"{new_key}\": {new_val},\n{space:width$}", width = column); + (first_key_start..first_key_start, content) + } else { + let new_val = serde_json::to_string(&new_value).unwrap(); + let mut content = format!(r#""{new_key}": {new_val},"#); + content.push(' '); + (first_key_start..first_key_start, content) + } + } else { + new_value = serde_json::json!({ new_key.to_string(): new_value }); + let indent_prefix_len = 4 * depth; + let mut new_val = to_pretty_json(&new_value, 4, indent_prefix_len); + if depth == 0 { + new_val.push('\n'); + } + // best effort to keep comments with best effort indentation + let mut replace_text = &text[existing_value_range.clone()]; + while let Some(comment_start) = replace_text.rfind("//") { + if let Some(comment_end) = replace_text[comment_start..].find('\n') { + let mut comment_with_indent_start = replace_text[..comment_start] + .rfind('\n') + .unwrap_or(comment_start); + if !replace_text[comment_with_indent_start..comment_start] + .trim() + .is_empty() + { + comment_with_indent_start = comment_start; + } + new_val.insert_str( + 1, + &replace_text[comment_with_indent_start..comment_start + comment_end], + ); + } + replace_text = &replace_text[..comment_start]; + } + + (existing_value_range, new_val) + } + } +} + +const TS_DOCUMENT_KIND: &'static str = "document"; +const TS_ARRAY_KIND: &'static str = "array"; +const TS_COMMENT_KIND: &'static str = "comment"; + +pub fn replace_top_level_array_value_in_json_text( + text: &str, + key_path: &[&str], + new_value: Option<&Value>, + replace_key: Option<&str>, + array_index: usize, + tab_size: usize, +) -> Result<(Range, String)> { + let mut parser = tree_sitter::Parser::new(); + parser + .set_language(&tree_sitter_json::LANGUAGE.into()) + .unwrap(); + let syntax_tree = parser.parse(text, None).unwrap(); + + let mut cursor = syntax_tree.walk(); + + if cursor.node().kind() == TS_DOCUMENT_KIND { + anyhow::ensure!( + cursor.goto_first_child(), + "Document empty - No top level array" + ); + } + + while cursor.node().kind() != TS_ARRAY_KIND { + anyhow::ensure!(cursor.goto_next_sibling(), "EOF - No top level array"); + } + + // false if no children + // + cursor.goto_first_child(); + debug_assert_eq!(cursor.node().kind(), "["); + + let mut index = 0; + + while index <= array_index { + let node = cursor.node(); + if !matches!(node.kind(), "[" | "]" | TS_COMMENT_KIND | ",") + && !node.is_extra() + && !node.is_missing() + { + if index == array_index { + break; + } + index += 1; + } + if !cursor.goto_next_sibling() { + if let Some(new_value) = new_value { + return append_top_level_array_value_in_json_text(text, new_value, tab_size); + } else { + return Ok((0..0, String::new())); + } + } + } + + let range = cursor.node().range(); + let indent_width = range.start_point.column; + let offset = range.start_byte; + let value_str = &text[range.start_byte..range.end_byte]; + let needs_indent = range.start_point.row > 0; + + let (mut replace_range, mut replace_value) = + replace_value_in_json_text(value_str, key_path, tab_size, new_value, replace_key); + + replace_range.start += offset; + replace_range.end += offset; + + if needs_indent { + let increased_indent = format!("\n{space:width$}", space = ' ', width = indent_width); + replace_value = replace_value.replace('\n', &increased_indent); + // replace_value.push('\n'); + } else { + while let Some(idx) = replace_value.find("\n ") { + replace_value.remove(idx + 1); + } + while let Some(idx) = replace_value.find("\n") { + replace_value.replace_range(idx..idx + 1, " "); + } + } + + return Ok((replace_range, replace_value)); +} + +pub fn append_top_level_array_value_in_json_text( + text: &str, + new_value: &Value, + tab_size: usize, +) -> Result<(Range, String)> { + let mut parser = tree_sitter::Parser::new(); + parser + .set_language(&tree_sitter_json::LANGUAGE.into()) + .unwrap(); + let syntax_tree = parser.parse(text, None).unwrap(); + + let mut cursor = syntax_tree.walk(); + + if cursor.node().kind() == TS_DOCUMENT_KIND { + anyhow::ensure!( + cursor.goto_first_child(), + "Document empty - No top level array" + ); + } + + while cursor.node().kind() != TS_ARRAY_KIND { + anyhow::ensure!(cursor.goto_next_sibling(), "EOF - No top level array"); + } + + anyhow::ensure!( + cursor.goto_last_child(), + "Malformed JSON syntax tree, expected `]` at end of array" + ); + debug_assert_eq!(cursor.node().kind(), "]"); + let close_bracket_start = cursor.node().start_byte(); + cursor.goto_previous_sibling(); + while (cursor.node().is_extra() || cursor.node().is_missing()) && cursor.goto_previous_sibling() + { + } + + let mut comma_range = None; + let mut prev_item_range = None; + + if cursor.node().kind() == "," { + comma_range = Some(cursor.node().byte_range()); + while cursor.goto_previous_sibling() && cursor.node().is_extra() {} + + debug_assert_ne!(cursor.node().kind(), "["); + prev_item_range = Some(cursor.node().range()); + } else { + while (cursor.node().is_extra() || cursor.node().is_missing()) + && cursor.goto_previous_sibling() + {} + if cursor.node().kind() != "[" { + prev_item_range = Some(cursor.node().range()); + } + } + + let (mut replace_range, mut replace_value) = + replace_value_in_json_text("", &[], tab_size, Some(new_value), None); + + replace_range.start = close_bracket_start; + replace_range.end = close_bracket_start; + + let space = ' '; + if let Some(prev_item_range) = prev_item_range { + let needs_newline = prev_item_range.start_point.row > 0; + let indent_width = text[..prev_item_range.start_byte].rfind('\n').map_or( + prev_item_range.start_point.column, + |idx| { + prev_item_range.start_point.column + - text[idx + 1..prev_item_range.start_byte].trim_start().len() + }, + ); + + let prev_item_end = comma_range + .as_ref() + .map_or(prev_item_range.end_byte, |range| range.end); + if text[prev_item_end..replace_range.start].trim().is_empty() { + replace_range.start = prev_item_end; + } + + if needs_newline { + let increased_indent = format!("\n{space:width$}", width = indent_width); + replace_value = replace_value.replace('\n', &increased_indent); + replace_value.push('\n'); + replace_value.insert_str(0, &format!("\n{space:width$}", width = indent_width)); + } else { + while let Some(idx) = replace_value.find("\n ") { + replace_value.remove(idx + 1); + } + while let Some(idx) = replace_value.find('\n') { + replace_value.replace_range(idx..idx + 1, " "); + } + replace_value.insert(0, ' '); + } + + if comma_range.is_none() { + replace_value.insert(0, ','); + } + } else { + if let Some(prev_newline) = text[..replace_range.start].rfind('\n') { + if text[prev_newline..replace_range.start].trim().is_empty() { + replace_range.start = prev_newline; + } + } + let indent = format!("\n{space:width$}", width = tab_size); + replace_value = replace_value.replace('\n', &indent); + replace_value.insert_str(0, &indent); + replace_value.push('\n'); + } + return Ok((replace_range, replace_value)); +} + +pub fn to_pretty_json( + value: &impl Serialize, + indent_size: usize, + indent_prefix_len: usize, +) -> String { + const SPACES: [u8; 32] = [b' '; 32]; + + debug_assert!(indent_size <= SPACES.len()); + debug_assert!(indent_prefix_len <= SPACES.len()); + + let mut output = Vec::new(); + let mut ser = serde_json::Serializer::with_formatter( + &mut output, + serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]), + ); + + value.serialize(&mut ser).unwrap(); + let text = String::from_utf8(output).unwrap(); + + let mut adjusted_text = String::new(); + for (i, line) in text.split('\n').enumerate() { + if i > 0 { + adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap()); + } + adjusted_text.push_str(line); + adjusted_text.push('\n'); + } + adjusted_text.pop(); + adjusted_text +} + +pub fn parse_json_with_comments(content: &str) -> Result { + Ok(serde_json_lenient::from_str(content)?) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::{Value, json}; + use unindent::Unindent; + + #[test] + fn object_replace() { + #[track_caller] + fn check_object_replace( + input: String, + key_path: &[&str], + value: Option, + expected: String, + ) { + let result = replace_value_in_json_text(&input, key_path, 4, value.as_ref(), None); + let mut result_str = input.to_string(); + result_str.replace_range(result.0, &result.1); + pretty_assertions::assert_eq!(expected, result_str); + } + check_object_replace( + r#"{ + "a": 1, + "b": 2 + }"# + .unindent(), + &["b"], + Some(json!(3)), + r#"{ + "a": 1, + "b": 3 + }"# + .unindent(), + ); + check_object_replace( + r#"{ + "a": 1, + "b": 2 + }"# + .unindent(), + &["b"], + None, + r#"{ + "a": 1 + }"# + .unindent(), + ); + check_object_replace( + r#"{ + "a": 1, + "b": 2 + }"# + .unindent(), + &["c"], + Some(json!(3)), + r#"{ + "c": 3, + "a": 1, + "b": 2 + }"# + .unindent(), + ); + check_object_replace( + r#"{ + "a": 1, + "b": { + "c": 2, + "d": 3, + } + }"# + .unindent(), + &["b", "c"], + Some(json!([1, 2, 3])), + r#"{ + "a": 1, + "b": { + "c": [ + 1, + 2, + 3 + ], + "d": 3, + } + }"# + .unindent(), + ); + + check_object_replace( + r#"{ + "name": "old_name", + "id": 123 + }"# + .unindent(), + &["name"], + Some(json!("new_name")), + r#"{ + "name": "new_name", + "id": 123 + }"# + .unindent(), + ); + + check_object_replace( + r#"{ + "enabled": false, + "count": 5 + }"# + .unindent(), + &["enabled"], + Some(json!(true)), + r#"{ + "enabled": true, + "count": 5 + }"# + .unindent(), + ); + + check_object_replace( + r#"{ + "value": null, + "other": "test" + }"# + .unindent(), + &["value"], + Some(json!(42)), + r#"{ + "value": 42, + "other": "test" + }"# + .unindent(), + ); + + check_object_replace( + r#"{ + "config": { + "old": true + }, + "name": "test" + }"# + .unindent(), + &["config"], + Some(json!({"new": false, "count": 3})), + r#"{ + "config": { + "new": false, + "count": 3 + }, + "name": "test" + }"# + .unindent(), + ); + + check_object_replace( + r#"{ + // This is a comment + "a": 1, + "b": 2 // Another comment + }"# + .unindent(), + &["b"], + Some(json!({"foo": "bar"})), + r#"{ + // This is a comment + "a": 1, + "b": { + "foo": "bar" + } // Another comment + }"# + .unindent(), + ); + + check_object_replace( + r#"{}"#.to_string(), + &["new_key"], + Some(json!("value")), + r#"{ + "new_key": "value" + } + "# + .unindent(), + ); + + check_object_replace( + r#"{ + "only_key": 123 + }"# + .unindent(), + &["only_key"], + None, + "{\n \n}".to_string(), + ); + + check_object_replace( + r#"{ + "level1": { + "level2": { + "level3": { + "target": "old" + } + } + } + }"# + .unindent(), + &["level1", "level2", "level3", "target"], + Some(json!("new")), + r#"{ + "level1": { + "level2": { + "level3": { + "target": "new" + } + } + } + }"# + .unindent(), + ); + + check_object_replace( + r#"{ + "parent": {} + }"# + .unindent(), + &["parent", "child"], + Some(json!("value")), + r#"{ + "parent": { + "child": "value" + } + }"# + .unindent(), + ); + + check_object_replace( + r#"{ + "a": 1, + "b": 2, + }"# + .unindent(), + &["b"], + Some(json!(3)), + r#"{ + "a": 1, + "b": 3, + }"# + .unindent(), + ); + + check_object_replace( + r#"{ + "items": [1, 2, 3], + "count": 3 + }"# + .unindent(), + &["items", "1"], + Some(json!(5)), + r#"{ + "items": { + "1": 5 + }, + "count": 3 + }"# + .unindent(), + ); + + check_object_replace( + r#"{ + "items": [1, 2, 3], + "count": 3 + }"# + .unindent(), + &["items", "1"], + None, + r#"{ + "items": { + "1": null + }, + "count": 3 + }"# + .unindent(), + ); + + check_object_replace( + r#"{ + "items": [1, 2, 3], + "count": 3 + }"# + .unindent(), + &["items"], + Some(json!(["a", "b", "c", "d"])), + r#"{ + "items": [ + "a", + "b", + "c", + "d" + ], + "count": 3 + }"# + .unindent(), + ); + + check_object_replace( + r#"{ + "0": "zero", + "1": "one" + }"# + .unindent(), + &["1"], + Some(json!("ONE")), + r#"{ + "0": "zero", + "1": "ONE" + }"# + .unindent(), + ); + // Test with comments between object members + check_object_replace( + r#"{ + "a": 1, + // Comment between members + "b": 2, + /* Block comment */ + "c": 3 + }"# + .unindent(), + &["b"], + Some(json!({"nested": true})), + r#"{ + "a": 1, + // Comment between members + "b": { + "nested": true + }, + /* Block comment */ + "c": 3 + }"# + .unindent(), + ); + + // Test with trailing comments on replaced value + check_object_replace( + r#"{ + "a": 1, // keep this comment + "b": 2 // this should stay + }"# + .unindent(), + &["a"], + Some(json!("changed")), + r#"{ + "a": "changed", // keep this comment + "b": 2 // this should stay + }"# + .unindent(), + ); + + // Test with deep indentation + check_object_replace( + r#"{ + "deeply": { + "nested": { + "value": "old" + } + } + }"# + .unindent(), + &["deeply", "nested", "value"], + Some(json!("new")), + r#"{ + "deeply": { + "nested": { + "value": "new" + } + } + }"# + .unindent(), + ); + + // Test removing value with comment preservation + check_object_replace( + r#"{ + // Header comment + "a": 1, + // This comment belongs to b + "b": 2, + // This comment belongs to c + "c": 3 + }"# + .unindent(), + &["b"], + None, + r#"{ + // Header comment + "a": 1, + // This comment belongs to b + // This comment belongs to c + "c": 3 + }"# + .unindent(), + ); + + // Test with multiline block comments + check_object_replace( + r#"{ + /* + * This is a multiline + * block comment + */ + "value": "old", + /* Another block */ "other": 123 + }"# + .unindent(), + &["value"], + Some(json!("new")), + r#"{ + /* + * This is a multiline + * block comment + */ + "value": "new", + /* Another block */ "other": 123 + }"# + .unindent(), + ); + + check_object_replace( + r#"{ + // This object is empty + }"# + .unindent(), + &["key"], + Some(json!("value")), + r#"{ + // This object is empty + "key": "value" + } + "# + .unindent(), + ); + + // Test replacing in object with only comments + check_object_replace( + r#"{ + // Comment 1 + // Comment 2 + }"# + .unindent(), + &["new"], + Some(json!(42)), + r#"{ + // Comment 1 + // Comment 2 + "new": 42 + } + "# + .unindent(), + ); + + // Test with inconsistent spacing + check_object_replace( + r#"{ + "a":1, + "b" : 2 , + "c": 3 + }"# + .unindent(), + &["b"], + Some(json!("spaced")), + r#"{ + "a":1, + "b" : "spaced" , + "c": 3 + }"# + .unindent(), + ); + } + + #[test] + fn array_replace() { + #[track_caller] + fn check_array_replace( + input: impl ToString, + index: usize, + key_path: &[&str], + value: Value, + expected: impl ToString, + ) { + let input = input.to_string(); + let result = replace_top_level_array_value_in_json_text( + &input, + key_path, + Some(&value), + None, + index, + 4, + ) + .expect("replace succeeded"); + let mut result_str = input; + result_str.replace_range(result.0, &result.1); + pretty_assertions::assert_eq!(expected.to_string(), result_str); + } + + check_array_replace(r#"[1, 3, 3]"#, 1, &[], json!(2), r#"[1, 2, 3]"#); + check_array_replace(r#"[1, 3, 3]"#, 2, &[], json!(2), r#"[1, 3, 2]"#); + check_array_replace(r#"[1, 3, 3,]"#, 3, &[], json!(2), r#"[1, 3, 3, 2]"#); + check_array_replace(r#"[1, 3, 3,]"#, 100, &[], json!(2), r#"[1, 3, 3, 2]"#); + check_array_replace( + r#"[ + 1, + 2, + 3, + ]"# + .unindent(), + 1, + &[], + json!({"foo": "bar", "baz": "qux"}), + r#"[ + 1, + { + "foo": "bar", + "baz": "qux" + }, + 3, + ]"# + .unindent(), + ); + check_array_replace( + r#"[1, 3, 3,]"#, + 1, + &[], + json!({"foo": "bar", "baz": "qux"}), + r#"[1, { "foo": "bar", "baz": "qux" }, 3,]"#, + ); + + check_array_replace( + r#"[1, { "foo": "bar", "baz": "qux" }, 3,]"#, + 1, + &["baz"], + json!({"qux": "quz"}), + r#"[1, { "foo": "bar", "baz": { "qux": "quz" } }, 3,]"#, + ); + + check_array_replace( + r#"[ + 1, + { + "foo": "bar", + "baz": "qux" + }, + 3 + ]"#, + 1, + &["baz"], + json!({"qux": "quz"}), + r#"[ + 1, + { + "foo": "bar", + "baz": { + "qux": "quz" + } + }, + 3 + ]"#, + ); + + check_array_replace( + r#"[ + 1, + { + "foo": "bar", + "baz": { + "qux": "quz" + } + }, + 3 + ]"#, + 1, + &["baz"], + json!("qux"), + r#"[ + 1, + { + "foo": "bar", + "baz": "qux" + }, + 3 + ]"#, + ); + + check_array_replace( + r#"[ + 1, + { + "foo": "bar", + // some comment to keep + "baz": { + // some comment to remove + "qux": "quz" + } + // some other comment to keep + }, + 3 + ]"#, + 1, + &["baz"], + json!("qux"), + r#"[ + 1, + { + "foo": "bar", + // some comment to keep + "baz": "qux" + // some other comment to keep + }, + 3 + ]"#, + ); + + // Test with comments between array elements + check_array_replace( + r#"[ + 1, + // This is element 2 + 2, + /* Block comment */ 3, + 4 // Trailing comment + ]"#, + 2, + &[], + json!("replaced"), + r#"[ + 1, + // This is element 2 + 2, + /* Block comment */ "replaced", + 4 // Trailing comment + ]"#, + ); + + // Test empty array with comments + check_array_replace( + r#"[ + // Empty array with comment + ]"# + .unindent(), + 0, + &[], + json!("first"), + r#"[ + // Empty array with comment + "first" + ]"# + .unindent(), + ); + check_array_replace( + r#"[]"#.unindent(), + 0, + &[], + json!("first"), + r#"[ + "first" + ]"# + .unindent(), + ); + + // Test array with leading comments + check_array_replace( + r#"[ + // Leading comment + // Another leading comment + 1, + 2 + ]"#, + 0, + &[], + json!({"new": "object"}), + r#"[ + // Leading comment + // Another leading comment + { + "new": "object" + }, + 2 + ]"#, + ); + + // Test with deep indentation + check_array_replace( + r#"[ + 1, + 2, + 3 + ]"#, + 1, + &[], + json!("deep"), + r#"[ + 1, + "deep", + 3 + ]"#, + ); + + // Test with mixed spacing + check_array_replace( + r#"[1,2, 3, 4]"#, + 2, + &[], + json!("spaced"), + r#"[1,2, "spaced", 4]"#, + ); + + // Test replacing nested array element + check_array_replace( + r#"[ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9] + ]"#, + 1, + &[], + json!(["a", "b", "c", "d"]), + r#"[ + [1, 2, 3], + [ + "a", + "b", + "c", + "d" + ], + [7, 8, 9] + ]"#, + ); + + // Test with multiline block comments + check_array_replace( + r#"[ + /* + * This is a + * multiline comment + */ + "first", + "second" + ]"#, + 0, + &[], + json!("updated"), + r#"[ + /* + * This is a + * multiline comment + */ + "updated", + "second" + ]"#, + ); + + // Test replacing with null + check_array_replace( + r#"[true, false, true]"#, + 1, + &[], + json!(null), + r#"[true, null, true]"#, + ); + + // Test single element array + check_array_replace( + r#"[42]"#, + 0, + &[], + json!({"answer": 42}), + r#"[{ "answer": 42 }]"#, + ); + + // Test array with only comments + check_array_replace( + r#"[ + // Comment 1 + // Comment 2 + // Comment 3 + ]"# + .unindent(), + 10, + &[], + json!(123), + r#"[ + // Comment 1 + // Comment 2 + // Comment 3 + 123 + ]"# + .unindent(), + ); + } + + #[test] + fn array_append() { + #[track_caller] + fn check_array_append(input: impl ToString, value: Value, expected: impl ToString) { + let input = input.to_string(); + let result = append_top_level_array_value_in_json_text(&input, &value, 4) + .expect("append succeeded"); + let mut result_str = input; + result_str.replace_range(result.0, &result.1); + pretty_assertions::assert_eq!(expected.to_string(), result_str); + } + check_array_append(r#"[1, 3, 3]"#, json!(4), r#"[1, 3, 3, 4]"#); + check_array_append(r#"[1, 3, 3,]"#, json!(4), r#"[1, 3, 3, 4]"#); + check_array_append(r#"[1, 3, 3 ]"#, json!(4), r#"[1, 3, 3, 4]"#); + check_array_append(r#"[1, 3, 3, ]"#, json!(4), r#"[1, 3, 3, 4]"#); + check_array_append( + r#"[ + 1, + 2, + 3 + ]"# + .unindent(), + json!(4), + r#"[ + 1, + 2, + 3, + 4 + ]"# + .unindent(), + ); + check_array_append( + r#"[ + 1, + 2, + 3, + ]"# + .unindent(), + json!(4), + r#"[ + 1, + 2, + 3, + 4 + ]"# + .unindent(), + ); + check_array_append( + r#"[ + 1, + 2, + 3, + ]"# + .unindent(), + json!({"foo": "bar", "baz": "qux"}), + r#"[ + 1, + 2, + 3, + { + "foo": "bar", + "baz": "qux" + } + ]"# + .unindent(), + ); + check_array_append( + r#"[ 1, 2, 3, ]"#.unindent(), + json!({"foo": "bar", "baz": "qux"}), + r#"[ 1, 2, 3, { "foo": "bar", "baz": "qux" }]"#.unindent(), + ); + check_array_append( + r#"[]"#, + json!({"foo": "bar"}), + r#"[ + { + "foo": "bar" + } + ]"# + .unindent(), + ); + + // Test with comments between array elements + check_array_append( + r#"[ + 1, + // Comment between elements + 2, + /* Block comment */ 3 + ]"# + .unindent(), + json!(4), + r#"[ + 1, + // Comment between elements + 2, + /* Block comment */ 3, + 4 + ]"# + .unindent(), + ); + + // Test with trailing comment on last element + check_array_append( + r#"[ + 1, + 2, + 3 // Trailing comment + ]"# + .unindent(), + json!("new"), + r#"[ + 1, + 2, + 3 // Trailing comment + , + "new" + ]"# + .unindent(), + ); + + // Test empty array with comments + check_array_append( + r#"[ + // Empty array with comment + ]"# + .unindent(), + json!("first"), + r#"[ + // Empty array with comment + "first" + ]"# + .unindent(), + ); + + // Test with multiline block comment at end + check_array_append( + r#"[ + 1, + 2 + /* + * This is a + * multiline comment + */ + ]"# + .unindent(), + json!(3), + r#"[ + 1, + 2 + /* + * This is a + * multiline comment + */ + , + 3 + ]"# + .unindent(), + ); + + // Test with deep indentation + check_array_append( + r#"[ + 1, + 2, + 3 + ]"# + .unindent(), + json!("deep"), + r#"[ + 1, + 2, + 3, + "deep" + ]"# + .unindent(), + ); + + // Test with no spacing + check_array_append(r#"[1,2,3]"#, json!(4), r#"[1,2,3, 4]"#); + + // Test appending complex nested structure + check_array_append( + r#"[ + {"a": 1}, + {"b": 2} + ]"# + .unindent(), + json!({"c": {"nested": [1, 2, 3]}}), + r#"[ + {"a": 1}, + {"b": 2}, + { + "c": { + "nested": [ + 1, + 2, + 3 + ] + } + } + ]"# + .unindent(), + ); + + // Test array ending with comment after bracket + check_array_append( + r#"[ + 1, + 2, + 3 + ] // Comment after array"# + .unindent(), + json!(4), + r#"[ + 1, + 2, + 3, + 4 + ] // Comment after array"# + .unindent(), + ); + + // Test with inconsistent element formatting + check_array_append( + r#"[1, + 2, + 3, + ]"# + .unindent(), + json!(4), + r#"[1, + 2, + 3, + 4 + ]"# + .unindent(), + ); + + // Test appending to single-line array with trailing comma + check_array_append( + r#"[1, 2, 3,]"#, + json!({"key": "value"}), + r#"[1, 2, 3, { "key": "value" }]"#, + ); + + // Test appending null value + check_array_append(r#"[true, false]"#, json!(null), r#"[true, false, null]"#); + + // Test appending to array with only comments + check_array_append( + r#"[ + // Just comments here + // More comments + ]"# + .unindent(), + json!(42), + r#"[ + // Just comments here + // More comments + 42 + ]"# + .unindent(), + ); + } +} diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index f5469adf381896169b4f8599111ac75d4af99f03..38e2e4968a115f684690427957cee75e62182b67 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -16,17 +16,17 @@ use std::{ ops::Range, path::{Path, PathBuf}, str::{self, FromStr}, - sync::{Arc, LazyLock}, + sync::Arc, }; -use streaming_iterator::StreamingIterator; -use tree_sitter::Query; -use util::RangeExt; use util::{ResultExt as _, merge_non_null_json_value_into}; pub type EditorconfigProperties = ec4rs::Properties; -use crate::{SettingsJsonSchemaParams, VsCodeSettings, WorktreeId}; +use crate::{ + SettingsJsonSchemaParams, VsCodeSettings, WorktreeId, parse_json_with_comments, + update_value_in_json_text, +}; /// A value that can be defined as a user setting. /// @@ -1334,273 +1334,6 @@ impl AnySettingValue for SettingValue { } } -fn update_value_in_json_text<'a>( - text: &mut String, - key_path: &mut Vec<&'a str>, - tab_size: usize, - old_value: &'a Value, - new_value: &'a Value, - preserved_keys: &[&str], - edits: &mut Vec<(Range, String)>, -) { - // If the old and new values are both objects, then compare them key by key, - // preserving the comments and formatting of the unchanged parts. Otherwise, - // replace the old value with the new value. - if let (Value::Object(old_object), Value::Object(new_object)) = (old_value, new_value) { - for (key, old_sub_value) in old_object.iter() { - key_path.push(key); - if let Some(new_sub_value) = new_object.get(key) { - // Key exists in both old and new, recursively update - update_value_in_json_text( - text, - key_path, - tab_size, - old_sub_value, - new_sub_value, - preserved_keys, - edits, - ); - } else { - // Key was removed from new object, remove the entire key-value pair - let (range, replacement) = replace_value_in_json_text(text, key_path, 0, None); - text.replace_range(range.clone(), &replacement); - edits.push((range, replacement)); - } - key_path.pop(); - } - for (key, new_sub_value) in new_object.iter() { - key_path.push(key); - if !old_object.contains_key(key) { - update_value_in_json_text( - text, - key_path, - tab_size, - &Value::Null, - new_sub_value, - preserved_keys, - edits, - ); - } - key_path.pop(); - } - } else if key_path - .last() - .map_or(false, |key| preserved_keys.contains(key)) - || old_value != new_value - { - let mut new_value = new_value.clone(); - if let Some(new_object) = new_value.as_object_mut() { - new_object.retain(|_, v| !v.is_null()); - } - let (range, replacement) = - replace_value_in_json_text(text, key_path, tab_size, Some(&new_value)); - text.replace_range(range.clone(), &replacement); - edits.push((range, replacement)); - } -} - -fn replace_value_in_json_text( - text: &str, - key_path: &[&str], - tab_size: usize, - new_value: Option<&Value>, -) -> (Range, String) { - static PAIR_QUERY: LazyLock = LazyLock::new(|| { - Query::new( - &tree_sitter_json::LANGUAGE.into(), - "(pair key: (string) @key value: (_) @value)", - ) - .expect("Failed to create PAIR_QUERY") - }); - - let mut parser = tree_sitter::Parser::new(); - parser - .set_language(&tree_sitter_json::LANGUAGE.into()) - .unwrap(); - let syntax_tree = parser.parse(text, None).unwrap(); - - let mut cursor = tree_sitter::QueryCursor::new(); - - let mut depth = 0; - let mut last_value_range = 0..0; - let mut first_key_start = None; - let mut existing_value_range = 0..text.len(); - let mut matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes()); - while let Some(mat) = matches.next() { - if mat.captures.len() != 2 { - continue; - } - - let key_range = mat.captures[0].node.byte_range(); - let value_range = mat.captures[1].node.byte_range(); - - // Don't enter sub objects until we find an exact - // match for the current keypath - if last_value_range.contains_inclusive(&value_range) { - continue; - } - - last_value_range = value_range.clone(); - - if key_range.start > existing_value_range.end { - break; - } - - first_key_start.get_or_insert(key_range.start); - - let found_key = text - .get(key_range.clone()) - .map(|key_text| { - depth < key_path.len() && key_text == format!("\"{}\"", key_path[depth]) - }) - .unwrap_or(false); - - if found_key { - existing_value_range = value_range; - // Reset last value range when increasing in depth - last_value_range = existing_value_range.start..existing_value_range.start; - depth += 1; - - if depth == key_path.len() { - break; - } - - first_key_start = None; - } - } - - // We found the exact key we want - if depth == key_path.len() { - if let Some(new_value) = new_value { - let new_val = to_pretty_json(new_value, tab_size, tab_size * depth); - (existing_value_range, new_val) - } else { - let mut removal_start = first_key_start.unwrap_or(existing_value_range.start); - let mut removal_end = existing_value_range.end; - - // Find the actual key position by looking for the key in the pair - // We need to extend the range to include the key, not just the value - if let Some(key_start) = text[..existing_value_range.start].rfind('"') { - if let Some(prev_key_start) = text[..key_start].rfind('"') { - removal_start = prev_key_start; - } else { - removal_start = key_start; - } - } - - // Look backward for a preceding comma first - let preceding_text = text.get(0..removal_start).unwrap_or(""); - if let Some(comma_pos) = preceding_text.rfind(',') { - // Check if there are only whitespace characters between the comma and our key - let between_comma_and_key = text.get(comma_pos + 1..removal_start).unwrap_or(""); - if between_comma_and_key.trim().is_empty() { - removal_start = comma_pos; - } - } - - if let Some(remaining_text) = text.get(existing_value_range.end..) { - let mut chars = remaining_text.char_indices(); - while let Some((offset, ch)) = chars.next() { - if ch == ',' { - removal_end = existing_value_range.end + offset + 1; - // Also consume whitespace after the comma - while let Some((_, next_ch)) = chars.next() { - if next_ch.is_whitespace() { - removal_end += next_ch.len_utf8(); - } else { - break; - } - } - break; - } else if !ch.is_whitespace() { - break; - } - } - } - (removal_start..removal_end, String::new()) - } - } else { - // We have key paths, construct the sub objects - let new_key = key_path[depth]; - - // We don't have the key, construct the nested objects - let mut new_value = - serde_json::to_value(new_value.unwrap_or(&serde_json::Value::Null)).unwrap(); - for key in key_path[(depth + 1)..].iter().rev() { - new_value = serde_json::json!({ key.to_string(): new_value }); - } - - if let Some(first_key_start) = first_key_start { - let mut row = 0; - let mut column = 0; - for (ix, char) in text.char_indices() { - if ix == first_key_start { - break; - } - if char == '\n' { - row += 1; - column = 0; - } else { - column += char.len_utf8(); - } - } - - if row > 0 { - // depth is 0 based, but division needs to be 1 based. - let new_val = to_pretty_json(&new_value, column / (depth + 1), column); - let space = ' '; - let content = format!("\"{new_key}\": {new_val},\n{space:width$}", width = column); - (first_key_start..first_key_start, content) - } else { - let new_val = serde_json::to_string(&new_value).unwrap(); - let mut content = format!(r#""{new_key}": {new_val},"#); - content.push(' '); - (first_key_start..first_key_start, content) - } - } else { - new_value = serde_json::json!({ new_key.to_string(): new_value }); - let indent_prefix_len = 4 * depth; - let mut new_val = to_pretty_json(&new_value, 4, indent_prefix_len); - if depth == 0 { - new_val.push('\n'); - } - - (existing_value_range, new_val) - } - } -} - -fn to_pretty_json(value: &impl Serialize, indent_size: usize, indent_prefix_len: usize) -> String { - const SPACES: [u8; 32] = [b' '; 32]; - - debug_assert!(indent_size <= SPACES.len()); - debug_assert!(indent_prefix_len <= SPACES.len()); - - let mut output = Vec::new(); - let mut ser = serde_json::Serializer::with_formatter( - &mut output, - serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]), - ); - - value.serialize(&mut ser).unwrap(); - let text = String::from_utf8(output).unwrap(); - - let mut adjusted_text = String::new(); - for (i, line) in text.split('\n').enumerate() { - if i > 0 { - adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap()); - } - adjusted_text.push_str(line); - adjusted_text.push('\n'); - } - adjusted_text.pop(); - adjusted_text -} - -pub fn parse_json_with_comments(content: &str) -> Result { - Ok(serde_json_lenient::from_str(content)?) -} - #[cfg(test)] mod tests { use crate::VsCodeSettingsSource; @@ -1784,6 +1517,22 @@ mod tests { ); } + fn check_settings_update( + store: &mut SettingsStore, + old_json: String, + update: fn(&mut T::FileContent), + expected_new_json: String, + cx: &mut App, + ) { + store.set_user_settings(&old_json, cx).ok(); + let edits = store.edits_for_update::(&old_json, update); + let mut new_json = old_json; + for (range, replacement) in edits.into_iter() { + new_json.replace_range(range, &replacement); + } + pretty_assertions::assert_eq!(new_json, expected_new_json); + } + #[gpui::test] fn test_setting_store_update(cx: &mut App) { let mut store = SettingsStore::new(cx); @@ -1890,12 +1639,12 @@ mod tests { &mut store, r#"{ "user": { "age": 36, "name": "Max", "staff": true } - }"# + }"# .unindent(), |settings| settings.age = Some(37), r#"{ "user": { "age": 37, "name": "Max", "staff": true } - }"# + }"# .unindent(), cx, ); @@ -2118,22 +1867,6 @@ mod tests { ); } - fn check_settings_update( - store: &mut SettingsStore, - old_json: String, - update: fn(&mut T::FileContent), - expected_new_json: String, - cx: &mut App, - ) { - store.set_user_settings(&old_json, cx).ok(); - let edits = store.edits_for_update::(&old_json, update); - let mut new_json = old_json; - for (range, replacement) in edits.into_iter() { - new_json.replace_range(range, &replacement); - } - pretty_assertions::assert_eq!(new_json, expected_new_json); - } - fn check_vscode_import( store: &mut SettingsStore, old: String, From 20a3e613b84671ab71931d80c9c5f2697675ec18 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 26 Jun 2025 21:25:07 -0600 Subject: [PATCH 010/107] vim: Better jump list support (#33495) Closes #23527 Closes #30183 Closes some Discord chats Release Notes: - vim: Motions now push to the jump list using the same logic as vim (i.e. `G`/`g g`/`g d` always do, but `j`/`k` always don't). Most non-vim actions (including clicking with the mouse) continue to push to the jump list only when they move the cursor by 10 or more lines. --- crates/editor/src/editor.rs | 31 +++++++---- crates/editor/src/items.rs | 2 +- crates/vim/src/motion.rs | 67 ++++++++++++++++++++++++ crates/vim/src/normal.rs | 52 +++++++++++++++--- crates/vim/test_data/test_jump_list.json | 14 +++++ 5 files changed, 147 insertions(+), 19 deletions(-) create mode 100644 crates/vim/test_data/test_jump_list.json diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6244e7a4c3fbaaacea3b81d86b4ed521744e2eb1..8ef52b84969fd61b3ddd6d2cfb1eb95227bcb265 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1256,7 +1256,7 @@ impl Default for SelectionHistoryMode { #[derive(Debug)] pub struct SelectionEffects { - nav_history: bool, + nav_history: Option, completions: bool, scroll: Option, } @@ -1264,7 +1264,7 @@ pub struct SelectionEffects { impl Default for SelectionEffects { fn default() -> Self { Self { - nav_history: true, + nav_history: None, completions: true, scroll: Some(Autoscroll::fit()), } @@ -1294,7 +1294,7 @@ impl SelectionEffects { pub fn nav_history(self, nav_history: bool) -> Self { Self { - nav_history, + nav_history: Some(nav_history), ..self } } @@ -2909,11 +2909,12 @@ impl Editor { let new_cursor_position = newest_selection.head(); let selection_start = newest_selection.start; - if effects.nav_history { + if effects.nav_history.is_none() || effects.nav_history == Some(true) { self.push_to_nav_history( *old_cursor_position, Some(new_cursor_position.to_point(buffer)), false, + effects.nav_history == Some(true), cx, ); } @@ -3164,7 +3165,7 @@ impl Editor { if let Some(state) = &mut self.deferred_selection_effects_state { state.effects.scroll = effects.scroll.or(state.effects.scroll); state.effects.completions = effects.completions; - state.effects.nav_history |= effects.nav_history; + state.effects.nav_history = effects.nav_history.or(state.effects.nav_history); let (changed, result) = self.selections.change_with(cx, change); state.changed |= changed; return result; @@ -13097,7 +13098,13 @@ impl Editor { } pub fn create_nav_history_entry(&mut self, cx: &mut Context) { - self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx); + self.push_to_nav_history( + self.selections.newest_anchor().head(), + None, + false, + true, + cx, + ); } fn push_to_nav_history( @@ -13105,6 +13112,7 @@ impl Editor { cursor_anchor: Anchor, new_position: Option, is_deactivate: bool, + always: bool, cx: &mut Context, ) { if let Some(nav_history) = self.nav_history.as_mut() { @@ -13116,7 +13124,7 @@ impl Editor { if let Some(new_position) = new_position { let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs(); - if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA { + if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) { return; } } @@ -14788,9 +14796,12 @@ impl Editor { let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else { return; }; - self.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_anchor_ranges([start..end]) - }); + self.change_selections( + SelectionEffects::default().nav_history(true), + window, + cx, + |s| s.select_anchor_ranges([start..end]), + ); } pub fn go_to_diagnostic( diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 93a80d7764944c82f547894a982b5cd1dbf02b26..4993ff689507ebc4478d4a92164360dd6e734a9e 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -778,7 +778,7 @@ impl Item for Editor { fn deactivated(&mut self, _: &mut Window, cx: &mut Context) { let selection = self.selections.newest_anchor(); - self.push_to_nav_history(selection.head(), None, true, cx); + self.push_to_nav_history(selection.head(), None, true, false, cx); } fn workspace_deactivated(&mut self, _: &mut Window, cx: &mut Context) { diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 6b92246e501092ed547ae7169ac0691f67f4a3a8..e9b01f5a674f8736b0379ca20d8907e1ac3782c6 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -768,6 +768,73 @@ impl Motion { } } + pub(crate) fn push_to_jump_list(&self) -> bool { + use Motion::*; + match self { + CurrentLine + | Down { .. } + | EndOfLine { .. } + | EndOfLineDownward + | FindBackward { .. } + | FindForward { .. } + | FirstNonWhitespace { .. } + | GoToColumn + | Left + | MiddleOfLine { .. } + | NextLineStart + | NextSubwordEnd { .. } + | NextSubwordStart { .. } + | NextWordEnd { .. } + | NextWordStart { .. } + | PreviousLineStart + | PreviousSubwordEnd { .. } + | PreviousSubwordStart { .. } + | PreviousWordEnd { .. } + | PreviousWordStart { .. } + | RepeatFind { .. } + | RepeatFindReversed { .. } + | Right + | StartOfLine { .. } + | StartOfLineDownward + | Up { .. } + | WrappingLeft + | WrappingRight => false, + EndOfDocument + | EndOfParagraph + | GoToPercentage + | Jump { .. } + | Matching + | NextComment + | NextGreaterIndent + | NextLesserIndent + | NextMethodEnd + | NextMethodStart + | NextSameIndent + | NextSectionEnd + | NextSectionStart + | PreviousComment + | PreviousGreaterIndent + | PreviousLesserIndent + | PreviousMethodEnd + | PreviousMethodStart + | PreviousSameIndent + | PreviousSectionEnd + | PreviousSectionStart + | SentenceBackward + | SentenceForward + | Sneak { .. } + | SneakBackward { .. } + | StartOfDocument + | StartOfParagraph + | UnmatchedBackward { .. } + | UnmatchedForward { .. } + | WindowBottom + | WindowMiddle + | WindowTop + | ZedSearchResult { .. } => true, + } + } + pub fn infallible(&self) -> bool { use Motion::*; match self { diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index ff9b347e41c49148f954b13acbb371cc7e23f458..475e9e73d3f29de87ccaead812d652c74790a881 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -24,10 +24,10 @@ use crate::{ }; use collections::BTreeSet; use convert::ConvertTarget; -use editor::Anchor; use editor::Bias; use editor::Editor; use editor::scroll::Autoscroll; +use editor::{Anchor, SelectionEffects}; use editor::{display_map::ToDisplayPoint, movement}; use gpui::{Context, Window, actions}; use language::{Point, SelectionGoal, ToPoint}; @@ -358,13 +358,18 @@ impl Vim { ) { self.update_editor(window, cx, |_, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.move_cursors_with(|map, cursor, goal| { - motion - .move_point(map, cursor, goal, times, &text_layout_details) - .unwrap_or((cursor, goal)) - }) - }) + editor.change_selections( + SelectionEffects::default().nav_history(motion.push_to_jump_list()), + window, + cx, + |s| { + s.move_cursors_with(|map, cursor, goal| { + motion + .move_point(map, cursor, goal, times, &text_layout_details) + .unwrap_or((cursor, goal)) + }) + }, + ) }); } @@ -1799,4 +1804,35 @@ mod test { fox jˇumps over the lazy dog"}); } + + #[gpui::test] + async fn test_jump_list(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(indoc! {" + ˇfn a() { } + + + + + + fn b() { } + + + + + + fn b() { }"}) + .await; + cx.simulate_shared_keystrokes("3 }").await; + cx.shared_state().await.assert_matches(); + cx.simulate_shared_keystrokes("ctrl-o").await; + cx.shared_state().await.assert_matches(); + cx.simulate_shared_keystrokes("ctrl-i").await; + cx.shared_state().await.assert_matches(); + cx.simulate_shared_keystrokes("1 1 k").await; + cx.shared_state().await.assert_matches(); + cx.simulate_shared_keystrokes("ctrl-o").await; + cx.shared_state().await.assert_matches(); + } } diff --git a/crates/vim/test_data/test_jump_list.json b/crates/vim/test_data/test_jump_list.json new file mode 100644 index 0000000000000000000000000000000000000000..833d1adadb91201413482427e4ae03b119645687 --- /dev/null +++ b/crates/vim/test_data/test_jump_list.json @@ -0,0 +1,14 @@ +{"Put":{"state":"ˇfn a() { }\n\n\n\n\n\nfn b() { }\n\n\n\n\n\nfn b() { }"}} +{"Key":"3"} +{"Key":"}"} +{"Get":{"state":"fn a() { }\n\n\n\n\n\nfn b() { }\n\n\n\n\n\nfn b() { ˇ}","mode":"Normal"}} +{"Key":"ctrl-o"} +{"Get":{"state":"ˇfn a() { }\n\n\n\n\n\nfn b() { }\n\n\n\n\n\nfn b() { }","mode":"Normal"}} +{"Key":"ctrl-i"} +{"Get":{"state":"fn a() { }\n\n\n\n\n\nfn b() { }\n\n\n\n\n\nfn b() { ˇ}","mode":"Normal"}} +{"Key":"1"} +{"Key":"1"} +{"Key":"k"} +{"Get":{"state":"fn a() { }\nˇ\n\n\n\n\nfn b() { }\n\n\n\n\n\nfn b() { }","mode":"Normal"}} +{"Key":"ctrl-o"} +{"Get":{"state":"ˇfn a() { }\n\n\n\n\n\nfn b() { }\n\n\n\n\n\nfn b() { }","mode":"Normal"}} From 8c9116daa5ea6001f08bf90cb3a8e55a9b695f83 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 26 Jun 2025 21:26:58 -0600 Subject: [PATCH 011/107] Fix panic in ctrl-g (#33474) Release Notes: - vim: Fixed a crash with ctrl-g --- crates/vim/src/normal.rs | 64 +++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 475e9e73d3f29de87ccaead812d652c74790a881..1d70227e0ba8791ebe6ebecd6e1202eae44d91db 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -30,7 +30,7 @@ use editor::scroll::Autoscroll; use editor::{Anchor, SelectionEffects}; use editor::{display_map::ToDisplayPoint, movement}; use gpui::{Context, Window, actions}; -use language::{Point, SelectionGoal, ToPoint}; +use language::{Point, SelectionGoal}; use log::error; use multi_buffer::MultiBufferRow; @@ -663,38 +663,42 @@ impl Vim { Vim::take_forced_motion(cx); self.update_editor(window, cx, |vim, editor, _window, cx| { let selection = editor.selections.newest_anchor(); - if let Some((_, buffer, _)) = editor.active_excerpt(cx) { - let filename = if let Some(file) = buffer.read(cx).file() { - if count.is_some() { - if let Some(local) = file.as_local() { - local.abs_path(cx).to_string_lossy().to_string() - } else { - file.full_path(cx).to_string_lossy().to_string() - } + let Some((buffer, point, _)) = editor + .buffer() + .read(cx) + .point_to_buffer_point(selection.head(), cx) + else { + return; + }; + let filename = if let Some(file) = buffer.read(cx).file() { + if count.is_some() { + if let Some(local) = file.as_local() { + local.abs_path(cx).to_string_lossy().to_string() } else { - file.path().to_string_lossy().to_string() + file.full_path(cx).to_string_lossy().to_string() } } else { - "[No Name]".into() - }; - let buffer = buffer.read(cx); - let snapshot = buffer.snapshot(); - let lines = buffer.max_point().row + 1; - let current_line = selection.head().text_anchor.to_point(&snapshot).row; - let percentage = current_line as f32 / lines as f32; - let modified = if buffer.is_dirty() { " [modified]" } else { "" }; - vim.status_label = Some( - format!( - "{}{} {} lines --{:.0}%--", - filename, - modified, - lines, - percentage * 100.0, - ) - .into(), - ); - cx.notify(); - } + file.path().to_string_lossy().to_string() + } + } else { + "[No Name]".into() + }; + let buffer = buffer.read(cx); + let lines = buffer.max_point().row + 1; + let current_line = point.row; + let percentage = current_line as f32 / lines as f32; + let modified = if buffer.is_dirty() { " [modified]" } else { "" }; + vim.status_label = Some( + format!( + "{}{} {} lines --{:.0}%--", + filename, + modified, + lines, + percentage * 100.0, + ) + .into(), + ); + cx.notify(); }); } From fbb5628ec6f4ab0badace8b0f1c4b77d89b2e4ba Mon Sep 17 00:00:00 2001 From: fantacell Date: Fri, 27 Jun 2025 06:32:35 +0200 Subject: [PATCH 012/107] Reset selection goal after helix motion (#33184) Closes #33060 Motions like `NextWordStart` don't reset the selection goal in vim mode `helix_normal` unlike in `normal` which can lead to the cursor jumping back to the previous horizontal position after going up or down. Release Notes: - N/A --- crates/vim/src/helix.rs | 88 +++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index 959c53e48ef4e41bf9f8fa0df56bbb79268caf99..d5312934e477d2d5ddea089695a5055858cd391b 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -2,6 +2,7 @@ use editor::{DisplayPoint, Editor, movement, scroll::Autoscroll}; use gpui::{Action, actions}; use gpui::{Context, Window}; use language::{CharClassifier, CharKind}; +use text::SelectionGoal; use crate::{Vim, motion::Motion, state::Mode}; @@ -49,43 +50,43 @@ impl Vim { editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let times = times.unwrap_or(1); + let new_goal = SelectionGoal::None; + let mut head = selection.head(); + let mut tail = selection.tail(); - if selection.head() == map.max_point() { + if head == map.max_point() { return; } // collapse to block cursor - if selection.tail() < selection.head() { - selection.set_tail(movement::left(map, selection.head()), selection.goal); + if tail < head { + tail = movement::left(map, head); } else { - selection.set_tail(selection.head(), selection.goal); - selection.set_head(movement::right(map, selection.head()), selection.goal); + tail = head; + head = movement::right(map, head); } // create a classifier - let classifier = map - .buffer_snapshot - .char_classifier_at(selection.head().to_point(map)); + let classifier = map.buffer_snapshot.char_classifier_at(head.to_point(map)); - let mut last_selection = selection.clone(); for _ in 0..times { - let (new_tail, new_head) = - movement::find_boundary_trail(map, selection.head(), |left, right| { + let (maybe_next_tail, next_head) = + movement::find_boundary_trail(map, head, |left, right| { is_boundary(left, right, &classifier) }); - selection.set_head(new_head, selection.goal); - if let Some(new_tail) = new_tail { - selection.set_tail(new_tail, selection.goal); + if next_head == head && maybe_next_tail.unwrap_or(next_head) == tail { + break; } - if selection.head() == last_selection.head() - && selection.tail() == last_selection.tail() - { - break; + head = next_head; + if let Some(next_tail) = maybe_next_tail { + tail = next_tail; } - last_selection = selection.clone(); } + + selection.set_tail(tail, new_goal); + selection.set_head(head, new_goal); }); }); }); @@ -102,47 +103,50 @@ impl Vim { editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let times = times.unwrap_or(1); + let new_goal = SelectionGoal::None; + let mut head = selection.head(); + let mut tail = selection.tail(); - if selection.head() == DisplayPoint::zero() { + if head == DisplayPoint::zero() { return; } // collapse to block cursor - if selection.tail() < selection.head() { - selection.set_tail(movement::left(map, selection.head()), selection.goal); + if tail < head { + tail = movement::left(map, head); } else { - selection.set_tail(selection.head(), selection.goal); - selection.set_head(movement::right(map, selection.head()), selection.goal); + tail = head; + head = movement::right(map, head); } + selection.set_head(head, new_goal); + selection.set_tail(tail, new_goal); // flip the selection selection.swap_head_tail(); + head = selection.head(); + tail = selection.tail(); // create a classifier - let classifier = map - .buffer_snapshot - .char_classifier_at(selection.head().to_point(map)); + let classifier = map.buffer_snapshot.char_classifier_at(head.to_point(map)); - let mut last_selection = selection.clone(); for _ in 0..times { - let (new_tail, new_head) = movement::find_preceding_boundary_trail( - map, - selection.head(), - |left, right| is_boundary(left, right, &classifier), - ); - - selection.set_head(new_head, selection.goal); - if let Some(new_tail) = new_tail { - selection.set_tail(new_tail, selection.goal); - } + let (maybe_next_tail, next_head) = + movement::find_preceding_boundary_trail(map, head, |left, right| { + is_boundary(left, right, &classifier) + }); - if selection.head() == last_selection.head() - && selection.tail() == last_selection.tail() - { + if next_head == head && maybe_next_tail.unwrap_or(next_head) == tail { break; } - last_selection = selection.clone(); + + head = next_head; + if let Some(next_tail) = maybe_next_tail { + tail = next_tail; + } } + + selection.set_tail(tail, new_goal); + selection.set_head(head, new_goal); }); }) }); From 6c46e1129df58c9cda15cf028a51c44868f1565d Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Fri, 27 Jun 2025 10:32:13 +0200 Subject: [PATCH 013/107] Cleanup remaining references to max mode (#33509) Release Notes: - N/A --- crates/agent/src/thread.rs | 2 +- crates/agent_ui/src/agent_panel.rs | 2 +- crates/agent_ui/src/agent_ui.rs | 2 +- .../src/{max_mode_tooltip.rs => burn_mode_tooltip.rs} | 0 crates/agent_ui/src/message_editor.rs | 2 +- crates/agent_ui/src/text_thread_editor.rs | 10 +++++----- crates/agent_ui/src/ui.rs | 4 ++-- .../ui/{max_mode_tooltip.rs => burn_mode_tooltip.rs} | 0 crates/assistant_context/src/assistant_context.rs | 6 +++--- crates/language_model/src/language_model.rs | 2 +- crates/language_models/src/provider/cloud.rs | 2 +- 11 files changed, 16 insertions(+), 16 deletions(-) rename crates/agent_ui/src/{max_mode_tooltip.rs => burn_mode_tooltip.rs} (100%) rename crates/agent_ui/src/ui/{max_mode_tooltip.rs => burn_mode_tooltip.rs} (100%) diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index 68624d7c3b9d304c5b27c79f1bcdb072117b7dcd..32c376ca67fdfa492233ccb6ce1b2947abba0538 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -1419,7 +1419,7 @@ impl Thread { } request.tools = available_tools; - request.mode = if model.supports_max_mode() { + request.mode = if model.supports_burn_mode() { Some(self.completion_mode.into()) } else { Some(CompletionMode::Normal.into()) diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index e7e7b6da13077821f42e4dfa2fc139050ad0ccbc..560e87b1c2ad9a86f8f83a0534f6b53091ebe2a2 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -2656,7 +2656,7 @@ impl AgentPanel { this.continue_conversation(window, cx); })), ) - .when(model.supports_max_mode(), |this| { + .when(model.supports_burn_mode(), |this| { this.child( Button::new("continue-burn-mode", "Continue with Burn Mode") .style(ButtonStyle::Filled) diff --git a/crates/agent_ui/src/agent_ui.rs b/crates/agent_ui/src/agent_ui.rs index 4babe4f676054740ea88645235ccbcb834d3fc18..29a4f38487b34e134218b72e824b1d3aba439cb9 100644 --- a/crates/agent_ui/src/agent_ui.rs +++ b/crates/agent_ui/src/agent_ui.rs @@ -4,6 +4,7 @@ mod agent_diff; mod agent_model_selector; mod agent_panel; mod buffer_codegen; +mod burn_mode_tooltip; mod context_picker; mod context_server_configuration; mod context_strip; @@ -11,7 +12,6 @@ mod debug; mod inline_assistant; mod inline_prompt_editor; mod language_model_selector; -mod max_mode_tooltip; mod message_editor; mod profile_selector; mod slash_command; diff --git a/crates/agent_ui/src/max_mode_tooltip.rs b/crates/agent_ui/src/burn_mode_tooltip.rs similarity index 100% rename from crates/agent_ui/src/max_mode_tooltip.rs rename to crates/agent_ui/src/burn_mode_tooltip.rs diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index 39f83d50cb51208521e57e57d1807c8a6371d3d4..015b50a801f87b754257ae3f9e27d662b3ad1fa6 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -575,7 +575,7 @@ impl MessageEditor { fn render_burn_mode_toggle(&self, cx: &mut Context) -> Option { let thread = self.thread.read(cx); let model = thread.configured_model(); - if !model?.model.supports_max_mode() { + if !model?.model.supports_burn_mode() { return None; } diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index c035282c9270ecdb786d221e76739b5886d3a092..49e5e27254241faa92347be50775c3509f1ea6b2 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -1,8 +1,8 @@ use crate::{ + burn_mode_tooltip::MaxModeTooltip, language_model_selector::{ LanguageModelSelector, ToggleModelSelector, language_model_selector, }, - max_mode_tooltip::MaxModeTooltip, }; use agent_settings::{AgentSettings, CompletionMode}; use anyhow::Result; @@ -2075,12 +2075,12 @@ impl TextThreadEditor { ) } - fn render_max_mode_toggle(&self, cx: &mut Context) -> Option { + fn render_burn_mode_toggle(&self, cx: &mut Context) -> Option { let context = self.context().read(cx); let active_model = LanguageModelRegistry::read_global(cx) .default_model() .map(|default| default.model)?; - if !active_model.supports_max_mode() { + if !active_model.supports_burn_mode() { return None; } @@ -2575,7 +2575,7 @@ impl Render for TextThreadEditor { }; let language_model_selector = self.language_model_selector_menu_handle.clone(); - let max_mode_toggle = self.render_max_mode_toggle(cx); + let burn_mode_toggle = self.render_burn_mode_toggle(cx); v_flex() .key_context("ContextEditor") @@ -2630,7 +2630,7 @@ impl Render for TextThreadEditor { h_flex() .gap_0p5() .child(self.render_inject_context_menu(cx)) - .when_some(max_mode_toggle, |this, element| this.child(element)), + .when_some(burn_mode_toggle, |this, element| this.child(element)), ) .child( h_flex() diff --git a/crates/agent_ui/src/ui.rs b/crates/agent_ui/src/ui.rs index c9adc2a63177bfad71203f37f79a140639605702..c076d113b8946c8bc9d85dd89672f3417f4bc15a 100644 --- a/crates/agent_ui/src/ui.rs +++ b/crates/agent_ui/src/ui.rs @@ -1,13 +1,13 @@ mod agent_notification; mod animated_label; +mod burn_mode_tooltip; mod context_pill; -mod max_mode_tooltip; mod onboarding_modal; pub mod preview; mod upsell; pub use agent_notification::*; pub use animated_label::*; +pub use burn_mode_tooltip::*; pub use context_pill::*; -pub use max_mode_tooltip::*; pub use onboarding_modal::*; diff --git a/crates/agent_ui/src/ui/max_mode_tooltip.rs b/crates/agent_ui/src/ui/burn_mode_tooltip.rs similarity index 100% rename from crates/agent_ui/src/ui/max_mode_tooltip.rs rename to crates/agent_ui/src/ui/burn_mode_tooltip.rs diff --git a/crates/assistant_context/src/assistant_context.rs b/crates/assistant_context/src/assistant_context.rs index cef9d2f0fd60c842883fcff80766416ca3db66de..0be8afcf698b664b490adab7e3772c1643e7ff2e 100644 --- a/crates/assistant_context/src/assistant_context.rs +++ b/crates/assistant_context/src/assistant_context.rs @@ -2346,13 +2346,13 @@ impl AssistantContext { completion_request.messages.push(request_message); } } - let supports_max_mode = if let Some(model) = model { - model.supports_max_mode() + let supports_burn_mode = if let Some(model) = model { + model.supports_burn_mode() } else { false }; - if supports_max_mode { + if supports_burn_mode { completion_request.mode = Some(self.completion_mode.into()); } completion_request diff --git a/crates/language_model/src/language_model.rs b/crates/language_model/src/language_model.rs index f84357bd98936e478a826df9a4d0563f2c857e10..ccde40c05f55250fa8d68a88e256b4fe246fda31 100644 --- a/crates/language_model/src/language_model.rs +++ b/crates/language_model/src/language_model.rs @@ -294,7 +294,7 @@ pub trait LanguageModel: Send + Sync { fn supports_tool_choice(&self, choice: LanguageModelToolChoice) -> bool; /// Returns whether this model supports "burn mode"; - fn supports_max_mode(&self) -> bool { + fn supports_burn_mode(&self) -> bool { false } diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index 58902850ea1d66d843306e9612a0ed2538a29ac9..62a24282dd1efb54b1f6a3ba25d65f559c59fd2e 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -695,7 +695,7 @@ impl LanguageModel for CloudLanguageModel { } } - fn supports_max_mode(&self) -> bool { + fn supports_burn_mode(&self) -> bool { self.model.supports_max_mode } From e6bc1308af109a6a149f9aa003dc904f604c53f9 Mon Sep 17 00:00:00 2001 From: Ron Harel <55725807+ronharel02@users.noreply.github.com> Date: Fri, 27 Jun 2025 12:08:05 +0300 Subject: [PATCH 014/107] Add SVG preview (#32694) Closes #10454 Implements SVG file preview capability similar to the existing markdown preview. - Adds `svg_preview` crate with preview view and live reloading upon file save. - Integrates SVG preview button in quick action bar. - File preview shortcuts (`ctrl/cmd+k v` and `ctrl/cmd+shift+v`) are extension-aware. Release Notes: - Added SVG file preview, accessible via the quick action bar button or keyboard shortcuts (`ctrl/cmd+k v` and `ctrl/cmd+shift+v`) when editing SVG files. --- Cargo.lock | 13 + Cargo.toml | 2 + assets/keymaps/default-linux.json | 18 +- assets/keymaps/default-macos.json | 18 +- crates/auto_update_ui/src/auto_update_ui.rs | 4 +- .../src/markdown_preview_view.rs | 37 +- crates/svg_preview/Cargo.toml | 20 ++ crates/svg_preview/LICENSE-GPL | 1 + crates/svg_preview/src/svg_preview.rs | 19 ++ crates/svg_preview/src/svg_preview_view.rs | 323 ++++++++++++++++++ crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 1 + crates/zed/src/zed.rs | 1 + crates/zed/src/zed/quick_action_bar.rs | 5 +- .../zed/quick_action_bar/markdown_preview.rs | 63 ---- .../zed/src/zed/quick_action_bar/preview.rs | 95 ++++++ 16 files changed, 528 insertions(+), 93 deletions(-) create mode 100644 crates/svg_preview/Cargo.toml create mode 120000 crates/svg_preview/LICENSE-GPL create mode 100644 crates/svg_preview/src/svg_preview.rs create mode 100644 crates/svg_preview/src/svg_preview_view.rs delete mode 100644 crates/zed/src/zed/quick_action_bar/markdown_preview.rs create mode 100644 crates/zed/src/zed/quick_action_bar/preview.rs diff --git a/Cargo.lock b/Cargo.lock index 7778b00ee775fa49c6a6f756c215baf48294455b..19e105e9a31692fc8ec251459a6de2e10e22289b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15526,6 +15526,18 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" +[[package]] +name = "svg_preview" +version = "0.1.0" +dependencies = [ + "editor", + "file_icons", + "gpui", + "ui", + "workspace", + "workspace-hack", +] + [[package]] name = "svgtypes" version = "0.15.3" @@ -20021,6 +20033,7 @@ dependencies = [ "snippet_provider", "snippets_ui", "supermaven", + "svg_preview", "sysinfo", "tab_switcher", "task", diff --git a/Cargo.toml b/Cargo.toml index 22377ccb4063f65f8770c0802002aee4c83ea4ec..4239fcf1e9e52d63282ac879067da3bba47acf25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,6 +95,7 @@ members = [ "crates/markdown_preview", "crates/media", "crates/menu", + "crates/svg_preview", "crates/migrator", "crates/mistral", "crates/multi_buffer", @@ -304,6 +305,7 @@ lmstudio = { path = "crates/lmstudio" } lsp = { path = "crates/lsp" } markdown = { path = "crates/markdown" } markdown_preview = { path = "crates/markdown_preview" } +svg_preview = { path = "crates/svg_preview" } media = { path = "crates/media" } menu = { path = "crates/menu" } migrator = { path = "crates/migrator" } diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index e21005816b84aca4e0c8e8c4e4bea2cb2003d4a6..525907a71a9f9eaea7a02f8eafdf9b5e15faaf4b 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -491,13 +491,27 @@ "ctrl-k r": "editor::RevealInFileManager", "ctrl-k p": "editor::CopyPath", "ctrl-\\": "pane::SplitRight", - "ctrl-k v": "markdown::OpenPreviewToTheSide", - "ctrl-shift-v": "markdown::OpenPreview", "ctrl-alt-shift-c": "editor::DisplayCursorNames", "alt-.": "editor::GoToHunk", "alt-,": "editor::GoToPreviousHunk" } }, + { + "context": "Editor && extension == md", + "use_key_equivalents": true, + "bindings": { + "ctrl-k v": "markdown::OpenPreviewToTheSide", + "ctrl-shift-v": "markdown::OpenPreview" + } + }, + { + "context": "Editor && extension == svg", + "use_key_equivalents": true, + "bindings": { + "ctrl-k v": "svg::OpenPreviewToTheSide", + "ctrl-shift-v": "svg::OpenPreview" + } + }, { "context": "Editor && mode == full", "bindings": { diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 51f4ffe23f255f31becbec25e15d20955ec058f9..121dbe93e086bf79bf8d4ac9c55e5b29433e2fc7 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -545,11 +545,25 @@ "cmd-k r": "editor::RevealInFileManager", "cmd-k p": "editor::CopyPath", "cmd-\\": "pane::SplitRight", - "cmd-k v": "markdown::OpenPreviewToTheSide", - "cmd-shift-v": "markdown::OpenPreview", "ctrl-cmd-c": "editor::DisplayCursorNames" } }, + { + "context": "Editor && extension == md", + "use_key_equivalents": true, + "bindings": { + "cmd-k v": "markdown::OpenPreviewToTheSide", + "cmd-shift-v": "markdown::OpenPreview" + } + }, + { + "context": "Editor && extension == svg", + "use_key_equivalents": true, + "bindings": { + "cmd-k v": "svg::OpenPreviewToTheSide", + "cmd-shift-v": "svg::OpenPreview" + } + }, { "context": "Editor && mode == full", "use_key_equivalents": true, diff --git a/crates/auto_update_ui/src/auto_update_ui.rs b/crates/auto_update_ui/src/auto_update_ui.rs index 30c1cddec2935d82f2ecc9fe0cfc569999d80d7b..afb135bc974f56d04db93e2a902fe48a64ab8ea7 100644 --- a/crates/auto_update_ui/src/auto_update_ui.rs +++ b/crates/auto_update_ui/src/auto_update_ui.rs @@ -1,7 +1,7 @@ use auto_update::AutoUpdater; use client::proto::UpdateNotification; use editor::{Editor, MultiBuffer}; -use gpui::{App, Context, DismissEvent, Entity, SharedString, Window, actions, prelude::*}; +use gpui::{App, Context, DismissEvent, Entity, Window, actions, prelude::*}; use http_client::HttpClient; use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView}; use release_channel::{AppVersion, ReleaseChannel}; @@ -94,7 +94,6 @@ fn view_release_notes_locally( let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); - let tab_content = Some(SharedString::from(body.title.to_string())); let editor = cx.new(|cx| { Editor::for_multibuffer(buffer, Some(project), window, cx) }); @@ -105,7 +104,6 @@ fn view_release_notes_locally( editor, workspace_handle, language_registry, - tab_content, window, cx, ); diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index 40c1783482f8b2a91126962d58b84b495e96a039..bf1a1da5727a9143e844921dabd770728dc8bcf0 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -17,10 +17,9 @@ use ui::prelude::*; use workspace::item::{Item, ItemHandle}; use workspace::{Pane, Workspace}; -use crate::OpenPreviewToTheSide; use crate::markdown_elements::ParsedMarkdownElement; use crate::{ - OpenFollowingPreview, OpenPreview, + OpenFollowingPreview, OpenPreview, OpenPreviewToTheSide, markdown_elements::ParsedMarkdown, markdown_parser::parse_markdown, markdown_renderer::{RenderContext, render_markdown_block}, @@ -36,7 +35,6 @@ pub struct MarkdownPreviewView { contents: Option, selected_block: usize, list_state: ListState, - tab_content_text: Option, language_registry: Arc, parsing_markdown_task: Option>>, mode: MarkdownPreviewMode, @@ -173,7 +171,6 @@ impl MarkdownPreviewView { editor, workspace_handle, language_registry, - None, window, cx, ) @@ -192,7 +189,6 @@ impl MarkdownPreviewView { editor, workspace_handle, language_registry, - None, window, cx, ) @@ -203,7 +199,6 @@ impl MarkdownPreviewView { active_editor: Entity, workspace: WeakEntity, language_registry: Arc, - tab_content_text: Option, window: &mut Window, cx: &mut Context, ) -> Entity { @@ -324,7 +319,6 @@ impl MarkdownPreviewView { workspace: workspace.clone(), contents: None, list_state, - tab_content_text, language_registry, parsing_markdown_task: None, image_cache: RetainAllImageCache::new(cx), @@ -405,12 +399,6 @@ impl MarkdownPreviewView { }, ); - let tab_content = editor.read(cx).tab_content_text(0, cx); - - if self.tab_content_text.is_none() { - self.tab_content_text = Some(format!("Preview {}", tab_content).into()); - } - self.active_editor = Some(EditorState { editor, _subscription: subscription, @@ -547,21 +535,28 @@ impl Focusable for MarkdownPreviewView { } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum PreviewEvent {} - -impl EventEmitter for MarkdownPreviewView {} +impl EventEmitter<()> for MarkdownPreviewView {} impl Item for MarkdownPreviewView { - type Event = PreviewEvent; + type Event = (); fn tab_icon(&self, _window: &Window, _cx: &App) -> Option { Some(Icon::new(IconName::FileDoc)) } - fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { - self.tab_content_text - .clone() + fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString { + self.active_editor + .as_ref() + .and_then(|editor_state| { + let buffer = editor_state.editor.read(cx).buffer().read(cx); + let buffer = buffer.as_singleton()?; + let file = buffer.read(cx).file()?; + let local_file = file.as_local()?; + local_file + .abs_path(cx) + .file_name() + .map(|name| format!("Preview {}", name.to_string_lossy()).into()) + }) .unwrap_or_else(|| SharedString::from("Markdown Preview")) } diff --git a/crates/svg_preview/Cargo.toml b/crates/svg_preview/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b783d7192cce888218617b46e935f8c689b70a56 --- /dev/null +++ b/crates/svg_preview/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "svg_preview" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/svg_preview.rs" + +[dependencies] +editor.workspace = true +file_icons.workspace = true +gpui.workspace = true +ui.workspace = true +workspace.workspace = true +workspace-hack.workspace = true diff --git a/crates/svg_preview/LICENSE-GPL b/crates/svg_preview/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/svg_preview/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/svg_preview/src/svg_preview.rs b/crates/svg_preview/src/svg_preview.rs new file mode 100644 index 0000000000000000000000000000000000000000..cbee76be834b6db23860c2a67e8e8030c81a01b7 --- /dev/null +++ b/crates/svg_preview/src/svg_preview.rs @@ -0,0 +1,19 @@ +use gpui::{App, actions}; +use workspace::Workspace; + +pub mod svg_preview_view; + +actions!( + svg, + [OpenPreview, OpenPreviewToTheSide, OpenFollowingPreview] +); + +pub fn init(cx: &mut App) { + cx.observe_new(|workspace: &mut Workspace, window, cx| { + let Some(window) = window else { + return; + }; + crate::svg_preview_view::SvgPreviewView::register(workspace, window, cx); + }) + .detach(); +} diff --git a/crates/svg_preview/src/svg_preview_view.rs b/crates/svg_preview/src/svg_preview_view.rs new file mode 100644 index 0000000000000000000000000000000000000000..327856d74989ba5cabab631486fd27133e3f684e --- /dev/null +++ b/crates/svg_preview/src/svg_preview_view.rs @@ -0,0 +1,323 @@ +use std::path::PathBuf; + +use editor::{Editor, EditorEvent}; +use file_icons::FileIcons; +use gpui::{ + App, Context, Entity, EventEmitter, FocusHandle, Focusable, ImageSource, IntoElement, + ParentElement, Render, Resource, RetainAllImageCache, Styled, Subscription, WeakEntity, Window, + div, img, +}; +use ui::prelude::*; +use workspace::item::Item; +use workspace::{Pane, Workspace}; + +use crate::{OpenFollowingPreview, OpenPreview, OpenPreviewToTheSide}; + +pub struct SvgPreviewView { + focus_handle: FocusHandle, + svg_path: Option, + image_cache: Entity, + _editor_subscription: Subscription, + _workspace_subscription: Option, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum SvgPreviewMode { + /// The preview will always show the contents of the provided editor. + Default, + /// The preview will "follow" the last active editor of an SVG file. + Follow, +} + +impl SvgPreviewView { + pub fn register(workspace: &mut Workspace, _window: &mut Window, _cx: &mut Context) { + workspace.register_action(move |workspace, _: &OpenPreview, window, cx| { + if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx) { + if Self::is_svg_file(&editor, cx) { + let view = Self::create_svg_view( + SvgPreviewMode::Default, + workspace, + editor.clone(), + window, + cx, + ); + workspace.active_pane().update(cx, |pane, cx| { + if let Some(existing_view_idx) = + Self::find_existing_preview_item_idx(pane, &editor, cx) + { + pane.activate_item(existing_view_idx, true, true, window, cx); + } else { + pane.add_item(Box::new(view), true, true, None, window, cx) + } + }); + cx.notify(); + } + } + }); + + workspace.register_action(move |workspace, _: &OpenPreviewToTheSide, window, cx| { + if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx) { + if Self::is_svg_file(&editor, cx) { + let editor_clone = editor.clone(); + let view = Self::create_svg_view( + SvgPreviewMode::Default, + workspace, + editor_clone, + window, + cx, + ); + let pane = workspace + .find_pane_in_direction(workspace::SplitDirection::Right, cx) + .unwrap_or_else(|| { + workspace.split_pane( + workspace.active_pane().clone(), + workspace::SplitDirection::Right, + window, + cx, + ) + }); + pane.update(cx, |pane, cx| { + if let Some(existing_view_idx) = + Self::find_existing_preview_item_idx(pane, &editor, cx) + { + pane.activate_item(existing_view_idx, true, true, window, cx); + } else { + pane.add_item(Box::new(view), false, false, None, window, cx) + } + }); + cx.notify(); + } + } + }); + + workspace.register_action(move |workspace, _: &OpenFollowingPreview, window, cx| { + if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx) { + if Self::is_svg_file(&editor, cx) { + let view = Self::create_svg_view( + SvgPreviewMode::Follow, + workspace, + editor, + window, + cx, + ); + workspace.active_pane().update(cx, |pane, cx| { + pane.add_item(Box::new(view), true, true, None, window, cx) + }); + cx.notify(); + } + } + }); + } + + fn find_existing_preview_item_idx( + pane: &Pane, + editor: &Entity, + cx: &App, + ) -> Option { + let editor_path = Self::get_svg_path(editor, cx); + pane.items_of_type::() + .find(|view| { + let view_read = view.read(cx); + view_read.svg_path.is_some() && view_read.svg_path == editor_path + }) + .and_then(|view| pane.index_for_item(&view)) + } + + pub fn resolve_active_item_as_svg_editor( + workspace: &Workspace, + cx: &mut Context, + ) -> Option> { + let editor = workspace.active_item(cx)?.act_as::(cx)?; + + if Self::is_svg_file(&editor, cx) { + Some(editor) + } else { + None + } + } + + fn create_svg_view( + mode: SvgPreviewMode, + workspace: &mut Workspace, + editor: Entity, + window: &mut Window, + cx: &mut Context, + ) -> Entity { + let workspace_handle = workspace.weak_handle(); + SvgPreviewView::new(mode, editor, workspace_handle, window, cx) + } + + pub fn new( + mode: SvgPreviewMode, + active_editor: Entity, + workspace_handle: WeakEntity, + window: &mut Window, + cx: &mut Context, + ) -> Entity { + cx.new(|cx| { + let svg_path = Self::get_svg_path(&active_editor, cx); + let image_cache = RetainAllImageCache::new(cx); + + let subscription = cx.subscribe_in( + &active_editor, + window, + |this: &mut SvgPreviewView, _editor, event: &EditorEvent, window, cx| { + match event { + EditorEvent::Saved => { + // Remove cached image to force reload + if let Some(svg_path) = &this.svg_path { + let resource = Resource::Path(svg_path.clone().into()); + this.image_cache.update(cx, |cache, cx| { + cache.remove(&resource, window, cx); + }); + } + cx.notify(); + } + _ => {} + } + }, + ); + + // Subscribe to workspace active item changes to follow SVG files + let workspace_subscription = if mode == SvgPreviewMode::Follow { + workspace_handle.upgrade().map(|workspace_handle| { + cx.subscribe_in( + &workspace_handle, + window, + |this: &mut SvgPreviewView, + workspace, + event: &workspace::Event, + _window, + cx| { + match event { + workspace::Event::ActiveItemChanged => { + let workspace_read = workspace.read(cx); + if let Some(active_item) = workspace_read.active_item(cx) { + if let Some(editor_entity) = + active_item.downcast::() + { + if Self::is_svg_file(&editor_entity, cx) { + let new_path = + Self::get_svg_path(&editor_entity, cx); + if this.svg_path != new_path { + this.svg_path = new_path; + cx.notify(); + } + } + } + } + } + _ => {} + } + }, + ) + }) + } else { + None + }; + + Self { + focus_handle: cx.focus_handle(), + svg_path, + image_cache, + _editor_subscription: subscription, + _workspace_subscription: workspace_subscription, + } + }) + } + + pub fn is_svg_file(editor: &Entity, cx: &C) -> bool + where + C: std::borrow::Borrow, + { + let app = cx.borrow(); + let buffer = editor.read(app).buffer().read(app); + if let Some(buffer) = buffer.as_singleton() { + if let Some(file) = buffer.read(app).file() { + return file + .path() + .extension() + .and_then(|ext| ext.to_str()) + .map(|ext| ext.eq_ignore_ascii_case("svg")) + .unwrap_or(false); + } + } + false + } + + fn get_svg_path(editor: &Entity, cx: &C) -> Option + where + C: std::borrow::Borrow, + { + let app = cx.borrow(); + let buffer = editor.read(app).buffer().read(app).as_singleton()?; + let file = buffer.read(app).file()?; + let local_file = file.as_local()?; + Some(local_file.abs_path(app)) + } +} + +impl Render for SvgPreviewView { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + v_flex() + .id("SvgPreview") + .key_context("SvgPreview") + .track_focus(&self.focus_handle(cx)) + .size_full() + .bg(cx.theme().colors().editor_background) + .flex() + .justify_center() + .items_center() + .child(if let Some(svg_path) = &self.svg_path { + img(ImageSource::from(svg_path.clone())) + .image_cache(&self.image_cache) + .max_w_full() + .max_h_full() + .with_fallback(|| { + div() + .p_4() + .child("Failed to load SVG file") + .into_any_element() + }) + .into_any_element() + } else { + div().p_4().child("No SVG file selected").into_any_element() + }) + } +} + +impl Focusable for SvgPreviewView { + fn focus_handle(&self, _cx: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl EventEmitter<()> for SvgPreviewView {} + +impl Item for SvgPreviewView { + type Event = (); + + fn tab_icon(&self, _window: &Window, cx: &App) -> Option { + // Use the same icon as SVG files in the file tree + self.svg_path + .as_ref() + .and_then(|svg_path| FileIcons::get_icon(svg_path, cx)) + .map(Icon::from_path) + .or_else(|| Some(Icon::new(IconName::Image))) + } + + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + self.svg_path + .as_ref() + .and_then(|svg_path| svg_path.file_name()) + .map(|name| name.to_string_lossy()) + .map(|name| format!("Preview {}", name).into()) + .unwrap_or_else(|| "SVG Preview".into()) + } + + fn telemetry_event_text(&self) -> Option<&'static str> { + Some("svg preview: open") + } + + fn to_item_events(_event: &Self::Event, _f: impl FnMut(workspace::item::ItemEvent)) {} +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 534d79c6ac4fb5ab792482f248021ee71197d082..4e426c3837f969802d0dc75de872412cae0567a5 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -85,6 +85,7 @@ libc.workspace = true log.workspace = true markdown.workspace = true markdown_preview.workspace = true +svg_preview.workspace = true menu.workspace = true migrator.workspace = true mimalloc = { version = "0.1", optional = true } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 0e08b304f7c09d225c1da8449de1fd093512bf74..00a1f150eae5bb87e62fd52f1464101e3a9fd750 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -582,6 +582,7 @@ pub fn main() { jj_ui::init(cx); feedback::init(cx); markdown_preview::init(cx); + svg_preview::init(cx); welcome::init(cx); settings_ui::init(cx); extensions_ui::init(cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 5ab4b672ae4023d8a485806146992181f4ec7d7b..ca1670227b903e2e2365edeba8fca69cd3e48df3 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -4323,6 +4323,7 @@ mod tests { "search", "snippets", "supermaven", + "svg", "tab_switcher", "task", "terminal", diff --git a/crates/zed/src/zed/quick_action_bar.rs b/crates/zed/src/zed/quick_action_bar.rs index 52c4ff3831e4b8ede0d4aea5fe2b22ec9fda3f81..85e28c6ae826b479731e35397d8d4195628a06d4 100644 --- a/crates/zed/src/zed/quick_action_bar.rs +++ b/crates/zed/src/zed/quick_action_bar.rs @@ -1,5 +1,6 @@ -mod markdown_preview; +mod preview; mod repl_menu; + use agent_settings::AgentSettings; use editor::actions::{ AddSelectionAbove, AddSelectionBelow, CodeActionSource, DuplicateLineDown, GoToDiagnostic, @@ -571,7 +572,7 @@ impl Render for QuickActionBar { .id("quick action bar") .gap(DynamicSpacing::Base01.rems(cx)) .children(self.render_repl_menu(cx)) - .children(self.render_toggle_markdown_preview(self.workspace.clone(), cx)) + .children(self.render_preview_button(self.workspace.clone(), cx)) .children(search_button) .when( AgentSettings::get_global(cx).enabled && AgentSettings::get_global(cx).button, diff --git a/crates/zed/src/zed/quick_action_bar/markdown_preview.rs b/crates/zed/src/zed/quick_action_bar/markdown_preview.rs deleted file mode 100644 index 44008f71100bed6a39874cdfa807a3046842ae53..0000000000000000000000000000000000000000 --- a/crates/zed/src/zed/quick_action_bar/markdown_preview.rs +++ /dev/null @@ -1,63 +0,0 @@ -use gpui::{AnyElement, Modifiers, WeakEntity}; -use markdown_preview::{ - OpenPreview, OpenPreviewToTheSide, markdown_preview_view::MarkdownPreviewView, -}; -use ui::{IconButtonShape, Tooltip, prelude::*, text_for_keystroke}; -use workspace::Workspace; - -use super::QuickActionBar; - -impl QuickActionBar { - pub fn render_toggle_markdown_preview( - &self, - workspace: WeakEntity, - cx: &mut Context, - ) -> Option { - let mut active_editor_is_markdown = false; - - if let Some(workspace) = self.workspace.upgrade() { - workspace.update(cx, |workspace, cx| { - active_editor_is_markdown = - MarkdownPreviewView::resolve_active_item_as_markdown_editor(workspace, cx) - .is_some(); - }); - } - - if !active_editor_is_markdown { - return None; - } - - let alt_click = gpui::Keystroke { - key: "click".into(), - modifiers: Modifiers::alt(), - ..Default::default() - }; - - let button = IconButton::new("toggle-markdown-preview", IconName::Eye) - .shape(IconButtonShape::Square) - .icon_size(IconSize::Small) - .style(ButtonStyle::Subtle) - .tooltip(move |window, cx| { - Tooltip::with_meta( - "Preview Markdown", - Some(&markdown_preview::OpenPreview), - format!("{} to open in a split", text_for_keystroke(&alt_click, cx)), - window, - cx, - ) - }) - .on_click(move |_, window, cx| { - if let Some(workspace) = workspace.upgrade() { - workspace.update(cx, |_, cx| { - if window.modifiers().alt { - window.dispatch_action(Box::new(OpenPreviewToTheSide), cx); - } else { - window.dispatch_action(Box::new(OpenPreview), cx); - } - }); - } - }); - - Some(button.into_any_element()) - } -} diff --git a/crates/zed/src/zed/quick_action_bar/preview.rs b/crates/zed/src/zed/quick_action_bar/preview.rs new file mode 100644 index 0000000000000000000000000000000000000000..57775d31fd74f859793141ce179473ee7777dde6 --- /dev/null +++ b/crates/zed/src/zed/quick_action_bar/preview.rs @@ -0,0 +1,95 @@ +use gpui::{AnyElement, Modifiers, WeakEntity}; +use markdown_preview::{ + OpenPreview as MarkdownOpenPreview, OpenPreviewToTheSide as MarkdownOpenPreviewToTheSide, + markdown_preview_view::MarkdownPreviewView, +}; +use svg_preview::{ + OpenPreview as SvgOpenPreview, OpenPreviewToTheSide as SvgOpenPreviewToTheSide, + svg_preview_view::SvgPreviewView, +}; +use ui::{IconButtonShape, Tooltip, prelude::*, text_for_keystroke}; +use workspace::Workspace; + +use super::QuickActionBar; + +#[derive(Clone, Copy)] +enum PreviewType { + Markdown, + Svg, +} + +impl QuickActionBar { + pub fn render_preview_button( + &self, + workspace_handle: WeakEntity, + cx: &mut Context, + ) -> Option { + let mut preview_type = None; + + if let Some(workspace) = self.workspace.upgrade() { + workspace.update(cx, |workspace, cx| { + if MarkdownPreviewView::resolve_active_item_as_markdown_editor(workspace, cx) + .is_some() + { + preview_type = Some(PreviewType::Markdown); + } else if SvgPreviewView::resolve_active_item_as_svg_editor(workspace, cx).is_some() + { + preview_type = Some(PreviewType::Svg); + } + }); + } + + let preview_type = preview_type?; + + let (button_id, tooltip_text, open_action, open_to_side_action, open_action_for_tooltip) = + match preview_type { + PreviewType::Markdown => ( + "toggle-markdown-preview", + "Preview Markdown", + Box::new(MarkdownOpenPreview) as Box, + Box::new(MarkdownOpenPreviewToTheSide) as Box, + &markdown_preview::OpenPreview as &dyn gpui::Action, + ), + PreviewType::Svg => ( + "toggle-svg-preview", + "Preview SVG", + Box::new(SvgOpenPreview) as Box, + Box::new(SvgOpenPreviewToTheSide) as Box, + &svg_preview::OpenPreview as &dyn gpui::Action, + ), + }; + + let alt_click = gpui::Keystroke { + key: "click".into(), + modifiers: Modifiers::alt(), + ..Default::default() + }; + + let button = IconButton::new(button_id, IconName::Eye) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .style(ButtonStyle::Subtle) + .tooltip(move |window, cx| { + Tooltip::with_meta( + tooltip_text, + Some(open_action_for_tooltip), + format!("{} to open in a split", text_for_keystroke(&alt_click, cx)), + window, + cx, + ) + }) + .on_click(move |_, window, cx| { + if let Some(workspace) = workspace_handle.upgrade() { + workspace.update(cx, |_, cx| { + if window.modifiers().alt { + window.dispatch_action(open_to_side_action.boxed_clone(), cx); + } else { + window.dispatch_action(open_action.boxed_clone(), cx); + } + }); + } + }); + + Some(button.into_any_element()) + } +} From 4c2415b3380c6d7456dba72693968adf3854d30d Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Fri, 27 Jun 2025 11:32:50 +0200 Subject: [PATCH 015/107] editor: Use `em_advance` everywhere for horizontal scroll position computations (#33514) Closes #33472 This PR fixes some regressions that were introduced in https://github.com/zed-industries/zed/pull/32558, which updated the editor scrolling to use `em_advance` instead of `em_width` for the horizontal scroll position calculation. However, not all occurrences were updated, which caused issues with wrap guides and some small stuttering with horizontal autoscroll whilst typing/navigating with the keyboard. Release Notes: - Fixed an issue where horizontal autoscrolling would stutter and indent guides would drift when scrolling horizontally. --- crates/editor/src/editor.rs | 29 +++++++++++++++++-------- crates/editor/src/element.rs | 14 ++++++------ crates/editor/src/mouse_context_menu.rs | 4 ++-- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8ef52b84969fd61b3ddd6d2cfb1eb95227bcb265..aad9e96d3a18dfa64a8db4247c6f146e579b8b8c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1215,6 +1215,12 @@ impl GutterDimensions { } } +struct CharacterDimensions { + em_width: Pixels, + em_advance: Pixels, + line_height: Pixels, +} + #[derive(Debug)] pub struct RemoteSelection { pub replica_id: ReplicaId, @@ -20520,15 +20526,20 @@ impl Editor { .and_then(|item| item.to_any_mut()?.downcast_mut::()) } - fn character_size(&self, window: &mut Window) -> gpui::Size { + fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions { let text_layout_details = self.text_layout_details(window); let style = &text_layout_details.editor_style; let font_id = window.text_system().resolve_font(&style.text.font()); let font_size = style.text.font_size.to_pixels(window.rem_size()); let line_height = style.text.line_height_in_pixels(window.rem_size()); let em_width = window.text_system().em_width(font_id, font_size).unwrap(); + let em_advance = window.text_system().em_advance(font_id, font_size).unwrap(); - gpui::Size::new(em_width, line_height) + CharacterDimensions { + em_width, + em_advance, + line_height, + } } pub fn wait_for_diff_to_load(&self) -> Option>> { @@ -22542,19 +22553,19 @@ impl EntityInputHandler for Editor { cx: &mut Context, ) -> Option> { let text_layout_details = self.text_layout_details(window); - let gpui::Size { - width: em_width, - height: line_height, - } = self.character_size(window); + let CharacterDimensions { + em_width, + em_advance, + line_height, + } = self.character_dimensions(window); let snapshot = self.snapshot(window, cx); let scroll_position = snapshot.scroll_position(); - let scroll_left = scroll_position.x * em_width; + let scroll_left = scroll_position.x * em_advance; let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot); let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left - + self.gutter_dimensions.width - + self.gutter_dimensions.margin; + + self.gutter_dimensions.full_width(); let y = line_height * (start.row().as_f32() - scroll_position.y); Some(Bounds { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 602a0579b3a23b4449d08a732580ac261bd841c2..6fee347c17ea6b80a9767d5fcbf9094f6160ac5a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -5238,8 +5238,8 @@ impl EditorElement { paint_highlight(range.start, range.end, color, edges); } - let scroll_left = - layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width; + let scroll_left = layout.position_map.snapshot.scroll_position().x + * layout.position_map.em_advance; for (wrap_position, active) in layout.wrap_guides.iter() { let x = (layout.position_map.text_hitbox.origin.x @@ -6676,7 +6676,7 @@ impl EditorElement { let position_map: &PositionMap = &position_map; let line_height = position_map.line_height; - let max_glyph_width = position_map.em_width; + let max_glyph_advance = position_map.em_advance; let (delta, axis) = match delta { gpui::ScrollDelta::Pixels(mut pixels) => { //Trackpad @@ -6687,15 +6687,15 @@ impl EditorElement { gpui::ScrollDelta::Lines(lines) => { //Not trackpad let pixels = - point(lines.x * max_glyph_width, lines.y * line_height); + point(lines.x * max_glyph_advance, lines.y * line_height); (pixels, None) } }; let current_scroll_position = position_map.snapshot.scroll_position(); - let x = (current_scroll_position.x * max_glyph_width + let x = (current_scroll_position.x * max_glyph_advance - (delta.x * scroll_sensitivity)) - / max_glyph_width; + / max_glyph_advance; let y = (current_scroll_position.y * line_height - (delta.y * scroll_sensitivity)) / line_height; @@ -8591,7 +8591,7 @@ impl Element for EditorElement { start_row, editor_content_width, scroll_width, - em_width, + em_advance, &line_layouts, cx, ) diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index 408cccd33282e76c29817d6d69efc29b5365eff0..b9b8cbe997b2c6bbdd4f45e50e25621c037badf1 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -275,10 +275,10 @@ pub fn deploy_context_menu( cx, ), None => { - let character_size = editor.character_size(window); + let character_size = editor.character_dimensions(window); let menu_position = MenuPosition::PinnedToEditor { source: source_anchor, - offset: gpui::point(character_size.width, character_size.height), + offset: gpui::point(character_size.em_width, character_size.line_height), }; Some(MouseContextMenu::new( editor, From 338a7395a7af38ae4d88623e039d90371196175a Mon Sep 17 00:00:00 2001 From: ddoemonn <109994179+ddoemonn@users.noreply.github.com> Date: Fri, 27 Jun 2025 12:37:05 +0300 Subject: [PATCH 016/107] Fix blend alpha colors with editor background in inline preview (#33513) Closes #33505 ## Before Screenshot 2025-06-27 at 12 22 57 ## After Screenshot 2025-06-27 at 12 22 47 Release Notes: - Fixed inline color previews not correctly blending alpha/transparency values with the editor background --- crates/editor/src/display_map.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index fd371e20cbf22585d1dd2640f01104dd16428750..a10a5f074c1c1922f6cdb053e7a4b37dd2230312 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -966,10 +966,22 @@ impl DisplaySnapshot { .and_then(|id| id.style(&editor_style.syntax)); if let Some(chunk_highlight) = chunk.highlight_style { + // For color inlays, blend the color with the editor background + let mut processed_highlight = chunk_highlight; + if chunk.is_inlay { + if let Some(inlay_color) = chunk_highlight.color { + // Only blend if the color has transparency (alpha < 1.0) + if inlay_color.a < 1.0 { + let blended_color = editor_style.background.blend(inlay_color); + processed_highlight.color = Some(blended_color); + } + } + } + if let Some(highlight_style) = highlight_style.as_mut() { - highlight_style.highlight(chunk_highlight); + highlight_style.highlight(processed_highlight); } else { - highlight_style = Some(chunk_highlight); + highlight_style = Some(processed_highlight); } } From 2178f66af67a159fe689d7bc6571d966f28d6349 Mon Sep 17 00:00:00 2001 From: Umesh Yadav <23421535+imumesh18@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:26:46 +0530 Subject: [PATCH 017/107] agent_ui: Rename MaxModeTooltip to BurnModeTooltip (#33521) Closes #ISSUE Release Notes: - N/A --- crates/agent_ui/src/burn_mode_tooltip.rs | 6 +++--- crates/agent_ui/src/text_thread_editor.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/agent_ui/src/burn_mode_tooltip.rs b/crates/agent_ui/src/burn_mode_tooltip.rs index a3100d4367c7a7b43edc3cc2b9b3a821941074e6..6354c07760f5aa0261b69e8dd08ce1f1b1be6023 100644 --- a/crates/agent_ui/src/burn_mode_tooltip.rs +++ b/crates/agent_ui/src/burn_mode_tooltip.rs @@ -1,11 +1,11 @@ use gpui::{Context, FontWeight, IntoElement, Render, Window}; use ui::{prelude::*, tooltip_container}; -pub struct MaxModeTooltip { +pub struct BurnModeTooltip { selected: bool, } -impl MaxModeTooltip { +impl BurnModeTooltip { pub fn new() -> Self { Self { selected: false } } @@ -16,7 +16,7 @@ impl MaxModeTooltip { } } -impl Render for MaxModeTooltip { +impl Render for BurnModeTooltip { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let (icon, color) = if self.selected { (IconName::ZedBurnModeOn, Color::Error) diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 49e5e27254241faa92347be50775c3509f1ea6b2..645bc451fcb8fbb91d05eb0bfe72814ea630c988 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -1,5 +1,5 @@ use crate::{ - burn_mode_tooltip::MaxModeTooltip, + burn_mode_tooltip::BurnModeTooltip, language_model_selector::{ LanguageModelSelector, ToggleModelSelector, language_model_selector, }, @@ -2107,7 +2107,7 @@ impl TextThreadEditor { }); })) .tooltip(move |_window, cx| { - cx.new(|_| MaxModeTooltip::new().selected(burn_mode_enabled)) + cx.new(|_| BurnModeTooltip::new().selected(burn_mode_enabled)) .into() }) .into_any_element(), From 865dd4c5fceae1249030a5ee3e335f012e440a56 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 27 Jun 2025 16:25:56 +0300 Subject: [PATCH 018/107] Rework LSP tool keyboard story (#33525) https://github.com/user-attachments/assets/81da68fe-bbc5-4b23-8182-923c752a8bd2 * Removes all extra elements: headers, buttons, to simplify the menu navigation approach and save space. Implements the keyboard navigation and panel toggling. * Keeps the status icon and the server name, and their ordering approach (current buffer/other) in the menu. The status icon can still be hovered, but that is not yet possible to trigger from the keyboard: future ideas would be make a similar side display instead of hover, as Zeta menu does: ![image](https://github.com/user-attachments/assets/c844bc39-00ed-4fe3-96d5-1c9d323a21cc) * Allows to start (if all are stopped) and stop (if some are not stopped) all servers at once now with the button at the bottom Release Notes: - N/A --- crates/language_tools/src/lsp_tool.rs | 517 ++++++++++++++------------ crates/zed/src/zed.rs | 20 +- 2 files changed, 285 insertions(+), 252 deletions(-) diff --git a/crates/language_tools/src/lsp_tool.rs b/crates/language_tools/src/lsp_tool.rs index 140f9e3fe64e74ae2760a8a7cc8b1450b7f82497..899aaf0679689c344b3fe6dcac15d76d40009b5c 100644 --- a/crates/language_tools/src/lsp_tool.rs +++ b/crates/language_tools/src/lsp_tool.rs @@ -4,13 +4,16 @@ use client::proto; use collections::{HashMap, HashSet}; use editor::{Editor, EditorEvent}; use feature_flags::FeatureFlagAppExt as _; -use gpui::{Corner, DismissEvent, Entity, Focusable as _, Subscription, Task, WeakEntity, actions}; +use gpui::{ + Corner, DismissEvent, Entity, Focusable as _, MouseButton, Subscription, Task, WeakEntity, + actions, +}; use language::{BinaryStatus, BufferId, LocalFile, ServerHealth}; use lsp::{LanguageServerId, LanguageServerName, LanguageServerSelector}; use picker::{Picker, PickerDelegate, popover_menu::PickerPopoverMenu}; use project::{LspStore, LspStoreEvent, project_settings::ProjectSettings}; use settings::{Settings as _, SettingsStore}; -use ui::{Context, Indicator, Tooltip, Window, prelude::*}; +use ui::{Context, Indicator, PopoverMenuHandle, Tooltip, Window, prelude::*}; use workspace::{StatusItemView, Workspace}; @@ -20,6 +23,7 @@ actions!(lsp_tool, [ToggleMenu]); pub struct LspTool { state: Entity, + popover_menu_handle: PopoverMenuHandle>, lsp_picker: Option>>, _subscriptions: Vec, } @@ -32,7 +36,7 @@ struct PickerState { } #[derive(Debug)] -struct LspPickerDelegate { +pub struct LspPickerDelegate { state: Entity, selected_index: usize, items: Vec, @@ -65,6 +69,23 @@ struct LanguageServerBinaryStatus { message: Option, } +#[derive(Debug)] +struct ServerInfo { + name: LanguageServerName, + id: Option, + health: Option, + binary_status: Option, + message: Option, +} + +impl ServerInfo { + fn server_selector(&self) -> LanguageServerSelector { + self.id + .map(LanguageServerSelector::Id) + .unwrap_or_else(|| LanguageServerSelector::Name(self.name.clone())) + } +} + impl LanguageServerHealthStatus { fn health(&self) -> Option { self.health.as_ref().map(|(_, health)| *health) @@ -159,23 +180,57 @@ impl LspPickerDelegate { } } + let mut can_stop_all = false; + let mut can_restart_all = true; + for (server_name, status) in state .language_servers .binary_statuses .iter() .filter(|(name, _)| !servers_with_health_checks.contains(name)) { - let has_matching_server = state + match status.status { + BinaryStatus::None => { + can_restart_all = false; + can_stop_all = true; + } + BinaryStatus::CheckingForUpdate => { + can_restart_all = false; + } + BinaryStatus::Downloading => { + can_restart_all = false; + } + BinaryStatus::Starting => { + can_restart_all = false; + } + BinaryStatus::Stopping => { + can_restart_all = false; + } + BinaryStatus::Stopped => {} + BinaryStatus::Failed { .. } => {} + } + + let matching_server_id = state .language_servers .servers_per_buffer_abs_path .iter() .filter(|(path, _)| editor_buffer_paths.contains(path)) .flat_map(|(_, server_associations)| server_associations.iter()) - .any(|(_, name)| name.as_ref() == Some(server_name)); - if has_matching_server { - buffer_servers.push(ServerData::WithBinaryStatus(server_name, status)); + .find_map(|(id, name)| { + if name.as_ref() == Some(server_name) { + Some(*id) + } else { + None + } + }); + if let Some(server_id) = matching_server_id { + buffer_servers.push(ServerData::WithBinaryStatus( + Some(server_id), + server_name, + status, + )); } else { - other_servers.push(ServerData::WithBinaryStatus(server_name, status)); + other_servers.push(ServerData::WithBinaryStatus(None, server_name, status)); } } @@ -184,23 +239,52 @@ impl LspPickerDelegate { let mut other_servers_start_index = None; let mut new_lsp_items = - Vec::with_capacity(buffer_servers.len() + other_servers.len() + 2); - - if !buffer_servers.is_empty() { - new_lsp_items.push(LspItem::Header(SharedString::new("This Buffer"))); - new_lsp_items.extend(buffer_servers.into_iter().map(ServerData::into_lsp_item)); - } - - if !other_servers.is_empty() { + Vec::with_capacity(buffer_servers.len() + other_servers.len() + 1); + new_lsp_items.extend(buffer_servers.into_iter().map(ServerData::into_lsp_item)); + if !new_lsp_items.is_empty() { other_servers_start_index = Some(new_lsp_items.len()); - new_lsp_items.push(LspItem::Header(SharedString::new("Other Servers"))); - new_lsp_items.extend(other_servers.into_iter().map(ServerData::into_lsp_item)); + } + new_lsp_items.extend(other_servers.into_iter().map(ServerData::into_lsp_item)); + if !new_lsp_items.is_empty() { + if can_stop_all { + new_lsp_items.push(LspItem::ToggleServersButton { restart: false }); + } else if can_restart_all { + new_lsp_items.push(LspItem::ToggleServersButton { restart: true }); + } } self.items = new_lsp_items; self.other_servers_start_index = other_servers_start_index; }); } + + fn server_info(&self, ix: usize) -> Option { + match self.items.get(ix)? { + LspItem::ToggleServersButton { .. } => None, + LspItem::WithHealthCheck( + language_server_id, + language_server_health_status, + language_server_binary_status, + ) => Some(ServerInfo { + name: language_server_health_status.name.clone(), + id: Some(*language_server_id), + health: language_server_health_status.health(), + binary_status: language_server_binary_status.clone(), + message: language_server_health_status.message(), + }), + LspItem::WithBinaryStatus( + server_id, + language_server_name, + language_server_binary_status, + ) => Some(ServerInfo { + name: language_server_name.clone(), + id: *server_id, + health: None, + binary_status: Some(language_server_binary_status.clone()), + message: language_server_binary_status.message.clone(), + }), + } + } } impl LanguageServers { @@ -261,7 +345,11 @@ enum ServerData<'a> { &'a LanguageServerHealthStatus, Option<&'a LanguageServerBinaryStatus>, ), - WithBinaryStatus(&'a LanguageServerName, &'a LanguageServerBinaryStatus), + WithBinaryStatus( + Option, + &'a LanguageServerName, + &'a LanguageServerBinaryStatus, + ), } #[derive(Debug)] @@ -271,15 +359,21 @@ enum LspItem { LanguageServerHealthStatus, Option, ), - WithBinaryStatus(LanguageServerName, LanguageServerBinaryStatus), - Header(SharedString), + WithBinaryStatus( + Option, + LanguageServerName, + LanguageServerBinaryStatus, + ), + ToggleServersButton { + restart: bool, + }, } impl ServerData<'_> { fn name(&self) -> &LanguageServerName { match self { Self::WithHealthCheck(_, state, _) => &state.name, - Self::WithBinaryStatus(name, ..) => name, + Self::WithBinaryStatus(_, name, ..) => name, } } @@ -288,8 +382,8 @@ impl ServerData<'_> { Self::WithHealthCheck(id, name, status) => { LspItem::WithHealthCheck(id, name.clone(), status.cloned()) } - Self::WithBinaryStatus(name, status) => { - LspItem::WithBinaryStatus(name.clone(), status.clone()) + Self::WithBinaryStatus(server_id, name, status) => { + LspItem::WithBinaryStatus(server_id, name.clone(), status.clone()) } } } @@ -333,7 +427,81 @@ impl PickerDelegate for LspPickerDelegate { Arc::default() } - fn confirm(&mut self, _: bool, _: &mut Window, _: &mut Context>) {} + fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context>) { + if let Some(LspItem::ToggleServersButton { restart }) = self.items.get(self.selected_index) + { + let lsp_store = self.state.read(cx).lsp_store.clone(); + lsp_store + .update(cx, |lsp_store, cx| { + if *restart { + let Some(workspace) = self.state.read(cx).workspace.upgrade() else { + return; + }; + let project = workspace.read(cx).project().clone(); + let buffer_store = project.read(cx).buffer_store().clone(); + let worktree_store = project.read(cx).worktree_store(); + + let buffers = self + .state + .read(cx) + .language_servers + .servers_per_buffer_abs_path + .keys() + .filter_map(|abs_path| { + worktree_store.read(cx).find_worktree(abs_path, cx) + }) + .filter_map(|(worktree, relative_path)| { + let entry = worktree.read(cx).entry_for_path(&relative_path)?; + project.read(cx).path_for_entry(entry.id, cx) + }) + .filter_map(|project_path| { + buffer_store.read(cx).get_by_path(&project_path) + }) + .collect(); + let selectors = self + .items + .iter() + // Do not try to use IDs as we have stopped all servers already, when allowing to restart them all + .flat_map(|item| match item { + LspItem::ToggleServersButton { .. } => None, + LspItem::WithHealthCheck(_, status, ..) => { + Some(LanguageServerSelector::Name(status.name.clone())) + } + LspItem::WithBinaryStatus(_, server_name, ..) => { + Some(LanguageServerSelector::Name(server_name.clone())) + } + }) + .collect(); + lsp_store.restart_language_servers_for_buffers(buffers, selectors, cx); + } else { + lsp_store.stop_all_language_servers(cx); + } + }) + .ok(); + } + + let Some(server_selector) = self + .server_info(self.selected_index) + .map(|info| info.server_selector()) + else { + return; + }; + let lsp_logs = cx.global::().0.clone(); + let lsp_store = self.state.read(cx).lsp_store.clone(); + let workspace = self.state.read(cx).workspace.clone(); + lsp_logs + .update(cx, |lsp_logs, cx| { + let has_logs = lsp_store + .update(cx, |lsp_store, _| { + lsp_store.as_local().is_some() && lsp_logs.has_server_logs(&server_selector) + }) + .unwrap_or(false); + if has_logs { + lsp_logs.open_server_trace(workspace, server_selector, window, cx); + } + }) + .ok(); + } fn dismissed(&mut self, _: &mut Window, cx: &mut Context>) { cx.emit(DismissEvent); @@ -342,70 +510,47 @@ impl PickerDelegate for LspPickerDelegate { fn render_match( &self, ix: usize, - _: bool, + selected: bool, _: &mut Window, cx: &mut Context>, ) -> Option { - let is_other_server = self - .other_servers_start_index - .map_or(false, |start| ix >= start); - - let server_binary_status; - let server_health; - let server_message; - let server_id; - let server_name; + let rendered_match = h_flex().px_1().gap_1(); + let rendered_match_contents = h_flex() + .id(("lsp-item", ix)) + .w_full() + .px_2() + .gap_2() + .when(selected, |server_entry| { + server_entry.bg(cx.theme().colors().element_hover) + }) + .hover(|s| s.bg(cx.theme().colors().element_hover)); - match self.items.get(ix)? { - LspItem::WithHealthCheck( - language_server_id, - language_server_health_status, - language_server_binary_status, - ) => { - server_binary_status = language_server_binary_status.as_ref(); - server_health = language_server_health_status.health(); - server_message = language_server_health_status.message(); - server_id = Some(*language_server_id); - server_name = language_server_health_status.name.clone(); - } - LspItem::WithBinaryStatus(language_server_name, language_server_binary_status) => { - server_binary_status = Some(language_server_binary_status); - server_health = None; - server_message = language_server_binary_status.message.clone(); - server_id = None; - server_name = language_server_name.clone(); - } - LspItem::Header(header) => { - return Some( - div() - .px_2p5() - .mb_1() - .child( - Label::new(header.clone()) - .size(LabelSize::Small) - .color(Color::Muted), - ) - .into_any_element(), - ); - } - }; + if let Some(LspItem::ToggleServersButton { restart }) = self.items.get(ix) { + let label = Label::new(if *restart { + "Restart All Servers" + } else { + "Stop All Servers" + }); + return Some( + rendered_match + .child(rendered_match_contents.child(label)) + .into_any_element(), + ); + } + let server_info = self.server_info(ix)?; let workspace = self.state.read(cx).workspace.clone(); let lsp_logs = cx.global::().0.upgrade()?; let lsp_store = self.state.read(cx).lsp_store.upgrade()?; - let server_selector = server_id - .map(LanguageServerSelector::Id) - .unwrap_or_else(|| LanguageServerSelector::Name(server_name.clone())); - let can_stop = server_binary_status.is_none_or(|status| { - matches!(status.status, BinaryStatus::None | BinaryStatus::Starting) - }); + let server_selector = server_info.server_selector(); // TODO currently, Zed remote does not work well with the LSP logs // https://github.com/zed-industries/zed/issues/28557 let has_logs = lsp_store.read(cx).as_local().is_some() && lsp_logs.read(cx).has_server_logs(&server_selector); - let status_color = server_binary_status + let status_color = server_info + .binary_status .and_then(|binary_status| match binary_status.status { BinaryStatus::None => None, BinaryStatus::CheckingForUpdate @@ -416,7 +561,7 @@ impl PickerDelegate for LspPickerDelegate { BinaryStatus::Failed { .. } => Some(Color::Error), }) .or_else(|| { - Some(match server_health? { + Some(match server_info.health? { ServerHealth::Ok => Color::Success, ServerHealth::Warning => Color::Warning, ServerHealth::Error => Color::Error, @@ -425,153 +570,40 @@ impl PickerDelegate for LspPickerDelegate { .unwrap_or(Color::Success); Some( - h_flex() - .px_1() - .gap_1() - .justify_between() + rendered_match .child( - h_flex() - .id("server-status-indicator") - .px_2() - .gap_2() + rendered_match_contents .child(Indicator::dot().color(status_color)) - .child(Label::new(server_name.0.clone())) - .when_some(server_message.clone(), |div, server_message| { - div.tooltip(Tooltip::text(server_message.clone())) - }), + .child(Label::new(server_info.name.0.clone())) + .when_some( + server_info.message.clone(), + |server_entry, server_message| { + server_entry.tooltip(Tooltip::text(server_message.clone())) + }, + ), ) - .child( - h_flex() - .when(has_logs, |button_list| { - button_list.child( - IconButton::new("debug-language-server", IconName::LspDebug) - .icon_size(IconSize::Small) - .alpha(0.8) - .tooltip(Tooltip::text("Debug Language Server")) - .on_click({ - let workspace = workspace.clone(); - let lsp_logs = lsp_logs.downgrade(); - let server_selector = server_selector.clone(); - move |_, window, cx| { - lsp_logs - .update(cx, |lsp_logs, cx| { - lsp_logs.open_server_trace( - workspace.clone(), - server_selector.clone(), - window, - cx, - ); - }) - .ok(); - } - }), - ) - }) - .when(can_stop, |button_list| { - button_list.child( - IconButton::new("stop-server", IconName::LspStop) - .icon_size(IconSize::Small) - .alpha(0.8) - .tooltip(Tooltip::text("Stop Server")) - .on_click({ - let lsp_store = lsp_store.downgrade(); - let server_selector = server_selector.clone(); - move |_, _, cx| { - lsp_store - .update(cx, |lsp_store, cx| { - lsp_store.stop_language_servers_for_buffers( - Vec::new(), - HashSet::from_iter([ - server_selector.clone() - ]), - cx, - ); - }) - .ok(); - } - }), - ) + .when_else( + has_logs, + |server_entry| { + server_entry.on_mouse_down(MouseButton::Left, { + let workspace = workspace.clone(); + let lsp_logs = lsp_logs.downgrade(); + let server_selector = server_selector.clone(); + move |_, window, cx| { + lsp_logs + .update(cx, |lsp_logs, cx| { + lsp_logs.open_server_trace( + workspace.clone(), + server_selector.clone(), + window, + cx, + ); + }) + .ok(); + } }) - .child( - IconButton::new("restart-server", IconName::LspRestart) - .icon_size(IconSize::Small) - .alpha(0.8) - .tooltip(Tooltip::text("Restart Server")) - .on_click({ - let state = self.state.clone(); - let workspace = workspace.clone(); - let lsp_store = lsp_store.downgrade(); - let editor_buffers = state - .read(cx) - .active_editor - .as_ref() - .map(|active_editor| active_editor.editor_buffers.clone()) - .unwrap_or_default(); - let server_selector = server_selector.clone(); - move |_, _, cx| { - if let Some(workspace) = workspace.upgrade() { - let project = workspace.read(cx).project().clone(); - let buffer_store = - project.read(cx).buffer_store().clone(); - let buffers = if is_other_server { - let worktree_store = - project.read(cx).worktree_store(); - state - .read(cx) - .language_servers - .servers_per_buffer_abs_path - .iter() - .filter_map(|(abs_path, servers)| { - if servers.values().any(|server| { - server.as_ref() == Some(&server_name) - }) { - worktree_store - .read(cx) - .find_worktree(abs_path, cx) - } else { - None - } - }) - .filter_map(|(worktree, relative_path)| { - let entry = worktree - .read(cx) - .entry_for_path(&relative_path)?; - project - .read(cx) - .path_for_entry(entry.id, cx) - }) - .filter_map(|project_path| { - buffer_store - .read(cx) - .get_by_path(&project_path) - }) - .collect::>() - } else { - editor_buffers - .iter() - .flat_map(|buffer_id| { - buffer_store.read(cx).get(*buffer_id) - }) - .collect::>() - }; - if !buffers.is_empty() { - lsp_store - .update(cx, |lsp_store, cx| { - lsp_store - .restart_language_servers_for_buffers( - buffers, - HashSet::from_iter([ - server_selector.clone(), - ]), - cx, - ); - }) - .ok(); - } - } - } - }), - ), + }, + |div| div.cursor_default(), ) .into_any_element(), ) @@ -586,35 +618,28 @@ impl PickerDelegate for LspPickerDelegate { div().child(div().track_focus(&editor.focus_handle(cx))) } - fn render_footer(&self, _: &mut Window, cx: &mut Context>) -> Option { - let lsp_store = self.state.read(cx).lsp_store.clone(); - - Some( - div() - .p_1() - .border_t_1() - .border_color(cx.theme().colors().border_variant) - .child( - Button::new("stop-all-servers", "Stop All Servers") - .disabled(self.items.is_empty()) - .on_click({ - move |_, _, cx| { - lsp_store - .update(cx, |lsp_store, cx| { - lsp_store.stop_all_language_servers(cx); - }) - .ok(); - } - }), - ) - .into_any_element(), - ) + fn separators_after_indices(&self) -> Vec { + if self.items.is_empty() { + return Vec::new(); + } + let mut indices = vec![self.items.len().saturating_sub(2)]; + if let Some(other_servers_start_index) = self.other_servers_start_index { + if other_servers_start_index > 0 { + indices.insert(0, other_servers_start_index - 1); + indices.dedup(); + } + } + indices } } -// TODO kb keyboard story impl LspTool { - pub fn new(workspace: &Workspace, window: &mut Window, cx: &mut Context) -> Self { + pub fn new( + workspace: &Workspace, + popover_menu_handle: PopoverMenuHandle>, + window: &mut Window, + cx: &mut Context, + ) -> Self { let settings_subscription = cx.observe_global_in::(window, move |lsp_tool, window, cx| { if ProjectSettings::get_global(cx).global_lsp_settings.button { @@ -644,6 +669,7 @@ impl LspTool { Self { state, + popover_menu_handle, lsp_picker: None, _subscriptions: vec![settings_subscription, lsp_store_subscription], } @@ -908,10 +934,11 @@ impl Render for LspTool { .when_some(indicator, IconButton::indicator) .icon_size(IconSize::Small) .indicator_border_color(Some(cx.theme().colors().status_bar_background)), - move |_, cx| Tooltip::simple("Language Servers", cx), + move |window, cx| Tooltip::for_action("Language Servers", &ToggleMenu, window, cx), Corner::BottomLeft, cx, ) + .with_handle(self.popover_menu_handle.clone()) .render(window, cx), ) } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index ca1670227b903e2e2365edeba8fca69cd3e48df3..2bbe3d0bcb6d119033b4fcc6ed6794faec914ca7 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -30,7 +30,7 @@ use gpui::{ px, retain_all, }; use image_viewer::ImageInfo; -use language_tools::lsp_tool::LspTool; +use language_tools::lsp_tool::{self, LspTool}; use migrate::{MigrationBanner, MigrationEvent, MigrationNotification, MigrationType}; use migrator::{migrate_keymap, migrate_settings}; pub use open_listener::*; @@ -294,20 +294,18 @@ pub fn initialize_workspace( show_software_emulation_warning_if_needed(specs, window, cx); } - let popover_menu_handle = PopoverMenuHandle::default(); - + let inline_completion_menu_handle = PopoverMenuHandle::default(); let edit_prediction_button = cx.new(|cx| { inline_completion_button::InlineCompletionButton::new( app_state.fs.clone(), app_state.user_store.clone(), - popover_menu_handle.clone(), + inline_completion_menu_handle.clone(), cx, ) }); - workspace.register_action({ move |_, _: &inline_completion_button::ToggleMenu, window, cx| { - popover_menu_handle.toggle(window, cx); + inline_completion_menu_handle.toggle(window, cx); } }); @@ -326,7 +324,15 @@ pub fn initialize_workspace( cx.new(|cx| toolchain_selector::ActiveToolchain::new(workspace, window, cx)); let vim_mode_indicator = cx.new(|cx| vim::ModeIndicator::new(window, cx)); let image_info = cx.new(|_cx| ImageInfo::new(workspace)); - let lsp_tool = cx.new(|cx| LspTool::new(workspace, window, cx)); + + let lsp_tool_menu_handle = PopoverMenuHandle::default(); + let lsp_tool = + cx.new(|cx| LspTool::new(workspace, lsp_tool_menu_handle.clone(), window, cx)); + workspace.register_action({ + move |_, _: &lsp_tool::ToggleMenu, window, cx| { + lsp_tool_menu_handle.toggle(window, cx); + } + }); let cursor_position = cx.new(|_| go_to_line::cursor_position::CursorPosition::new(workspace)); From e3ce0618a3a9a55b6a1b55118e48c9d6a087f0e2 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Fri, 27 Jun 2025 09:40:50 -0400 Subject: [PATCH 019/107] collab: Lookup avatars by GitHub ID instead of username (#33523) Closes: https://github.com/zed-industries/zed/issues/19207 This will correctly show Avatars for recently renamed/deleted users and for enterprise users where the username avatar url triggers a redirect to an auth prompt. Also saves a request (302 redirect) per avatar. Tested locally and avatars loaded as expected. Release Notes: - N/A --- crates/collab/src/db/queries/channels.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index a4899f408d9d60d8222e558cd12964597cf4228d..9a370bb73b91b6f6434fc53d6a024e4cd00dae1e 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -734,8 +734,8 @@ impl Database { users.push(proto::User { id: user.id.to_proto(), avatar_url: format!( - "https://github.com/{}.png?size=128", - user.github_login + "https://avatars.githubusercontent.com/u/{}?s=128&v=4", + user.github_user_id ), github_login: user.github_login, name: user.name, From 3ab4ad6de8acaaa0f4698387e24887585d3becd9 Mon Sep 17 00:00:00 2001 From: Umesh Yadav <23421535+imumesh18@users.noreply.github.com> Date: Fri, 27 Jun 2025 20:06:16 +0530 Subject: [PATCH 020/107] language_models: Use `JsonSchemaSubset` for Gemini models in OpenRouter (#33477) Closes #33466 Release Notes: - N/A --- crates/language_models/src/provider/open_router.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/language_models/src/provider/open_router.rs b/crates/language_models/src/provider/open_router.rs index b447ee1bd72b4d13bbcf04da15ca5c26037e6405..3a8a450cf6e6ed26fcb79ae86c6f695851374d5b 100644 --- a/crates/language_models/src/provider/open_router.rs +++ b/crates/language_models/src/provider/open_router.rs @@ -11,8 +11,8 @@ use language_model::{ AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, - LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent, - RateLimiter, Role, StopReason, TokenUsage, + LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolSchemaFormat, + LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason, TokenUsage, }; use open_router::{ Model, ModelMode as OpenRouterModelMode, ResponseStreamEvent, list_models, stream_completion, @@ -374,6 +374,15 @@ impl LanguageModel for OpenRouterLanguageModel { self.model.supports_tool_calls() } + fn tool_input_format(&self) -> LanguageModelToolSchemaFormat { + let model_id = self.model.id().trim().to_lowercase(); + if model_id.contains("gemini") { + LanguageModelToolSchemaFormat::JsonSchemaSubset + } else { + LanguageModelToolSchemaFormat::JsonSchema + } + } + fn telemetry_id(&self) -> String { format!("openrouter/{}", self.model.id()) } From 9e2023bffc38bf1ec3561850b74401a863a880e4 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Fri, 27 Jun 2025 20:14:01 +0530 Subject: [PATCH 021/107] editor: Fix editor tests from changing on format on save (#33532) Use placeholder to prevent format-on-save from removing whitespace in editor tests, which leads to unnecessary git diff and failing tests. cc: https://github.com/zed-industries/zed/pull/32340 Release Notes: - N/A --- crates/editor/src/editor_tests.rs | 180 +++++++++++++++++------------- 1 file changed, 102 insertions(+), 78 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 5b9a2ef773f6deb5ef2f26e573125021445cbcfd..1716acc9850a0c67ef21259c33199e78fd85ab0e 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -4335,48 +4335,60 @@ async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) { cx.update_editor(|e, window, cx| { e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx); }); - cx.assert_editor_state(indoc! {" - « - abc // No indentation - abc // 1 tab - abc // 2 tabs - abc // Tab followed by space - abc // Space followed by tab (3 spaces should be the result) - abc // Mixed indentation (tab conversion depends on the column) - abc // Already space indented - - abc\tdef // Only the leading tab is manipulatedˇ» - "}); + cx.assert_editor_state( + indoc! {" + « + abc // No indentation + abc // 1 tab + abc // 2 tabs + abc // Tab followed by space + abc // Space followed by tab (3 spaces should be the result) + abc // Mixed indentation (tab conversion depends on the column) + abc // Already space indented + · + abc\tdef // Only the leading tab is manipulatedˇ» + "} + .replace("·", "") + .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace + ); // Test on just a few lines, the others should remain unchanged // Only lines (3, 5, 10, 11) should change - cx.set_state(indoc! {" - - abc // No indentation - \tabcˇ // 1 tab - \t\tabc // 2 tabs - \t abcˇ // Tab followed by space - \tabc // Space followed by tab (3 spaces should be the result) - \t \t \t \tabc // Mixed indentation (tab conversion depends on the column) - abc // Already space indented - «\t - \tabc\tdef // Only the leading tab is manipulatedˇ» - "}); + cx.set_state( + indoc! {" + · + abc // No indentation + \tabcˇ // 1 tab + \t\tabc // 2 tabs + \t abcˇ // Tab followed by space + \tabc // Space followed by tab (3 spaces should be the result) + \t \t \t \tabc // Mixed indentation (tab conversion depends on the column) + abc // Already space indented + «\t + \tabc\tdef // Only the leading tab is manipulatedˇ» + "} + .replace("·", "") + .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace + ); cx.update_editor(|e, window, cx| { e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx); }); - cx.assert_editor_state(indoc! {" - - abc // No indentation - « abc // 1 tabˇ» - \t\tabc // 2 tabs - « abc // Tab followed by spaceˇ» - \tabc // Space followed by tab (3 spaces should be the result) - \t \t \t \tabc // Mixed indentation (tab conversion depends on the column) - abc // Already space indented - « - abc\tdef // Only the leading tab is manipulatedˇ» - "}); + cx.assert_editor_state( + indoc! {" + · + abc // No indentation + « abc // 1 tabˇ» + \t\tabc // 2 tabs + « abc // Tab followed by spaceˇ» + \tabc // Space followed by tab (3 spaces should be the result) + \t \t \t \tabc // Mixed indentation (tab conversion depends on the column) + abc // Already space indented + « · + abc\tdef // Only the leading tab is manipulatedˇ» + "} + .replace("·", "") + .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace + ); // SINGLE SELECTION // Ln.1 "«" tests empty lines @@ -4396,18 +4408,22 @@ async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) { cx.update_editor(|e, window, cx| { e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx); }); - cx.assert_editor_state(indoc! {" - « - abc // No indentation - abc // 1 tab - abc // 2 tabs - abc // Tab followed by space - abc // Space followed by tab (3 spaces should be the result) - abc // Mixed indentation (tab conversion depends on the column) - abc // Already space indented - - abc\tdef // Only the leading tab is manipulatedˇ» - "}); + cx.assert_editor_state( + indoc! {" + « + abc // No indentation + abc // 1 tab + abc // 2 tabs + abc // Tab followed by space + abc // Space followed by tab (3 spaces should be the result) + abc // Mixed indentation (tab conversion depends on the column) + abc // Already space indented + · + abc\tdef // Only the leading tab is manipulatedˇ» + "} + .replace("·", "") + .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace + ); } #[gpui::test] @@ -4455,39 +4471,47 @@ async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) { // Test on just a few lines, the other should remain unchanged // Only lines (4, 8, 11, 12) should change - cx.set_state(indoc! {" - - abc // No indentation - abc // 1 space (< 3 so dont convert) - abc // 2 spaces (< 3 so dont convert) - « abc // 3 spaces (convert)ˇ» - abc // 5 spaces (1 tab + 2 spaces) - \t\t\tabc // Already tab indented - \t abc // Tab followed by space - \tabc ˇ // Space followed by tab (should be consumed due to tab) - \t\t \tabc // Mixed indentation - \t \t \t \tabc // Mixed indentation - \t \tˇ - « abc \t // Only the leading spaces should be convertedˇ» - "}); + cx.set_state( + indoc! {" + · + abc // No indentation + abc // 1 space (< 3 so dont convert) + abc // 2 spaces (< 3 so dont convert) + « abc // 3 spaces (convert)ˇ» + abc // 5 spaces (1 tab + 2 spaces) + \t\t\tabc // Already tab indented + \t abc // Tab followed by space + \tabc ˇ // Space followed by tab (should be consumed due to tab) + \t\t \tabc // Mixed indentation + \t \t \t \tabc // Mixed indentation + \t \tˇ + « abc \t // Only the leading spaces should be convertedˇ» + "} + .replace("·", "") + .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace + ); cx.update_editor(|e, window, cx| { e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx); }); - cx.assert_editor_state(indoc! {" - - abc // No indentation - abc // 1 space (< 3 so dont convert) - abc // 2 spaces (< 3 so dont convert) - «\tabc // 3 spaces (convert)ˇ» - abc // 5 spaces (1 tab + 2 spaces) - \t\t\tabc // Already tab indented - \t abc // Tab followed by space - «\tabc // Space followed by tab (should be consumed due to tab)ˇ» - \t\t \tabc // Mixed indentation - \t \t \t \tabc // Mixed indentation - «\t\t\t - \tabc \t // Only the leading spaces should be convertedˇ» - "}); + cx.assert_editor_state( + indoc! {" + · + abc // No indentation + abc // 1 space (< 3 so dont convert) + abc // 2 spaces (< 3 so dont convert) + «\tabc // 3 spaces (convert)ˇ» + abc // 5 spaces (1 tab + 2 spaces) + \t\t\tabc // Already tab indented + \t abc // Tab followed by space + «\tabc // Space followed by tab (should be consumed due to tab)ˇ» + \t\t \tabc // Mixed indentation + \t \t \t \tabc // Mixed indentation + «\t\t\t + \tabc \t // Only the leading spaces should be convertedˇ» + "} + .replace("·", "") + .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace + ); // SINGLE SELECTION // Ln.1 "«" tests empty lines From d74f3f4ea6f969f9db4330aef14c69b06fbb45d1 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 27 Jun 2025 09:16:15 -0600 Subject: [PATCH 022/107] Fix crash in git checkout (#33499) Closes #33438 Release Notes: - git: Use git cli to perform checkouts (to avoid a crash seen in libgit2) --- crates/git/src/repository.rs | 53 ++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index 7b07cb62a1ee66f8aa6d11b9be969741458b0785..2ecd4bb894348cf3fc532a8473e43f0712e61700 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -1032,32 +1032,39 @@ impl GitRepository for RealGitRepository { fn change_branch(&self, name: String) -> BoxFuture<'_, Result<()>> { let repo = self.repository.clone(); + let working_directory = self.working_directory(); + let git_binary_path = self.git_binary_path.clone(); + let executor = self.executor.clone(); + let branch = self.executor.spawn(async move { + let repo = repo.lock(); + let branch = if let Ok(branch) = repo.find_branch(&name, BranchType::Local) { + branch + } else if let Ok(revision) = repo.find_branch(&name, BranchType::Remote) { + let (_, branch_name) = name.split_once("/").context("Unexpected branch format")?; + let revision = revision.get(); + let branch_commit = revision.peel_to_commit()?; + let mut branch = repo.branch(&branch_name, &branch_commit, false)?; + branch.set_upstream(Some(&name))?; + branch + } else { + anyhow::bail!("Branch not found"); + }; + + Ok(branch + .name()? + .context("cannot checkout anonymous branch")? + .to_string()) + }); + self.executor .spawn(async move { - let repo = repo.lock(); - let branch = if let Ok(branch) = repo.find_branch(&name, BranchType::Local) { - branch - } else if let Ok(revision) = repo.find_branch(&name, BranchType::Remote) { - let (_, branch_name) = - name.split_once("/").context("Unexpected branch format")?; - let revision = revision.get(); - let branch_commit = revision.peel_to_commit()?; - let mut branch = repo.branch(&branch_name, &branch_commit, false)?; - branch.set_upstream(Some(&name))?; - branch - } else { - anyhow::bail!("Branch not found"); - }; + let branch = branch.await?; - let revision = branch.get(); - let as_tree = revision.peel_to_tree()?; - repo.checkout_tree(as_tree.as_object(), None)?; - repo.set_head( - revision - .name() - .context("Branch name could not be retrieved")?, - )?; - Ok(()) + GitBinary::new(git_binary_path, working_directory?, executor) + .run(&["checkout", &branch]) + .await?; + + anyhow::Ok(()) }) .boxed() } From 157199b65bb6aa7862c6c7ccb6d5678b662d4d51 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 27 Jun 2025 09:39:38 -0600 Subject: [PATCH 023/107] Replace newlines in search bar (#33504) Release Notes: - search: Pasted newlines are now rendered as "\n" (with an underline), instead of line-wrapping. This should make it much clearer what you're searching for. Screenshot 2025-06-27 at 00 34 52 --- crates/editor/src/editor.rs | 84 +++++++++++++++++++++++++++---- crates/editor/src/editor_tests.rs | 18 +++++++ 2 files changed, 92 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index aad9e96d3a18dfa64a8db4247c6f146e579b8b8c..376aa60ba42f275acbdb8fe5e1f59fdf1d7be711 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1143,6 +1143,7 @@ pub struct Editor { drag_and_drop_selection_enabled: bool, next_color_inlay_id: usize, colors: Option, + folding_newlines: Task<()>, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] @@ -2159,6 +2160,7 @@ impl Editor { mode, selection_drag_state: SelectionDragState::None, drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection, + folding_newlines: Task::ready(()), }; if let Some(breakpoints) = editor.breakpoint_store.as_ref() { editor @@ -6717,6 +6719,77 @@ impl Editor { }) } + fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context) { + struct NewlineFold; + let type_id = std::any::TypeId::of::(); + if !self.mode.is_single_line() { + return; + } + let snapshot = self.snapshot(window, cx); + if snapshot.buffer_snapshot.max_point().row == 0 { + return; + } + let task = cx.background_spawn(async move { + let new_newlines = snapshot + .buffer_chars_at(0) + .filter_map(|(c, i)| { + if c == '\n' { + Some( + snapshot.buffer_snapshot.anchor_after(i) + ..snapshot.buffer_snapshot.anchor_before(i + 1), + ) + } else { + None + } + }) + .collect::>(); + let existing_newlines = snapshot + .folds_in_range(0..snapshot.buffer_snapshot.len()) + .filter_map(|fold| { + if fold.placeholder.type_tag == Some(type_id) { + Some(fold.range.start..fold.range.end) + } else { + None + } + }) + .collect::>(); + + (new_newlines, existing_newlines) + }); + self.folding_newlines = cx.spawn(async move |this, cx| { + let (new_newlines, existing_newlines) = task.await; + if new_newlines == existing_newlines { + return; + } + let placeholder = FoldPlaceholder { + render: Arc::new(move |_, _, cx| { + div() + .bg(cx.theme().status().hint_background) + .border_b_1() + .size_full() + .font(ThemeSettings::get_global(cx).buffer_font.clone()) + .border_color(cx.theme().status().hint) + .child("\\n") + .into_any() + }), + constrain_width: false, + merge_adjacent: false, + type_tag: Some(type_id), + }; + let creases = new_newlines + .into_iter() + .map(|range| Crease::simple(range, placeholder.clone())) + .collect(); + this.update(cx, |this, cx| { + this.display_map.update(cx, |display_map, cx| { + display_map.remove_folds_with_type(existing_newlines, type_id, cx); + display_map.fold(creases, cx); + }); + }) + .ok(); + }); + } + fn refresh_selected_text_highlights( &mut self, on_buffer_edit: bool, @@ -17100,16 +17173,6 @@ impl Editor { return; } - let mut buffers_affected = HashSet::default(); - let multi_buffer = self.buffer().read(cx); - for crease in &creases { - if let Some((_, buffer, _)) = - multi_buffer.excerpt_containing(crease.range().start.clone(), cx) - { - buffers_affected.insert(buffer.read(cx).remote_id()); - }; - } - self.display_map.update(cx, |map, cx| map.fold(creases, cx)); if auto_scroll { @@ -19435,6 +19498,7 @@ impl Editor { self.refresh_active_diagnostics(cx); self.refresh_code_actions(window, cx); self.refresh_selected_text_highlights(true, window, cx); + self.refresh_single_line_folds(window, cx); refresh_matching_bracket_highlights(self, window, cx); if self.has_active_inline_completion() { self.update_visible_inline_completion(window, cx); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 1716acc9850a0c67ef21259c33199e78fd85ab0e..1df4c54b1cbcc8cf9ca1c00aa2d762c6b1dda05f 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -22770,6 +22770,24 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { }); } +#[gpui::test] +async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + let (editor, cx) = cx.add_window_view(Editor::single_line); + editor.update_in(cx, |editor, window, cx| { + editor.set_text("oops\n\nwow\n", window, cx) + }); + cx.run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯"); + }); + editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx)); + cx.run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!(editor.display_text(cx), "oop⋯wow⋯"); + }); +} + #[track_caller] fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec { editor From 7432e947bc60204bc0f8fcfb13ab06f190369451 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Fri, 27 Jun 2025 10:46:04 -0500 Subject: [PATCH 024/107] Add `element_selection_background` highlight to theme (#32388) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #32354 The issue is that we render selections over the text in the agent panel, but under the text in editor, so themes that have no alpha for the selection background color (defaults to 0xff) will just occlude the selected region. Making the selection render under the text in markdown would be a significant (and complicated) refactor, as selections can cross element boundaries (i.e. spanning code block and a header after the code block). The solution is to add a new highlight to themes `element_selection_background` that defaults to the local players selection background with an alpha of 0.25 (roughly equal to 0x3D which is the alpha we use for selection backgrounds in default themes) if the alpha of the local players selection is 1.0. The idea here is to give theme authors more control over how the selections look outside of editor, as in the agent panel specifically, the background color is different, so while an alpha of 0.25 looks acceptable, a different color would likely be better. CC: @iamnbutler. Would appreciate your thoughts on this. > Note: Before and after using Everforest theme | Before | After | |-------| -----| | Screenshot 2025-06-09 at 5 23 10 PM | Screenshot 2025-06-09 at 5 25 03 PM | Clearly, the selection in the after doesn't look _that_ great, but it is better than the before, and this PR makes the color of the selection configurable by the theme so that this theme author could make it a lighter color for better contrast. Release Notes: - agent panel: Fixed an issue with some themes where selections inside the agent panel would occlude the selected text completely Co-authored-by: Antonio --- crates/agent_ui/src/active_thread.rs | 4 ++-- .../configure_context_server_modal.rs | 2 +- crates/assistant_tools/src/edit_file_tool.rs | 2 +- crates/assistant_tools/src/terminal_tool.rs | 2 +- crates/editor/src/hover_popover.rs | 4 ++-- crates/markdown/examples/markdown.rs | 6 +----- crates/markdown/examples/markdown_as_child.rs | 6 +----- crates/markdown/src/markdown.rs | 1 - crates/recent_projects/src/ssh_connections.rs | 2 +- crates/theme/src/default_colors.rs | 2 ++ crates/theme/src/fallback_themes.rs | 20 +++++++++++++++++-- crates/theme/src/schema.rs | 8 ++++++++ crates/theme/src/styles/colors.rs | 2 ++ crates/theme/src/theme.rs | 15 ++++++++------ crates/ui_prompt/src/ui_prompt.rs | 5 ++++- 15 files changed, 53 insertions(+), 28 deletions(-) diff --git a/crates/agent_ui/src/active_thread.rs b/crates/agent_ui/src/active_thread.rs index 4da959d36e9f77ba06e71d722400a60d9f5be25b..5f9dfc7ab2ee844d7a8f4b6077861ff24e6d03cf 100644 --- a/crates/agent_ui/src/active_thread.rs +++ b/crates/agent_ui/src/active_thread.rs @@ -204,7 +204,7 @@ pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle MarkdownStyle { base_text_style: text_style.clone(), syntax: cx.theme().syntax().clone(), - selection_background_color: cx.theme().players().local().selection, + selection_background_color: cx.theme().colors().element_selection_background, code_block_overflow_x_scroll: true, table_overflow_x_scroll: true, heading_level_styles: Some(HeadingLevelStyles { @@ -301,7 +301,7 @@ fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle { MarkdownStyle { base_text_style: text_style, syntax: cx.theme().syntax().clone(), - selection_background_color: cx.theme().players().local().selection, + selection_background_color: cx.theme().colors().element_selection_background, code_block_overflow_x_scroll: false, code_block: StyleRefinement { margin: EdgesRefinement::default(), diff --git a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs index 30fad51cfcbc100bdf469278c0210a220c7e2833..af08eaf935ba9887a36ef6093ca3dcd53f1608f0 100644 --- a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs +++ b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs @@ -748,7 +748,7 @@ pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle MarkdownStyle { base_text_style: text_style.clone(), - selection_background_color: cx.theme().players().local().selection, + selection_background_color: colors.element_selection_background, link: TextStyleRefinement { background_color: Some(colors.editor_foreground.opacity(0.025)), underline: Some(UnderlineStyle { diff --git a/crates/assistant_tools/src/edit_file_tool.rs b/crates/assistant_tools/src/edit_file_tool.rs index fde697b00eb2177bf3ac0382fd7d0c78daa0907e..fcf82856922c2e1c78345cc129aaea871a63ecfa 100644 --- a/crates/assistant_tools/src/edit_file_tool.rs +++ b/crates/assistant_tools/src/edit_file_tool.rs @@ -1065,7 +1065,7 @@ fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle { MarkdownStyle { base_text_style: text_style.clone(), - selection_background_color: cx.theme().players().local().selection, + selection_background_color: cx.theme().colors().element_selection_background, ..Default::default() } } diff --git a/crates/assistant_tools/src/terminal_tool.rs b/crates/assistant_tools/src/terminal_tool.rs index 5ec0ce7b8f0a4f5f5a34efd01069cd0487104b58..2c582a531069eb9a81340af7eb07731e8df8a96e 100644 --- a/crates/assistant_tools/src/terminal_tool.rs +++ b/crates/assistant_tools/src/terminal_tool.rs @@ -691,7 +691,7 @@ fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle { MarkdownStyle { base_text_style: text_style.clone(), - selection_background_color: cx.theme().players().local().selection, + selection_background_color: cx.theme().colors().element_selection_background, ..Default::default() } } diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index b174a3ba62ed3924cf4a0da151ad20330a77eafa..9e6fc356ea6ee840824b174fd216d0ea10828d59 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -648,7 +648,7 @@ pub fn hover_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { ..Default::default() }, syntax: cx.theme().syntax().clone(), - selection_background_color: { cx.theme().players().local().selection }, + selection_background_color: cx.theme().colors().element_selection_background, heading: StyleRefinement::default() .font_weight(FontWeight::BOLD) .text_base() @@ -697,7 +697,7 @@ pub fn diagnostics_markdown_style(window: &Window, cx: &App) -> MarkdownStyle { ..Default::default() }, syntax: cx.theme().syntax().clone(), - selection_background_color: { cx.theme().players().local().selection }, + selection_background_color: cx.theme().colors().element_selection_background, height_is_multiple_of_line_height: true, heading: StyleRefinement::default() .font_weight(FontWeight::BOLD) diff --git a/crates/markdown/examples/markdown.rs b/crates/markdown/examples/markdown.rs index 16387a8000c2bbe1ae38a9979f96e5e1b1dda85d..bf685bd9acfe9f678454dffc538ca66e3ca0910d 100644 --- a/crates/markdown/examples/markdown.rs +++ b/crates/markdown/examples/markdown.rs @@ -107,11 +107,7 @@ impl Render for MarkdownExample { ..Default::default() }, syntax: cx.theme().syntax().clone(), - selection_background_color: { - let mut selection = cx.theme().players().local().selection; - selection.fade_out(0.7); - selection - }, + selection_background_color: cx.theme().colors().element_selection_background, ..Default::default() }; diff --git a/crates/markdown/examples/markdown_as_child.rs b/crates/markdown/examples/markdown_as_child.rs index 62a35629b1dedfb33b08c0ce52d5fab94ee18ccf..862b657c8c50c7adc88642f1af21a4c075ff77f2 100644 --- a/crates/markdown/examples/markdown_as_child.rs +++ b/crates/markdown/examples/markdown_as_child.rs @@ -91,11 +91,7 @@ impl Render for HelloWorld { ..Default::default() }, syntax: cx.theme().syntax().clone(), - selection_background_color: { - let mut selection = cx.theme().players().local().selection; - selection.fade_out(0.7); - selection - }, + selection_background_color: cx.theme().colors().element_selection_background, heading: Default::default(), ..Default::default() }; diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index ac959d13b5c0236b0d4b3caf99df1970d2f73031..9c057baec97840d81317d828f635a9aad0f6c9fc 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -504,7 +504,6 @@ impl MarkdownElement { let selection = self.markdown.read(cx).selection; let selection_start = rendered_text.position_for_source_index(selection.start); let selection_end = rendered_text.position_for_source_index(selection.end); - if let Some(((start_position, start_line_height), (end_position, end_line_height))) = selection_start.zip(selection_end) { diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 070d8dc4e35295f43d3dbad37e4ce6ea3bd9d4be..5a38e1aadb00e75f9139eb4eccdacacb5e967593 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -248,7 +248,7 @@ impl Render for SshPrompt { text_style.refine(&refinement); let markdown_style = MarkdownStyle { base_text_style: text_style, - selection_background_color: cx.theme().players().local().selection, + selection_background_color: cx.theme().colors().element_selection_background, ..Default::default() }; diff --git a/crates/theme/src/default_colors.rs b/crates/theme/src/default_colors.rs index 33d7b86e3db7a2e311acf9c3cf1fc9548185b42d..3424e0fe04cdbc11544fa81018edba4ff2b357c1 100644 --- a/crates/theme/src/default_colors.rs +++ b/crates/theme/src/default_colors.rs @@ -52,6 +52,7 @@ impl ThemeColors { element_active: neutral().light_alpha().step_5(), element_selected: neutral().light_alpha().step_5(), element_disabled: neutral().light_alpha().step_3(), + element_selection_background: blue().light().step_3().alpha(0.25), drop_target_background: blue().light_alpha().step_2(), ghost_element_background: system.transparent, ghost_element_hover: neutral().light_alpha().step_3(), @@ -174,6 +175,7 @@ impl ThemeColors { element_active: neutral().dark_alpha().step_5(), element_selected: neutral().dark_alpha().step_5(), element_disabled: neutral().dark_alpha().step_3(), + element_selection_background: blue().dark().step_3().alpha(0.25), drop_target_background: blue().dark_alpha().step_2(), ghost_element_background: system.transparent, ghost_element_hover: neutral().dark_alpha().step_4(), diff --git a/crates/theme/src/fallback_themes.rs b/crates/theme/src/fallback_themes.rs index afc977d7fdd1ad4cba18d63c899746837d79325f..5e9967d4603a5bac8c9f1a7e461c7319f52f82d7 100644 --- a/crates/theme/src/fallback_themes.rs +++ b/crates/theme/src/fallback_themes.rs @@ -4,7 +4,8 @@ use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla, WindowBackgroundAppearan use crate::{ AccentColors, Appearance, PlayerColors, StatusColors, StatusColorsRefinement, SyntaxTheme, - SystemColors, Theme, ThemeColors, ThemeFamily, ThemeStyles, default_color_scales, + SystemColors, Theme, ThemeColors, ThemeColorsRefinement, ThemeFamily, ThemeStyles, + default_color_scales, }; /// The default theme family for Zed. @@ -41,6 +42,19 @@ pub(crate) fn apply_status_color_defaults(status: &mut StatusColorsRefinement) { } } +pub(crate) fn apply_theme_color_defaults( + theme_colors: &mut ThemeColorsRefinement, + player_colors: &PlayerColors, +) { + if theme_colors.element_selection_background.is_none() { + let mut selection = player_colors.local().selection; + if selection.a == 1.0 { + selection.a = 0.25; + } + theme_colors.element_selection_background = Some(selection); + } +} + pub(crate) fn zed_default_dark() -> Theme { let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.); let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.); @@ -74,6 +88,7 @@ pub(crate) fn zed_default_dark() -> Theme { a: 1.0, }; + let player = PlayerColors::dark(); Theme { id: "one_dark".to_string(), name: "One Dark".into(), @@ -97,6 +112,7 @@ pub(crate) fn zed_default_dark() -> Theme { element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0), element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0), element_disabled: SystemColors::default().transparent, + element_selection_background: player.local().selection.alpha(0.25), drop_target_background: hsla(220.0 / 360., 8.3 / 100., 21.4 / 100., 1.0), ghost_element_background: SystemColors::default().transparent, ghost_element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0), @@ -258,7 +274,7 @@ pub(crate) fn zed_default_dark() -> Theme { warning_background: yellow, warning_border: yellow, }, - player: PlayerColors::dark(), + player, syntax: Arc::new(SyntaxTheme { highlights: vec![ ("attribute".into(), purple.into()), diff --git a/crates/theme/src/schema.rs b/crates/theme/src/schema.rs index a071ca26c8b0105828aa0f42ab315c6d67902823..01fdafd94df58f182074bc9c5ffeaa49fe36ab62 100644 --- a/crates/theme/src/schema.rs +++ b/crates/theme/src/schema.rs @@ -219,6 +219,10 @@ pub struct ThemeColorsContent { #[serde(rename = "element.disabled")] pub element_disabled: Option, + /// Background Color. Used for the background of selections in a UI element. + #[serde(rename = "element.selection_background")] + pub element_selection_background: Option, + /// Background Color. Used for the area that shows where a dragged element will be dropped. #[serde(rename = "drop_target.background")] pub drop_target_background: Option, @@ -726,6 +730,10 @@ impl ThemeColorsContent { .element_disabled .as_ref() .and_then(|color| try_parse_color(color).ok()), + element_selection_background: self + .element_selection_background + .as_ref() + .and_then(|color| try_parse_color(color).ok()), drop_target_background: self .drop_target_background .as_ref() diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index fb821385f54fb72e4f445a0c88b9edc4814574f8..76d18c6d6553edbc57ba2666a433b857141ba05b 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -51,6 +51,8 @@ pub struct ThemeColors { /// /// This could include a selected checkbox, a toggleable button that is toggled on, etc. pub element_selected: Hsla, + /// Background Color. Used for the background of selections in a UI element. + pub element_selection_background: Hsla, /// Background Color. Used for the disabled state of an element that should have a different background than the surface it's on. /// /// Disabled states are shown when a user cannot interact with an element, like a disabled button or input. diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 3b5306c216b8f6a100b75ce9d3e915c39989d620..bdb52693c0bad35107b79fc21bc127d496cec396 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -35,6 +35,7 @@ use serde::Deserialize; use uuid::Uuid; pub use crate::default_colors::*; +use crate::fallback_themes::apply_theme_color_defaults; pub use crate::font_family_cache::*; pub use crate::icon_theme::*; pub use crate::icon_theme_schema::*; @@ -165,12 +166,6 @@ impl ThemeFamily { AppearanceContent::Dark => Appearance::Dark, }; - let mut refined_theme_colors = match theme.appearance { - AppearanceContent::Light => ThemeColors::light(), - AppearanceContent::Dark => ThemeColors::dark(), - }; - refined_theme_colors.refine(&theme.style.theme_colors_refinement()); - let mut refined_status_colors = match theme.appearance { AppearanceContent::Light => StatusColors::light(), AppearanceContent::Dark => StatusColors::dark(), @@ -185,6 +180,14 @@ impl ThemeFamily { }; refined_player_colors.merge(&theme.style.players); + let mut refined_theme_colors = match theme.appearance { + AppearanceContent::Light => ThemeColors::light(), + AppearanceContent::Dark => ThemeColors::dark(), + }; + let mut theme_colors_refinement = theme.style.theme_colors_refinement(); + apply_theme_color_defaults(&mut theme_colors_refinement, &refined_player_colors); + refined_theme_colors.refine(&theme_colors_refinement); + let mut refined_accent_colors = match theme.appearance { AppearanceContent::Light => AccentColors::light(), AppearanceContent::Dark => AccentColors::dark(), diff --git a/crates/ui_prompt/src/ui_prompt.rs b/crates/ui_prompt/src/ui_prompt.rs index dc6aee177d72dc5898f6dcc43895d02aa02f7714..2b6a030f26e752401a56a61a3f6a0a881bb89557 100644 --- a/crates/ui_prompt/src/ui_prompt.rs +++ b/crates/ui_prompt/src/ui_prompt.rs @@ -153,7 +153,10 @@ impl Render for ZedPromptRenderer { }); MarkdownStyle { base_text_style, - selection_background_color: { cx.theme().players().local().selection }, + selection_background_color: cx + .theme() + .colors() + .element_selection_background, ..Default::default() } })) From f9987a11419cc7ea2c7a06a6a8017b9d1232ed11 Mon Sep 17 00:00:00 2001 From: 5brian Date: Fri, 27 Jun 2025 12:18:26 -0400 Subject: [PATCH 025/107] vim: Grep in visual line (#33414) From https://github.com/zed-industries/zed/pull/10831#issuecomment-2078523272 > I agree with not prefilling the search bar with a multiline query. Not sure if it's a bug that a one-line visual line selection does not get pre filled, this PR corrects the query to use the visual line selection instead of the 'normal' selection Release Notes: - N/A --- crates/editor/src/items.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 4993ff689507ebc4478d4a92164360dd6e734a9e..ec3590dba217677bbaf2c8aa36bfd3147b9d6cbf 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1521,7 +1521,7 @@ impl SearchableItem for Editor { fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context) -> String { let setting = EditorSettings::get_global(cx).seed_search_query_from_cursor; let snapshot = &self.snapshot(window, cx).buffer_snapshot; - let selection = self.selections.newest::(cx); + let selection = self.selections.newest_adjusted(cx); match setting { SeedQuerySetting::Never => String::new(), From 01dfb6fa825ad9558c9c337bbe6ddb61e9128eda Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 27 Jun 2025 19:31:40 +0300 Subject: [PATCH 026/107] Respect server capabilities on queries (#33538) Closes https://github.com/zed-industries/zed/issues/33522 Turns out a bunch of Zed requests were not checking their capabilities correctly, due to odd copy-paste and due to default that assumed that the capabilities are met. Adjust the code, which includes the document colors, add the test on the colors case. Release Notes: - Fixed excessive document colors requests for unrelated files --- crates/editor/src/editor_tests.rs | 78 ++++++++--- crates/project/src/lsp_command.rs | 68 ++++++++-- crates/project/src/lsp_store.rs | 121 +++++++++++------- .../project/src/lsp_store/lsp_ext_command.rs | 22 +++- crates/project/src/project.rs | 31 ++++- .../remote_server/src/remote_editing_tests.rs | 13 +- 6 files changed, 259 insertions(+), 74 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 1df4c54b1cbcc8cf9ca1c00aa2d762c6b1dda05f..1ef2294d41d2815b2bfadb21257a0cc3132ebf3a 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -22631,6 +22631,18 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { color_provider: Some(lsp::ColorProviderCapability::Simple(true)), ..lsp::ServerCapabilities::default() }, + name: "rust-analyzer", + ..FakeLspAdapter::default() + }, + ); + let mut fake_servers_without_capabilities = language_registry.register_fake_lsp( + "Rust", + FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + color_provider: Some(lsp::ColorProviderCapability::Simple(false)), + ..lsp::ServerCapabilities::default() + }, + name: "not-rust-analyzer", ..FakeLspAdapter::default() }, ); @@ -22650,6 +22662,8 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { .downcast::() .unwrap(); let fake_language_server = fake_servers.next().await.unwrap(); + let fake_language_server_without_capabilities = + fake_servers_without_capabilities.next().await.unwrap(); let requests_made = Arc::new(AtomicUsize::new(0)); let closure_requests_made = Arc::clone(&requests_made); let mut color_request_handle = fake_language_server @@ -22661,34 +22675,59 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { lsp::Url::from_file_path(path!("/a/first.rs")).unwrap() ); requests_made.fetch_add(1, atomic::Ordering::Release); - Ok(vec![lsp::ColorInformation { - range: lsp::Range { - start: lsp::Position { - line: 0, - character: 0, + Ok(vec![ + lsp::ColorInformation { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 0, + }, + end: lsp::Position { + line: 0, + character: 1, + }, }, - end: lsp::Position { - line: 0, - character: 1, + color: lsp::Color { + red: 0.33, + green: 0.33, + blue: 0.33, + alpha: 0.33, }, }, - color: lsp::Color { - red: 0.33, - green: 0.33, - blue: 0.33, - alpha: 0.33, + lsp::ColorInformation { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 0, + }, + end: lsp::Position { + line: 0, + character: 1, + }, + }, + color: lsp::Color { + red: 0.33, + green: 0.33, + blue: 0.33, + alpha: 0.33, + }, }, - }]) + ]) } }); + + let _handle = fake_language_server_without_capabilities + .set_request_handler::(move |_, _| async move { + panic!("Should not be called"); + }); color_request_handle.next().await.unwrap(); cx.run_until_parked(); color_request_handle.next().await.unwrap(); cx.run_until_parked(); assert_eq!( - 2, + 3, requests_made.load(atomic::Ordering::Acquire), - "Should query for colors once per editor open and once after the language server startup" + "Should query for colors once per editor open (1) and once after the language server startup (2)" ); cx.executor().advance_clock(Duration::from_millis(500)); @@ -22718,7 +22757,7 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { color_request_handle.next().await.unwrap(); cx.run_until_parked(); assert_eq!( - 4, + 5, requests_made.load(atomic::Ordering::Acquire), "Should query for colors once per save and once per formatting after save" ); @@ -22733,7 +22772,7 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { .unwrap(); close.await.unwrap(); assert_eq!( - 4, + 5, requests_made.load(atomic::Ordering::Acquire), "After saving and closing the editor, no extra requests should be made" ); @@ -22745,10 +22784,11 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { }) }) .unwrap(); + cx.executor().advance_clock(Duration::from_millis(100)); color_request_handle.next().await.unwrap(); cx.run_until_parked(); assert_eq!( - 5, + 6, requests_made.load(atomic::Ordering::Acquire), "After navigating back to an editor and reopening it, another color request should be made" ); diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 18164b4bcb68613987b4c938eaa317f8d6564c7b..cdeb9f71c1ec18dee0b8a21f1cfa84ceb2c8c453 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -107,9 +107,7 @@ pub trait LspCommand: 'static + Sized + Send + std::fmt::Debug { } /// When false, `to_lsp_params_or_response` default implementation will return the default response. - fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool { - true - } + fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool; fn to_lsp( &self, @@ -277,6 +275,16 @@ impl LspCommand for PrepareRename { "Prepare rename" } + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + capabilities + .server_capabilities + .rename_provider + .is_some_and(|capability| match capability { + OneOf::Left(enabled) => enabled, + OneOf::Right(options) => options.prepare_provider.unwrap_or(false), + }) + } + fn to_lsp_params_or_response( &self, path: &Path, @@ -459,6 +467,16 @@ impl LspCommand for PerformRename { "Rename" } + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + capabilities + .server_capabilities + .rename_provider + .is_some_and(|capability| match capability { + OneOf::Left(enabled) => enabled, + OneOf::Right(_options) => true, + }) + } + fn to_lsp( &self, path: &Path, @@ -583,7 +601,10 @@ impl LspCommand for GetDefinition { capabilities .server_capabilities .definition_provider - .is_some() + .is_some_and(|capability| match capability { + OneOf::Left(supported) => supported, + OneOf::Right(_options) => true, + }) } fn to_lsp( @@ -682,7 +703,11 @@ impl LspCommand for GetDeclaration { capabilities .server_capabilities .declaration_provider - .is_some() + .is_some_and(|capability| match capability { + lsp::DeclarationCapability::Simple(supported) => supported, + lsp::DeclarationCapability::RegistrationOptions(..) => true, + lsp::DeclarationCapability::Options(..) => true, + }) } fn to_lsp( @@ -777,6 +802,16 @@ impl LspCommand for GetImplementation { "Get implementation" } + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + capabilities + .server_capabilities + .implementation_provider + .is_some_and(|capability| match capability { + lsp::ImplementationProviderCapability::Simple(enabled) => enabled, + lsp::ImplementationProviderCapability::Options(_options) => true, + }) + } + fn to_lsp( &self, path: &Path, @@ -1437,7 +1472,10 @@ impl LspCommand for GetDocumentHighlights { capabilities .server_capabilities .document_highlight_provider - .is_some() + .is_some_and(|capability| match capability { + OneOf::Left(supported) => supported, + OneOf::Right(_options) => true, + }) } fn to_lsp( @@ -1590,7 +1628,10 @@ impl LspCommand for GetDocumentSymbols { capabilities .server_capabilities .document_symbol_provider - .is_some() + .is_some_and(|capability| match capability { + OneOf::Left(supported) => supported, + OneOf::Right(_options) => true, + }) } fn to_lsp( @@ -2116,6 +2157,13 @@ impl LspCommand for GetCompletions { "Get completion" } + fn check_capabilities(&self, capabilities: AdapterServerCapabilities) -> bool { + capabilities + .server_capabilities + .completion_provider + .is_some() + } + fn to_lsp( &self, path: &Path, @@ -4161,7 +4209,11 @@ impl LspCommand for GetDocumentColor { server_capabilities .server_capabilities .color_provider - .is_some() + .is_some_and(|capability| match capability { + lsp::ColorProviderCapability::Simple(supported) => supported, + lsp::ColorProviderCapability::ColorProvider(..) => true, + lsp::ColorProviderCapability::Options(..) => true, + }) } fn to_lsp( diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 6e56dec99944264df93528fefeb9db5f51c43844..15057ac7f201d6f9da8478d60bcd9388e213258b 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -3545,7 +3545,8 @@ pub struct LspStore { lsp_data: Option, } -type DocumentColorTask = Shared, Arc>>>; +type DocumentColorTask = + Shared, Arc>>>; #[derive(Debug)] struct LspData { @@ -3557,7 +3558,7 @@ struct LspData { #[derive(Debug, Default)] struct BufferLspData { - colors: Option>, + colors: Option>, } #[derive(Debug)] @@ -6237,13 +6238,13 @@ impl LspStore { .flat_map(|lsp_data| lsp_data.buffer_lsp_data.values()) .filter_map(|buffer_data| buffer_data.get(&abs_path)) .filter_map(|buffer_data| { - let colors = buffer_data.colors.as_deref()?; + let colors = buffer_data.colors.as_ref()?; received_colors_data = true; Some(colors) }) .flatten() .cloned() - .collect::>(); + .collect::>(); if buffer_lsp_data.is_empty() || for_server_id.is_some() { if received_colors_data && for_server_id.is_none() { @@ -6297,42 +6298,25 @@ impl LspStore { let task_abs_path = abs_path.clone(); let new_task = cx .spawn(async move |lsp_store, cx| { - cx.background_executor().timer(Duration::from_millis(50)).await; - let fetched_colors = match lsp_store - .update(cx, |lsp_store, cx| { - lsp_store.fetch_document_colors(buffer, cx) - }) { - Ok(fetch_task) => fetch_task.await - .with_context(|| { - format!( - "Fetching document colors for buffer with path {task_abs_path:?}" - ) - }), - Err(e) => return Err(Arc::new(e)), - }; - let fetched_colors = match fetched_colors { - Ok(fetched_colors) => fetched_colors, - Err(e) => return Err(Arc::new(e)), - }; - - let lsp_colors = lsp_store.update(cx, |lsp_store, _| { - let lsp_data = lsp_store.lsp_data.as_mut().with_context(|| format!( - "Document lsp data got updated between fetch and update for path {task_abs_path:?}" - ))?; - let mut lsp_colors = Vec::new(); - anyhow::ensure!(lsp_data.mtime == buffer_mtime, "Buffer lsp data got updated between fetch and update for path {task_abs_path:?}"); - for (server_id, colors) in fetched_colors { - let colors_lsp_data = &mut lsp_data.buffer_lsp_data.entry(server_id).or_default().entry(task_abs_path.clone()).or_default().colors; - *colors_lsp_data = Some(colors.clone()); - lsp_colors.extend(colors); + match fetch_document_colors( + lsp_store.clone(), + buffer, + task_abs_path.clone(), + cx, + ) + .await + { + Ok(colors) => Ok(colors), + Err(e) => { + lsp_store + .update(cx, |lsp_store, _| { + if let Some(lsp_data) = lsp_store.lsp_data.as_mut() { + lsp_data.colors_update.remove(&task_abs_path); + } + }) + .ok(); + Err(Arc::new(e)) } - Ok(lsp_colors) - }); - - match lsp_colors { - Ok(Ok(lsp_colors)) => Ok(lsp_colors), - Ok(Err(e)) => Err(Arc::new(e)), - Err(e) => Err(Arc::new(e)), } }) .shared(); @@ -6350,11 +6334,11 @@ impl LspStore { } } - fn fetch_document_colors( + fn fetch_document_colors_for_buffer( &mut self, buffer: Entity, cx: &mut Context, - ) -> Task)>>> { + ) -> Task)>>> { if let Some((client, project_id)) = self.upstream_client() { let request_task = client.request(proto::MultiLspQuery { project_id, @@ -6403,7 +6387,9 @@ impl LspStore { .await .into_iter() .fold(HashMap::default(), |mut acc, (server_id, colors)| { - acc.entry(server_id).or_insert_with(Vec::new).extend(colors); + acc.entry(server_id) + .or_insert_with(HashSet::default) + .extend(colors); acc }) .into_iter() @@ -6418,7 +6404,9 @@ impl LspStore { .await .into_iter() .fold(HashMap::default(), |mut acc, (server_id, colors)| { - acc.entry(server_id).or_insert_with(Vec::new).extend(colors); + acc.entry(server_id) + .or_insert_with(HashSet::default) + .extend(colors); acc }) .into_iter() @@ -10691,6 +10679,53 @@ impl LspStore { } } +async fn fetch_document_colors( + lsp_store: WeakEntity, + buffer: Entity, + task_abs_path: PathBuf, + cx: &mut AsyncApp, +) -> anyhow::Result> { + cx.background_executor() + .timer(Duration::from_millis(50)) + .await; + let Some(buffer_mtime) = buffer.update(cx, |buffer, _| buffer.saved_mtime())? else { + return Ok(HashSet::default()); + }; + let fetched_colors = lsp_store + .update(cx, |lsp_store, cx| { + lsp_store.fetch_document_colors_for_buffer(buffer, cx) + })? + .await + .with_context(|| { + format!("Fetching document colors for buffer with path {task_abs_path:?}") + })?; + + lsp_store.update(cx, |lsp_store, _| { + let lsp_data = lsp_store.lsp_data.as_mut().with_context(|| { + format!( + "Document lsp data got updated between fetch and update for path {task_abs_path:?}" + ) + })?; + let mut lsp_colors = HashSet::default(); + anyhow::ensure!( + lsp_data.mtime == buffer_mtime, + "Buffer lsp data got updated between fetch and update for path {task_abs_path:?}" + ); + for (server_id, colors) in fetched_colors { + let colors_lsp_data = &mut lsp_data + .buffer_lsp_data + .entry(server_id) + .or_default() + .entry(task_abs_path.clone()) + .or_default() + .colors; + *colors_lsp_data = Some(colors.clone()); + lsp_colors.extend(colors); + } + Ok(lsp_colors) + })? +} + fn subscribe_to_binary_statuses( languages: &Arc, cx: &mut Context<'_, LspStore>, diff --git a/crates/project/src/lsp_store/lsp_ext_command.rs b/crates/project/src/lsp_store/lsp_ext_command.rs index 2b6d11ceb92aee19240f10b2c140e3d48f3b9586..cb13fa5efcfd753e0ffb12fbcc0f3d84e09ff370 100644 --- a/crates/project/src/lsp_store/lsp_ext_command.rs +++ b/crates/project/src/lsp_store/lsp_ext_command.rs @@ -16,7 +16,7 @@ use language::{ Buffer, point_to_lsp, proto::{deserialize_anchor, serialize_anchor}, }; -use lsp::{LanguageServer, LanguageServerId}; +use lsp::{AdapterServerCapabilities, LanguageServer, LanguageServerId}; use rpc::proto::{self, PeerId}; use serde::{Deserialize, Serialize}; use std::{ @@ -68,6 +68,10 @@ impl LspCommand for ExpandMacro { "Expand macro" } + fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool { + true + } + fn to_lsp( &self, path: &Path, @@ -196,6 +200,10 @@ impl LspCommand for OpenDocs { "Open docs" } + fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool { + true + } + fn to_lsp( &self, path: &Path, @@ -326,6 +334,10 @@ impl LspCommand for SwitchSourceHeader { "Switch source header" } + fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool { + true + } + fn to_lsp( &self, path: &Path, @@ -404,6 +416,10 @@ impl LspCommand for GoToParentModule { "Go to parent module" } + fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool { + true + } + fn to_lsp( &self, path: &Path, @@ -578,6 +594,10 @@ impl LspCommand for GetLspRunnables { "LSP Runnables" } + fn check_capabilities(&self, _: AdapterServerCapabilities) -> bool { + true + } + fn to_lsp( &self, path: &Path, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index cdf66610633178d24637b752ea04de97c205ebca..ae1185c8be186c44cefa3d4ca255b508224f8b41 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -779,13 +779,42 @@ pub struct DocumentColor { pub color_presentations: Vec, } -#[derive(Clone, Debug, PartialEq)] +impl Eq for DocumentColor {} + +impl std::hash::Hash for DocumentColor { + fn hash(&self, state: &mut H) { + self.lsp_range.hash(state); + self.color.red.to_bits().hash(state); + self.color.green.to_bits().hash(state); + self.color.blue.to_bits().hash(state); + self.color.alpha.to_bits().hash(state); + self.resolved.hash(state); + self.color_presentations.hash(state); + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ColorPresentation { pub label: String, pub text_edit: Option, pub additional_text_edits: Vec, } +impl std::hash::Hash for ColorPresentation { + fn hash(&self, state: &mut H) { + self.label.hash(state); + if let Some(ref edit) = self.text_edit { + edit.range.hash(state); + edit.new_text.hash(state); + } + self.additional_text_edits.len().hash(state); + for edit in &self.additional_text_edits { + edit.range.hash(state); + edit.new_text.hash(state); + } + } +} + #[derive(Clone)] pub enum DirectoryLister { Project(Entity), diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index e9805b12a214509d6c9ba2b04c2183d4a7a331e0..9730984f2632be65330203fcd93350cf29233435 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -422,7 +422,12 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext "Rust", FakeLspAdapter { name: "rust-analyzer", - ..Default::default() + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions::default()), + rename_provider: Some(lsp::OneOf::Left(true)), + ..lsp::ServerCapabilities::default() + }, + ..FakeLspAdapter::default() }, ) }); @@ -430,7 +435,11 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext let mut fake_lsp = server_cx.update(|cx| { headless.read(cx).languages.register_fake_language_server( LanguageServerName("rust-analyzer".into()), - Default::default(), + lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions::default()), + rename_provider: Some(lsp::OneOf::Left(true)), + ..lsp::ServerCapabilities::default() + }, None, ) }); From c9ce4aec9176febf5169e45a291fa98e0c454879 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Fri, 27 Jun 2025 14:31:58 -0400 Subject: [PATCH 027/107] Fix debug adapters from extensions not being picked up (#33546) Copy the debug adapter schemas so that they're available to the extension host, like we do for other extension assets. Release Notes: - N/A --- crates/extension_cli/src/main.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/crates/extension_cli/src/main.rs b/crates/extension_cli/src/main.rs index 93148191516bd9810c4a4380ff5b0cbbfea5ac64..7e87a5fd9e97e77ccfc56fc27aabbbe1447a8918 100644 --- a/crates/extension_cli/src/main.rs +++ b/crates/extension_cli/src/main.rs @@ -259,6 +259,33 @@ async fn copy_extension_resources( } } + if !manifest.debug_adapters.is_empty() { + let output_debug_adapter_schemas_dir = output_dir.join("debug_adapter_schemas"); + fs::create_dir_all(&output_debug_adapter_schemas_dir)?; + for (debug_adapter, entry) in &manifest.debug_adapters { + let schema_path = entry.schema_path.clone().unwrap_or_else(|| { + PathBuf::from("debug_adapter_schemas".to_owned()) + .join(format!("{debug_adapter}.json")) + }); + copy_recursive( + fs.as_ref(), + &extension_path.join(schema_path.clone()), + &output_debug_adapter_schemas_dir.join(format!("{debug_adapter}.json")), + CopyOptions { + overwrite: true, + ignore_if_exists: false, + }, + ) + .await + .with_context(|| { + format!( + "failed to copy debug adapter schema '{}'", + schema_path.display() + ) + })?; + } + } + Ok(()) } From 14bb10d78307326123423a3577b02eadd3abf822 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 27 Jun 2025 13:15:50 -0600 Subject: [PATCH 028/107] Don't panic on vintage files (#33543) Release Notes: - remoting: Fix a crash on the remote side when encountering files from before 1970. --- crates/proto/src/proto.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index e3aeb557ab3f55307211e9419176a642f3174097..918ac9e93596ce5de102f841ab95073778aab056 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -632,7 +632,7 @@ impl From for SystemTime { impl From for Timestamp { fn from(time: SystemTime) -> Self { - let duration = time.duration_since(UNIX_EPOCH).unwrap(); + let duration = time.duration_since(UNIX_EPOCH).unwrap_or_default(); Self { seconds: duration.as_secs(), nanos: duration.subsec_nanos(), From f12b0dddf4af5f99015e75dd11605be3b055b201 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Fri, 27 Jun 2025 15:34:21 -0400 Subject: [PATCH 029/107] Touch up extension DAP schemas fix (#33548) Updates #33546 Release Notes: - N/A Co-authored-by: Piotr --- crates/extension_cli/src/main.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/extension_cli/src/main.rs b/crates/extension_cli/src/main.rs index 7e87a5fd9e97e77ccfc56fc27aabbbe1447a8918..45a7e3b6412ea9fba02a6394394b3ca9fc5bc58f 100644 --- a/crates/extension_cli/src/main.rs +++ b/crates/extension_cli/src/main.rs @@ -260,17 +260,20 @@ async fn copy_extension_resources( } if !manifest.debug_adapters.is_empty() { - let output_debug_adapter_schemas_dir = output_dir.join("debug_adapter_schemas"); - fs::create_dir_all(&output_debug_adapter_schemas_dir)?; for (debug_adapter, entry) in &manifest.debug_adapters { let schema_path = entry.schema_path.clone().unwrap_or_else(|| { PathBuf::from("debug_adapter_schemas".to_owned()) - .join(format!("{debug_adapter}.json")) + .join(debug_adapter.as_ref()) + .with_extension("json") }); + let parent = schema_path + .parent() + .with_context(|| format!("invalid empty schema path for {debug_adapter}"))?; + fs::create_dir_all(output_dir.join(parent))?; copy_recursive( fs.as_ref(), - &extension_path.join(schema_path.clone()), - &output_debug_adapter_schemas_dir.join(format!("{debug_adapter}.json")), + &extension_path.join(&schema_path), + &output_dir.join(&schema_path), CopyOptions { overwrite: true, ignore_if_exists: false, From 5fbb7b0d40c636f19fe912fb873866cab45b1527 Mon Sep 17 00:00:00 2001 From: Umesh Yadav <23421535+imumesh18@users.noreply.github.com> Date: Sat, 28 Jun 2025 01:07:26 +0530 Subject: [PATCH 030/107] copilot: Only set Copilot-Vision-Request header for vision requests (#33552) Closes #31951 The fix is copied and translated from copilot chat actual implementation code: https://github.com/microsoft/vscode-copilot-chat/blob/ad7cbcae9a964e8efb869bf1426999e56ea63cf0/src/platform/openai/node/fetch.ts#L493C1-L495C3 Release Notes: - Fix copilot failing due to missing `Copilot-Vision-Request` from request. --- crates/copilot/src/copilot_chat.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/crates/copilot/src/copilot_chat.rs b/crates/copilot/src/copilot_chat.rs index 19cff56c911b2d85f91b5075f238f8320f846e3f..b1fa1565f30ed79fdff763964708fe01c62d023f 100644 --- a/crates/copilot/src/copilot_chat.rs +++ b/crates/copilot/src/copilot_chat.rs @@ -698,16 +698,16 @@ async fn stream_completion( completion_url: Arc, request: Request, ) -> Result>> { - let is_vision_request = request.messages.last().map_or(false, |message| match message { - ChatMessage::User { content } - | ChatMessage::Assistant { content, .. } - | ChatMessage::Tool { content, .. } => { - matches!(content, ChatMessageContent::Multipart(parts) if parts.iter().any(|part| matches!(part, ChatMessagePart::Image { .. }))) - } - _ => false, - }); - - let request_builder = HttpRequest::builder() + let is_vision_request = request.messages.iter().any(|message| match message { + ChatMessage::User { content } + | ChatMessage::Assistant { content, .. } + | ChatMessage::Tool { content, .. } => { + matches!(content, ChatMessageContent::Multipart(parts) if parts.iter().any(|part| matches!(part, ChatMessagePart::Image { .. }))) + } + _ => false, + }); + + let mut request_builder = HttpRequest::builder() .method(Method::POST) .uri(completion_url.as_ref()) .header( @@ -719,8 +719,12 @@ async fn stream_completion( ) .header("Authorization", format!("Bearer {}", api_key)) .header("Content-Type", "application/json") - .header("Copilot-Integration-Id", "vscode-chat") - .header("Copilot-Vision-Request", is_vision_request.to_string()); + .header("Copilot-Integration-Id", "vscode-chat"); + + if is_vision_request { + request_builder = + request_builder.header("Copilot-Vision-Request", is_vision_request.to_string()); + } let is_streaming = request.stream; From f338c46bf79f773ec7a41729270227da8c2660b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Fri, 27 Jun 2025 21:41:17 +0200 Subject: [PATCH 031/107] Fix line indices when using a selection in the context (#33549) Closes #33152. The label for a file selection context had an off-by-one error on line indices. This PR adjusts that. Before: Screenshot 2025-06-27 at 20 55 28 After: Screenshot 2025-06-27 at 20 49 35 Release Notes: - Fixed a off-by-one error on the line indices when using a selection as context for the agent, --- crates/agent_ui/src/context_picker.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/agent_ui/src/context_picker.rs b/crates/agent_ui/src/context_picker.rs index b0069a2446bdce30968518d2af7f6d60ab0ad59e..f303f34a52856a068f1d2da33cf1f0a4fb5813a5 100644 --- a/crates/agent_ui/src/context_picker.rs +++ b/crates/agent_ui/src/context_picker.rs @@ -930,8 +930,8 @@ impl MentionLink { format!( "[@{} ({}-{})]({}:{}:{}-{})", file_name, - line_range.start, - line_range.end, + line_range.start + 1, + line_range.end + 1, Self::SELECTION, full_path, line_range.start, From 28380d714d6dd32f5d7e242d690483714fa3f969 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 27 Jun 2025 09:48:17 -0600 Subject: [PATCH 032/107] Remove `into SelectionEffects` from .change_selections In #32656 I generalized the argument to change selections to allow controling both the scroll and the nav history (and the completion trigger). To avoid conflicting with ongoing debugger cherry-picks I left the argument as an `impl Into<>`, but I think it's clearer to make callers specify what they want here. I converted a lot of `None` arguments to `SelectionEffects::no_scroll()` to be exactly compatible; but I think many people used none as an "i don't care" value in which case Default::default() might be more appropraite --- crates/agent_ui/src/active_thread.rs | 24 +- crates/agent_ui/src/agent_diff.rs | 25 +- crates/agent_ui/src/inline_assistant.rs | 3 +- crates/agent_ui/src/text_thread_editor.rs | 13 +- crates/assistant_tools/src/edit_file_tool.rs | 4 +- .../collab/src/tests/channel_buffer_tests.rs | 10 +- crates/collab/src/tests/editor_tests.rs | 40 +- crates/collab/src/tests/following_tests.rs | 22 +- crates/collab_ui/src/channel_view.rs | 17 +- .../src/copilot_completion_provider.rs | 13 +- crates/debugger_ui/src/stack_trace_view.rs | 9 +- crates/diagnostics/src/diagnostic_renderer.rs | 3 +- crates/diagnostics/src/diagnostics.rs | 3 +- crates/editor/src/editor.rs | 381 ++++++++++-------- crates/editor/src/editor_tests.rs | 278 +++++++------ crates/editor/src/element.rs | 18 +- crates/editor/src/hover_links.rs | 2 +- crates/editor/src/hover_popover.rs | 4 +- crates/editor/src/inlay_hint_cache.rs | 81 ++-- crates/editor/src/items.rs | 6 +- crates/editor/src/jsx_tag_auto_close.rs | 2 +- crates/editor/src/mouse_context_menu.rs | 6 +- crates/editor/src/proposed_changes_editor.rs | 6 +- crates/editor/src/test.rs | 6 +- crates/editor/src/test/editor_test_context.rs | 6 +- crates/git_ui/src/commit_view.rs | 4 +- crates/git_ui/src/project_diff.rs | 15 +- crates/go_to_line/src/go_to_line.rs | 13 +- .../src/inline_completion_button.rs | 13 +- crates/journal/src/journal.rs | 11 +- crates/language_tools/src/syntax_tree_view.rs | 4 +- .../src/markdown_preview_view.rs | 11 +- crates/outline/src/outline.rs | 11 +- crates/outline_panel/src/outline_panel.rs | 6 +- crates/picker/src/picker.rs | 11 +- crates/project_panel/src/project_panel.rs | 4 +- crates/project_symbols/src/project_symbols.rs | 11 +- crates/repl/src/session.rs | 3 +- crates/rules_library/src/rules_library.rs | 28 +- crates/search/src/buffer_search.rs | 21 +- crates/search/src/project_search.rs | 10 +- crates/tasks_ui/src/modal.rs | 4 +- crates/tasks_ui/src/tasks_ui.rs | 4 +- crates/vim/src/change_list.rs | 4 +- crates/vim/src/command.rs | 28 +- crates/vim/src/helix.rs | 12 +- crates/vim/src/indent.rs | 9 +- crates/vim/src/insert.rs | 4 +- crates/vim/src/motion.rs | 3 +- crates/vim/src/normal.rs | 23 +- crates/vim/src/normal/change.rs | 5 +- crates/vim/src/normal/convert.rs | 12 +- crates/vim/src/normal/delete.rs | 9 +- crates/vim/src/normal/increment.rs | 4 +- crates/vim/src/normal/mark.rs | 7 +- crates/vim/src/normal/paste.rs | 12 +- crates/vim/src/normal/substitute.rs | 4 +- crates/vim/src/normal/toggle_comments.rs | 10 +- crates/vim/src/normal/yank.rs | 10 +- crates/vim/src/replace.rs | 10 +- crates/vim/src/rewrap.rs | 12 +- crates/vim/src/surrounds.rs | 10 +- crates/vim/src/vim.rs | 53 +-- crates/vim/src/visual.rs | 35 +- crates/zed/src/zed.rs | 20 +- 65 files changed, 837 insertions(+), 625 deletions(-) diff --git a/crates/agent_ui/src/active_thread.rs b/crates/agent_ui/src/active_thread.rs index 5f9dfc7ab2ee844d7a8f4b6077861ff24e6d03cf..7ee3b7158b6f9f8db6788c80f93123bd1ad463c6 100644 --- a/crates/agent_ui/src/active_thread.rs +++ b/crates/agent_ui/src/active_thread.rs @@ -19,7 +19,7 @@ use audio::{Audio, Sound}; use collections::{HashMap, HashSet}; use editor::actions::{MoveUp, Paste}; use editor::scroll::Autoscroll; -use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer}; +use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, SelectionEffects}; use gpui::{ AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry, ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla, @@ -689,9 +689,12 @@ fn open_markdown_link( }) .context("Could not find matching symbol")?; - editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_anchor_ranges([symbol_range.start..symbol_range.start]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_anchor_ranges([symbol_range.start..symbol_range.start]), + ); anyhow::Ok(()) }) }) @@ -708,10 +711,15 @@ fn open_markdown_link( .downcast::() .context("Item is not an editor")?; active_editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_ranges([Point::new(line_range.start as u32, 0) - ..Point::new(line_range.start as u32, 0)]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| { + s.select_ranges([Point::new(line_range.start as u32, 0) + ..Point::new(line_range.start as u32, 0)]) + }, + ); anyhow::Ok(()) }) }) diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs index b8e67512e2b069f2a4f19c4903512f385c4eeab7..1a0f3ff27d83a98d343985b3f827aab26afd192a 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -5,7 +5,8 @@ use anyhow::Result; use buffer_diff::DiffHunkStatus; use collections::{HashMap, HashSet}; use editor::{ - Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot, ToPoint, + Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot, + SelectionEffects, ToPoint, actions::{GoToHunk, GoToPreviousHunk}, scroll::Autoscroll, }; @@ -171,15 +172,9 @@ impl AgentDiffPane { if let Some(first_hunk) = first_hunk { let first_hunk_start = first_hunk.multi_buffer_range().start; - editor.change_selections( - Some(Autoscroll::fit()), - window, - cx, - |selections| { - selections - .select_anchor_ranges([first_hunk_start..first_hunk_start]); - }, - ) + editor.change_selections(Default::default(), window, cx, |selections| { + selections.select_anchor_ranges([first_hunk_start..first_hunk_start]); + }) } } @@ -242,7 +237,7 @@ impl AgentDiffPane { if let Some(first_hunk) = first_hunk { let first_hunk_start = first_hunk.multi_buffer_range().start; - editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_anchor_ranges([first_hunk_start..first_hunk_start]); }) } @@ -416,7 +411,7 @@ fn update_editor_selection( }; if let Some(target_hunk) = target_hunk { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { let next_hunk_start = target_hunk.multi_buffer_range().start; selections.select_anchor_ranges([next_hunk_start..next_hunk_start]); }) @@ -1544,7 +1539,7 @@ impl AgentDiff { let first_hunk_start = first_hunk.multi_buffer_range().start; editor.change_selections( - Some(Autoscroll::center()), + SelectionEffects::scroll(Autoscroll::center()), window, cx, |selections| { @@ -1868,7 +1863,7 @@ mod tests { // Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) }); }); @@ -2124,7 +2119,7 @@ mod tests { // Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end. editor1.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) }); }); diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs index 6e77e764a5ed172f0948d7d76f476377cafd04b7..c9c173a68be5191e77690e826378ca52d3db9684 100644 --- a/crates/agent_ui/src/inline_assistant.rs +++ b/crates/agent_ui/src/inline_assistant.rs @@ -18,6 +18,7 @@ use agent_settings::AgentSettings; use anyhow::{Context as _, Result}; use client::telemetry::Telemetry; use collections::{HashMap, HashSet, VecDeque, hash_map}; +use editor::SelectionEffects; use editor::{ Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint, @@ -1159,7 +1160,7 @@ impl InlineAssistant { let position = assist.range.start; editor.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_anchor_ranges([position..position]) }); diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 645bc451fcb8fbb91d05eb0bfe72814ea630c988..dcb239a46ddec79d7aa52c4180cb511e8b74ac71 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -21,7 +21,6 @@ use editor::{ BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint, }, - scroll::Autoscroll, }; use editor::{FoldPlaceholder, display_map::CreaseId}; use fs::Fs; @@ -389,7 +388,7 @@ impl TextThreadEditor { cursor..cursor }; self.editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges([new_selection]) }); }); @@ -449,8 +448,7 @@ impl TextThreadEditor { if let Some(command) = self.slash_commands.command(name, cx) { self.editor.update(cx, |editor, cx| { editor.transact(window, cx, |editor, window, cx| { - editor - .change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel()); + editor.change_selections(Default::default(), window, cx, |s| s.try_cancel()); let snapshot = editor.buffer().read(cx).snapshot(cx); let newest_cursor = editor.selections.newest::(cx).head(); if newest_cursor.column > 0 @@ -1583,7 +1581,7 @@ impl TextThreadEditor { self.editor.update(cx, |editor, cx| { editor.transact(window, cx, |this, window, cx| { - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(selections); }); this.insert("", window, cx); @@ -3141,6 +3139,7 @@ pub fn make_lsp_adapter_delegate( #[cfg(test)] mod tests { use super::*; + use editor::SelectionEffects; use fs::FakeFs; use gpui::{App, TestAppContext, VisualTestContext}; use indoc::indoc; @@ -3366,7 +3365,9 @@ mod tests { ) { context_editor.update_in(cx, |context_editor, window, cx| { context_editor.editor.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([range])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([range]) + }); }); context_editor.copy(&Default::default(), window, cx); diff --git a/crates/assistant_tools/src/edit_file_tool.rs b/crates/assistant_tools/src/edit_file_tool.rs index fcf82856922c2e1c78345cc129aaea871a63ecfa..8c7728b4b72c9aa52c717e58fbdd63591dd88f0f 100644 --- a/crates/assistant_tools/src/edit_file_tool.rs +++ b/crates/assistant_tools/src/edit_file_tool.rs @@ -10,7 +10,7 @@ use assistant_tool::{ ToolUseStatus, }; use buffer_diff::{BufferDiff, BufferDiffSnapshot}; -use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey, scroll::Autoscroll}; +use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey}; use futures::StreamExt; use gpui::{ Animation, AnimationExt, AnyWindowHandle, App, AppContext, AsyncApp, Entity, Task, @@ -823,7 +823,7 @@ impl ToolCard for EditFileToolCard { let first_hunk_start = first_hunk.multi_buffer_range().start; editor.change_selections( - Some(Autoscroll::fit()), + Default::default(), window, cx, |selections| { diff --git a/crates/collab/src/tests/channel_buffer_tests.rs b/crates/collab/src/tests/channel_buffer_tests.rs index 4069f61f90b48bfedfd4780f0865a061e4ab6971..0b331ff1e66279f5e2f5e52f9d83f0eaca6cfcdb 100644 --- a/crates/collab/src/tests/channel_buffer_tests.rs +++ b/crates/collab/src/tests/channel_buffer_tests.rs @@ -178,7 +178,7 @@ async fn test_channel_notes_participant_indices( channel_view_a.update_in(cx_a, |notes, window, cx| { notes.editor.update(cx, |editor, cx| { editor.insert("a", window, cx); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges(vec![0..1]); }); }); @@ -188,7 +188,7 @@ async fn test_channel_notes_participant_indices( notes.editor.update(cx, |editor, cx| { editor.move_down(&Default::default(), window, cx); editor.insert("b", window, cx); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges(vec![1..2]); }); }); @@ -198,7 +198,7 @@ async fn test_channel_notes_participant_indices( notes.editor.update(cx, |editor, cx| { editor.move_down(&Default::default(), window, cx); editor.insert("c", window, cx); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges(vec![2..3]); }); }); @@ -273,12 +273,12 @@ async fn test_channel_notes_participant_indices( .unwrap(); editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges(vec![0..1]); }); }); editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges(vec![2..3]); }); }); diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 7a51caefa1c2f7f6a3e7f702ae9594b790760d7d..2cc3ca76d1b639cc479cb44cde93a73570d5eb7f 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -4,7 +4,7 @@ use crate::{ }; use call::ActiveCall; use editor::{ - DocumentColorsRenderMode, Editor, EditorSettings, RowInfo, + DocumentColorsRenderMode, Editor, EditorSettings, RowInfo, SelectionEffects, actions::{ ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo, @@ -348,7 +348,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu // Type a completion trigger character as the guest. editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input(".", window, cx); }); cx_b.focus(&editor_b); @@ -461,7 +463,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu // Now we do a second completion, this time to ensure that documentation/snippets are // resolved editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([46..46])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([46..46]) + }); editor.handle_input("; a", window, cx); editor.handle_input(".", window, cx); }); @@ -613,7 +617,7 @@ async fn test_collaborating_with_code_actions( // Move cursor to a location that contains code actions. editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 31)..Point::new(1, 31)]) }); }); @@ -817,7 +821,9 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T // Move cursor to a location that can be renamed. let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([7..7])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([7..7]) + }); editor.rename(&Rename, window, cx).unwrap() }); @@ -863,7 +869,9 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T editor.cancel(&editor::actions::Cancel, window, cx); }); let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([7..8])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([7..8]) + }); editor.rename(&Rename, window, cx).unwrap() }); @@ -1364,7 +1372,9 @@ async fn test_on_input_format_from_host_to_guest( // Type a on type formatting trigger character as the guest. cx_a.focus(&editor_a); editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input(">", window, cx); }); @@ -1460,7 +1470,9 @@ async fn test_on_input_format_from_guest_to_host( // Type a on type formatting trigger character as the guest. cx_b.focus(&editor_b); editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input(":", window, cx); }); @@ -1697,7 +1709,9 @@ async fn test_mutual_editor_inlay_hint_cache_update( let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1; editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone())); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13].clone()) + }); editor.handle_input(":", window, cx); }); cx_b.focus(&editor_b); @@ -1718,7 +1732,9 @@ async fn test_mutual_editor_inlay_hint_cache_update( let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1; editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input("a change to increment both buffers' versions", window, cx); }); cx_a.focus(&editor_a); @@ -2121,7 +2137,9 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo }); editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone())); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13].clone()) + }); editor.handle_input(":", window, cx); }); color_request_handle.next().await.unwrap(); diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 99f9b3350512f8d7eb126cb7a427979ab360d509..a77112213f195190e613c2382300bfbbeca70066 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -6,7 +6,7 @@ use collab_ui::{ channel_view::ChannelView, notifications::project_shared_notification::ProjectSharedNotification, }; -use editor::{Editor, MultiBuffer, PathKey}; +use editor::{Editor, MultiBuffer, PathKey, SelectionEffects}; use gpui::{ AppContext as _, BackgroundExecutor, BorrowAppContext, Entity, SharedString, TestAppContext, VisualContext, VisualTestContext, point, @@ -376,7 +376,9 @@ async fn test_basic_following( // Changes to client A's editor are reflected on client B. editor_a1.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([1..1, 2..2]) + }); }); executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); executor.run_until_parked(); @@ -393,7 +395,9 @@ async fn test_basic_following( editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO")); editor_a1.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([3..3])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([3..3]) + }); editor.set_scroll_position(point(0., 100.), window, cx); }); executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); @@ -1647,7 +1651,9 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T // b should follow a to position 1 editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([1..1])) + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([1..1]) + }) }); cx_a.executor() .advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); @@ -1667,7 +1673,9 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T // b should not follow a to position 2 editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([2..2])) + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([2..2]) + }) }); cx_a.executor() .advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); @@ -1968,7 +1976,7 @@ async fn test_following_to_channel_notes_without_a_shared_project( assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); notes.editor.update(cx, |editor, cx| { editor.insert("Hello from A.", window, cx); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges(vec![3..4]); }); }); @@ -2109,7 +2117,7 @@ async fn test_following_after_replacement(cx_a: &mut TestAppContext, cx_b: &mut workspace.add_item_to_center(Box::new(editor.clone()) as _, window, cx) }); editor.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::row_range(4..4)]); }) }); diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 80cc504308b30579d80e42e35e3267117a8bc456..c872f99aa10ee160ed499621d9aceb2aa7c06a05 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -7,8 +7,8 @@ use client::{ }; use collections::HashMap; use editor::{ - CollaborationHub, DisplayPoint, Editor, EditorEvent, display_map::ToDisplayPoint, - scroll::Autoscroll, + CollaborationHub, DisplayPoint, Editor, EditorEvent, SelectionEffects, + display_map::ToDisplayPoint, scroll::Autoscroll, }; use gpui::{ AnyView, App, ClipboardItem, Context, Entity, EventEmitter, Focusable, Pixels, Point, Render, @@ -260,9 +260,16 @@ impl ChannelView { .find(|item| &Channel::slug(&item.text).to_lowercase() == &position) { self.editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| { - s.replace_cursors_with(|map| vec![item.range.start.to_display_point(map)]) - }) + editor.change_selections( + SelectionEffects::scroll(Autoscroll::focused()), + window, + cx, + |s| { + s.replace_cursors_with(|map| { + vec![item.range.start.to_display_point(map)] + }) + }, + ) }); return; } diff --git a/crates/copilot/src/copilot_completion_provider.rs b/crates/copilot/src/copilot_completion_provider.rs index ff636178753b11bbe3be920a27a27a5c467cef5e..8dc04622f9020c2fe175304764157b409c7936c1 100644 --- a/crates/copilot/src/copilot_completion_provider.rs +++ b/crates/copilot/src/copilot_completion_provider.rs @@ -264,7 +264,8 @@ fn common_prefix, T2: Iterator>(a: T1, b: mod tests { use super::*; use editor::{ - Editor, ExcerptRange, MultiBuffer, test::editor_lsp_test_context::EditorLspTestContext, + Editor, ExcerptRange, MultiBuffer, SelectionEffects, + test::editor_lsp_test_context::EditorLspTestContext, }; use fs::FakeFs; use futures::StreamExt; @@ -478,7 +479,7 @@ mod tests { // Reset the editor to verify how suggestions behave when tabbing on leading indentation. cx.update_editor(|editor, window, cx| { editor.set_text("fn foo() {\n \n}", window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 2)..Point::new(1, 2)]) }); }); @@ -767,7 +768,7 @@ mod tests { ); _ = editor.update(cx, |editor, window, cx| { // Ensure copilot suggestions are shown for the first excerpt. - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 5)..Point::new(1, 5)]) }); editor.next_edit_prediction(&Default::default(), window, cx); @@ -793,7 +794,7 @@ mod tests { ); _ = editor.update(cx, |editor, window, cx| { // Move to another excerpt, ensuring the suggestion gets cleared. - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(4, 5)..Point::new(4, 5)]) }); assert!(!editor.has_active_inline_completion()); @@ -1019,7 +1020,7 @@ mod tests { ); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)]) }); editor.refresh_inline_completion(true, false, window, cx); @@ -1029,7 +1030,7 @@ mod tests { assert!(copilot_requests.try_next().is_err()); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(5, 0)..Point::new(5, 0)]) }); editor.refresh_inline_completion(true, false, window, cx); diff --git a/crates/debugger_ui/src/stack_trace_view.rs b/crates/debugger_ui/src/stack_trace_view.rs index 675522e99996b276b5f62eeb88297dfe7d592579..aef053df4a1ea930fb09a779e08afecfa08ddde9 100644 --- a/crates/debugger_ui/src/stack_trace_view.rs +++ b/crates/debugger_ui/src/stack_trace_view.rs @@ -4,7 +4,7 @@ use collections::HashMap; use dap::StackFrameId; use editor::{ Anchor, Bias, DebugStackFrameLine, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, - RowHighlightOptions, ToPoint, scroll::Autoscroll, + RowHighlightOptions, SelectionEffects, ToPoint, scroll::Autoscroll, }; use gpui::{ AnyView, App, AppContext, Entity, EventEmitter, Focusable, IntoElement, Render, SharedString, @@ -99,10 +99,11 @@ impl StackTraceView { if frame_anchor.excerpt_id != editor.selections.newest_anchor().head().excerpt_id { - let auto_scroll = - Some(Autoscroll::center().for_anchor(frame_anchor)); + let effects = SelectionEffects::scroll( + Autoscroll::center().for_anchor(frame_anchor), + ); - editor.change_selections(auto_scroll, window, cx, |selections| { + editor.change_selections(effects, window, cx, |selections| { let selection_id = selections.new_selection_id(); let selection = Selection { diff --git a/crates/diagnostics/src/diagnostic_renderer.rs b/crates/diagnostics/src/diagnostic_renderer.rs index 9524f97ff1e14599576df549844ee7c164d6d017..77bb249733f612ede3017e1cff592927b40e8d43 100644 --- a/crates/diagnostics/src/diagnostic_renderer.rs +++ b/crates/diagnostics/src/diagnostic_renderer.rs @@ -4,7 +4,6 @@ use editor::{ Anchor, Editor, EditorSnapshot, ToOffset, display_map::{BlockContext, BlockPlacement, BlockProperties, BlockStyle}, hover_popover::diagnostics_markdown_style, - scroll::Autoscroll, }; use gpui::{AppContext, Entity, Focusable, WeakEntity}; use language::{BufferId, Diagnostic, DiagnosticEntry}; @@ -311,7 +310,7 @@ impl DiagnosticBlock { let range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot); editor.unfold_ranges(&[range.start..range.end], true, false, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges([range.start..range.start]); }); window.focus(&editor.focus_handle(cx)); diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 4f66a5a8839ddd8a3a2405a2b57114b73a1cf9f8..8b49c536245a2509cb73254eca8de6d1be1cfd75 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -12,7 +12,6 @@ use diagnostic_renderer::DiagnosticBlock; use editor::{ DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey, display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId}, - scroll::Autoscroll, }; use futures::future::join_all; use gpui::{ @@ -626,7 +625,7 @@ impl ProjectDiagnosticsEditor { if let Some(anchor_range) = anchor_ranges.first() { let range_to_select = anchor_range.start..anchor_range.start; this.editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges([range_to_select]); }) }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 376aa60ba42f275acbdb8fe5e1f59fdf1d7be711..48ceaec18b40b5453901d804c8a06efae5b122b5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1262,6 +1262,19 @@ impl Default for SelectionHistoryMode { } #[derive(Debug)] +/// SelectionEffects controls the side-effects of updating the selection. +/// +/// The default behaviour does "what you mostly want": +/// - it pushes to the nav history if the cursor moved by >10 lines +/// - it re-triggers completion requests +/// - it scrolls to fit +/// +/// You might want to modify these behaviours. For example when doing a "jump" +/// like go to definition, we always want to add to nav history; but when scrolling +/// in vim mode we never do. +/// +/// Similarly, you might want to disable scrolling if you don't want the viewport to +/// move. pub struct SelectionEffects { nav_history: Option, completions: bool, @@ -3164,12 +3177,11 @@ impl Editor { /// effects of selection change occur at the end of the transaction. pub fn change_selections( &mut self, - effects: impl Into, + effects: SelectionEffects, window: &mut Window, cx: &mut Context, change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R, ) -> R { - let effects = effects.into(); if let Some(state) = &mut self.deferred_selection_effects_state { state.effects.scroll = effects.scroll.or(state.effects.scroll); state.effects.completions = effects.completions; @@ -3449,8 +3461,13 @@ impl Editor { }; let selections_count = self.selections.count(); + let effects = if auto_scroll { + SelectionEffects::default() + } else { + SelectionEffects::no_scroll() + }; - self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| { + self.change_selections(effects, window, cx, |s| { if let Some(point_to_delete) = point_to_delete { s.delete(point_to_delete); @@ -3488,13 +3505,18 @@ impl Editor { .buffer_snapshot .anchor_before(position.to_point(&display_map)); - self.change_selections(Some(Autoscroll::newest()), window, cx, |s| { - s.clear_disjoint(); - s.set_pending_anchor_range( - pointer_position..pointer_position, - SelectMode::Character, - ); - }); + self.change_selections( + SelectionEffects::scroll(Autoscroll::newest()), + window, + cx, + |s| { + s.clear_disjoint(); + s.set_pending_anchor_range( + pointer_position..pointer_position, + SelectMode::Character, + ); + }, + ); }; let tail = self.selections.newest::(cx).tail(); @@ -3609,7 +3631,7 @@ impl Editor { pending.reversed = false; } - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.set_pending(pending, mode); }); } else { @@ -3625,7 +3647,7 @@ impl Editor { self.columnar_selection_state.take(); if self.selections.pending_anchor().is_some() { let selections = self.selections.all::(cx); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select(selections); s.clear_pending(); }); @@ -3699,7 +3721,7 @@ impl Editor { _ => selection_ranges, }; - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(ranges); }); cx.notify(); @@ -3739,7 +3761,7 @@ impl Editor { } if self.mode.is_full() - && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel()) + && self.change_selections(Default::default(), window, cx, |s| s.try_cancel()) { return; } @@ -4542,9 +4564,7 @@ impl Editor { }) .collect(); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(new_selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(new_selections)); this.refresh_inline_completion(true, false, window, cx); }); } @@ -4573,7 +4593,7 @@ impl Editor { self.transact(window, cx, |editor, window, cx| { editor.edit(edits, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let mut index = 0; s.move_cursors_with(|map, _, _| { let row = rows[index]; @@ -4635,7 +4655,7 @@ impl Editor { self.transact(window, cx, |editor, window, cx| { editor.edit(edits, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let mut index = 0; s.move_cursors_with(|map, _, _| { let row = rows[index]; @@ -4712,7 +4732,7 @@ impl Editor { anchors }); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select_anchors(selection_anchors); }); @@ -4856,7 +4876,7 @@ impl Editor { .collect(); drop(buffer); - self.change_selections(None, window, cx, |selections| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select(new_selections) }); } @@ -7160,7 +7180,7 @@ impl Editor { self.unfold_ranges(&[target..target], true, false, cx); // Note that this is also done in vim's handler of the Tab action. self.change_selections( - Some(Autoscroll::newest()), + SelectionEffects::scroll(Autoscroll::newest()), window, cx, |selections| { @@ -7205,7 +7225,7 @@ impl Editor { buffer.edit(edits.iter().cloned(), None, cx) }); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_anchor_ranges([last_edit_end..last_edit_end]); }); @@ -7252,9 +7272,14 @@ impl Editor { match &active_inline_completion.completion { InlineCompletion::Move { target, .. } => { let target = *target; - self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| { - selections.select_anchor_ranges([target..target]); - }); + self.change_selections( + SelectionEffects::scroll(Autoscroll::newest()), + window, + cx, + |selections| { + selections.select_anchor_ranges([target..target]); + }, + ); } InlineCompletion::Edit { edits, .. } => { // Find an insertion that starts at the cursor position. @@ -7855,9 +7880,12 @@ impl Editor { this.entry("Run to cursor", None, move |window, cx| { weak_editor .update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |s| { - s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]) - }); + editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]), + ); }) .ok(); @@ -9398,7 +9426,7 @@ impl Editor { .collect::>() }); if let Some(tabstop) = tabstops.first() { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { // Reverse order so that the first range is the newest created selection. // Completions will use it and autoscroll will prioritize it. s.select_ranges(tabstop.ranges.iter().rev().cloned()); @@ -9516,7 +9544,7 @@ impl Editor { } } if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { // Reverse order so that the first range is the newest created selection. // Completions will use it and autoscroll will prioritize it. s.select_ranges(current_ranges.iter().rev().cloned()) @@ -9606,9 +9634,7 @@ impl Editor { } } - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); this.insert("", window, cx); let empty_str: Arc = Arc::from(""); for (buffer, edits) in linked_ranges { @@ -9644,7 +9670,7 @@ impl Editor { pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = movement::right(map, selection.head()); @@ -9787,9 +9813,7 @@ impl Editor { self.transact(window, cx, |this, window, cx| { this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); this.refresh_inline_completion(true, false, window, cx); }); } @@ -9822,9 +9846,7 @@ impl Editor { self.transact(window, cx, |this, window, cx| { this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); }); } @@ -9977,9 +9999,7 @@ impl Editor { ); }); let selections = this.selections.all::(cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); }); } @@ -10004,9 +10024,7 @@ impl Editor { buffer.autoindent_ranges(selections, cx); }); let selections = this.selections.all::(cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); }); } @@ -10087,7 +10105,7 @@ impl Editor { }) .collect(); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(new_selections); }); }); @@ -10153,7 +10171,7 @@ impl Editor { } } - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges(cursor_positions) }); }); @@ -10740,7 +10758,7 @@ impl Editor { }) .collect(); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(new_selections); }); @@ -11091,7 +11109,7 @@ impl Editor { buffer.edit(edits, None, cx); }); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(new_selections); }); @@ -11127,7 +11145,7 @@ impl Editor { this.buffer.update(cx, |buffer, cx| { buffer.edit(edits, None, cx); }); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges([last_edit_start..last_edit_end]); }); }); @@ -11329,7 +11347,7 @@ impl Editor { } }); this.fold_creases(refold_creases, true, window, cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(new_selections); }) }); @@ -11430,9 +11448,7 @@ impl Editor { } }); this.fold_creases(refold_creases, true, window, cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(new_selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(new_selections)); }); } @@ -11440,7 +11456,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); let text_layout_details = &self.text_layout_details(window); self.transact(window, cx, |this, window, cx| { - let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + let edits = this.change_selections(Default::default(), window, cx, |s| { let mut edits: Vec<(Range, String)> = Default::default(); s.move_with(|display_map, selection| { if !selection.is_empty() { @@ -11488,7 +11504,7 @@ impl Editor { this.buffer .update(cx, |buffer, cx| buffer.edit(edits, None, cx)); let selections = this.selections.all::(cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(selections); }); }); @@ -11744,7 +11760,7 @@ impl Editor { } self.transact(window, cx, |this, window, cx| { - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(selections); }); this.insert("", window, cx); @@ -11760,7 +11776,7 @@ impl Editor { pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|snapshot, sel| { if sel.is_empty() { sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row())) @@ -11964,9 +11980,7 @@ impl Editor { }); let selections = this.selections.all::(cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); } else { this.insert(&clipboard_text, window, cx); } @@ -12005,7 +12019,7 @@ impl Editor { if let Some((selections, _)) = self.selection_history.transaction(transaction_id).cloned() { - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_anchors(selections.to_vec()); }); } else { @@ -12035,7 +12049,7 @@ impl Editor { if let Some((_, Some(selections))) = self.selection_history.transaction(transaction_id).cloned() { - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_anchors(selections.to_vec()); }); } else { @@ -12065,7 +12079,7 @@ impl Editor { pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let cursor = if selection.is_empty() { movement::left(map, selection.start) @@ -12079,14 +12093,14 @@ impl Editor { pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None)); }) } pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let cursor = if selection.is_empty() { movement::right(map, selection.end) @@ -12100,7 +12114,7 @@ impl Editor { pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None)); }) } @@ -12121,7 +12135,7 @@ impl Editor { let selection_count = self.selections.count(); let first_selection = self.selections.first_anchor(); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12162,7 +12176,7 @@ impl Editor { let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12199,7 +12213,7 @@ impl Editor { let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12225,7 +12239,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details) }) @@ -12240,7 +12254,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details) }) @@ -12261,7 +12275,7 @@ impl Editor { let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::up_by_rows(map, head, row_count, goal, false, text_layout_details) }) @@ -12299,15 +12313,15 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let autoscroll = if action.center_cursor { - Autoscroll::center() + let effects = if action.center_cursor { + SelectionEffects::scroll(Autoscroll::center()) } else { - Autoscroll::fit() + SelectionEffects::default() }; let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(autoscroll), window, cx, |s| { + self.change_selections(effects, window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12328,7 +12342,7 @@ impl Editor { pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::up(map, head, goal, false, text_layout_details) }) @@ -12349,7 +12363,7 @@ impl Editor { let selection_count = self.selections.count(); let first_selection = self.selections.first_anchor(); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12385,7 +12399,7 @@ impl Editor { let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::down_by_rows(map, head, row_count, goal, false, text_layout_details) }) @@ -12423,14 +12437,14 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let autoscroll = if action.center_cursor { - Autoscroll::center() + let effects = if action.center_cursor { + SelectionEffects::scroll(Autoscroll::center()) } else { - Autoscroll::fit() + SelectionEffects::default() }; let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(autoscroll), window, cx, |s| { + self.change_selections(effects, window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12451,7 +12465,7 @@ impl Editor { pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::down(map, head, goal, false, text_layout_details) }) @@ -12509,7 +12523,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, head, _| { ( movement::previous_word_start(map, head), @@ -12526,7 +12540,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, head, _| { ( movement::previous_subword_start(map, head), @@ -12543,7 +12557,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::previous_word_start(map, head), @@ -12560,7 +12574,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::previous_subword_start(map, head), @@ -12579,7 +12593,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { this.select_autoclose_pair(window, cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = if action.ignore_newlines { @@ -12604,7 +12618,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { this.select_autoclose_pair(window, cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = movement::previous_subword_start(map, selection.head()); @@ -12623,7 +12637,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, head, _| { (movement::next_word_end(map, head), SelectionGoal::None) }); @@ -12637,7 +12651,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, head, _| { (movement::next_subword_end(map, head), SelectionGoal::None) }); @@ -12651,7 +12665,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { (movement::next_word_end(map, head), SelectionGoal::None) }); @@ -12665,7 +12679,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { (movement::next_subword_end(map, head), SelectionGoal::None) }); @@ -12680,7 +12694,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = if action.ignore_newlines { @@ -12704,7 +12718,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = movement::next_subword_end(map, selection.head()); @@ -12723,7 +12737,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, head, _| { ( movement::indented_line_beginning( @@ -12745,7 +12759,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::indented_line_beginning( @@ -12768,7 +12782,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_with(|_, selection| { selection.reversed = true; }); @@ -12793,7 +12807,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, head, _| { ( movement::line_end(map, head, action.stop_at_soft_wraps), @@ -12810,7 +12824,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::line_end(map, head, action.stop_at_soft_wraps), @@ -12869,7 +12883,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::start_of_paragraph(map, selection.head(), 1), @@ -12890,7 +12904,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::end_of_paragraph(map, selection.head(), 1), @@ -12911,7 +12925,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::start_of_paragraph(map, head, 1), @@ -12932,7 +12946,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::end_of_paragraph(map, head, 1), @@ -12953,7 +12967,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::start_of_excerpt( @@ -12978,7 +12992,7 @@ impl Editor { return; } - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::start_of_excerpt( @@ -13003,7 +13017,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::end_of_excerpt( @@ -13028,7 +13042,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::end_of_excerpt( @@ -13053,7 +13067,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev), @@ -13074,7 +13088,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next), @@ -13095,7 +13109,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next), @@ -13116,7 +13130,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev), @@ -13137,7 +13151,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges(vec![0..0]); }); } @@ -13151,7 +13165,7 @@ impl Editor { let mut selection = self.selections.last::(cx); selection.set_head(Point::zero(), SelectionGoal::None); self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select(vec![selection]); }); } @@ -13163,7 +13177,7 @@ impl Editor { } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let cursor = self.buffer.read(cx).read(cx).len(); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges(vec![cursor..cursor]) }); } @@ -13229,7 +13243,7 @@ impl Editor { let buffer = self.buffer.read(cx).snapshot(cx); let mut selection = self.selections.first::(cx); selection.set_head(buffer.len(), SelectionGoal::None); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select(vec![selection]); }); } @@ -13237,7 +13251,7 @@ impl Editor { pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let end = self.buffer.read(cx).read(cx).len(); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(vec![0..end]); }); } @@ -13253,7 +13267,7 @@ impl Editor { selection.end = cmp::min(max_point, Point::new(rows.end.0, 0)); selection.reversed = false; } - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select(selections); }); } @@ -13290,7 +13304,7 @@ impl Editor { } } } - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges(new_selection_ranges); }); } @@ -13438,7 +13452,7 @@ impl Editor { } } - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select(final_selections); }); @@ -13476,7 +13490,12 @@ impl Editor { auto_scroll.is_some(), cx, ); - self.change_selections(auto_scroll, window, cx, |s| { + let effects = if let Some(scroll) = auto_scroll { + SelectionEffects::scroll(scroll) + } else { + SelectionEffects::no_scroll() + }; + self.change_selections(effects, window, cx, |s| { if replace_newest { s.delete(s.newest_anchor().id); } @@ -13688,7 +13707,7 @@ impl Editor { } self.unfold_ranges(&new_selections.clone(), false, false, cx); - self.change_selections(None, window, cx, |selections| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges(new_selections) }); @@ -13859,7 +13878,7 @@ impl Editor { let selections = self.selections.disjoint_anchors(); match selections.first() { Some(first) if selections.len() >= 2 => { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges([first.range()]); }); } @@ -13883,7 +13902,7 @@ impl Editor { let selections = self.selections.disjoint_anchors(); match selections.last() { Some(last) if selections.len() >= 2 => { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges([last.range()]); }); } @@ -14162,9 +14181,7 @@ impl Editor { } drop(snapshot); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); let selections = this.selections.all::(cx); let selections_on_single_row = selections.windows(2).all(|selections| { @@ -14183,7 +14200,7 @@ impl Editor { if advance_downwards { let snapshot = this.buffer.read(cx).snapshot(cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|display_snapshot, display_point, _| { let mut point = display_point.to_point(display_snapshot); point.row += 1; @@ -14250,7 +14267,7 @@ impl Editor { .collect::>(); if selected_larger_symbol { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select(new_selections); }); } @@ -14350,7 +14367,7 @@ impl Editor { if selected_larger_node { self.select_syntax_node_history.disable_clearing = true; - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select(new_selections.clone()); }); self.select_syntax_node_history.disable_clearing = false; @@ -14396,7 +14413,7 @@ impl Editor { } self.select_syntax_node_history.disable_clearing = true; - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select(selections.to_vec()); }); self.select_syntax_node_history.disable_clearing = false; @@ -14661,7 +14678,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_offsets_with(|snapshot, selection| { let Some(enclosing_bracket_ranges) = snapshot.enclosing_bracket_ranges(selection.start..selection.end) @@ -14722,9 +14739,12 @@ impl Editor { self.selection_history.mode = SelectionHistoryMode::Undoing; self.with_selection_effects_deferred(window, cx, |this, window, cx| { this.end_selection(window, cx); - this.change_selections(Some(Autoscroll::newest()), window, cx, |s| { - s.select_anchors(entry.selections.to_vec()) - }); + this.change_selections( + SelectionEffects::scroll(Autoscroll::newest()), + window, + cx, + |s| s.select_anchors(entry.selections.to_vec()), + ); }); self.selection_history.mode = SelectionHistoryMode::Normal; @@ -14745,9 +14765,12 @@ impl Editor { self.selection_history.mode = SelectionHistoryMode::Redoing; self.with_selection_effects_deferred(window, cx, |this, window, cx| { this.end_selection(window, cx); - this.change_selections(Some(Autoscroll::newest()), window, cx, |s| { - s.select_anchors(entry.selections.to_vec()) - }); + this.change_selections( + SelectionEffects::scroll(Autoscroll::newest()), + window, + cx, + |s| s.select_anchors(entry.selections.to_vec()), + ); }); self.selection_history.mode = SelectionHistoryMode::Normal; @@ -14980,7 +15003,7 @@ impl Editor { let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else { return; }; - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges(vec![ next_diagnostic.range.start..next_diagnostic.range.start, ]) @@ -15022,7 +15045,7 @@ impl Editor { let autoscroll = Autoscroll::center(); self.unfold_ranges(&[destination..destination], false, false, cx); - self.change_selections(Some(autoscroll), window, cx, |s| { + self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| { s.select_ranges([destination..destination]); }); } @@ -15085,7 +15108,7 @@ impl Editor { .next_change(1, Direction::Next) .map(|s| s.to_vec()) { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { let map = s.display_map(); s.select_display_ranges(selections.iter().map(|a| { let point = a.to_display_point(&map); @@ -15106,7 +15129,7 @@ impl Editor { .next_change(1, Direction::Prev) .map(|s| s.to_vec()) { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { let map = s.display_map(); s.select_display_ranges(selections.iter().map(|a| { let point = a.to_display_point(&map); @@ -15726,10 +15749,16 @@ impl Editor { match multibuffer_selection_mode { MultibufferSelectionMode::First => { if let Some(first_range) = ranges.first() { - editor.change_selections(None, window, cx, |selections| { - selections.clear_disjoint(); - selections.select_anchor_ranges(std::iter::once(first_range.clone())); - }); + editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |selections| { + selections.clear_disjoint(); + selections + .select_anchor_ranges(std::iter::once(first_range.clone())); + }, + ); } editor.highlight_background::( &ranges, @@ -15738,10 +15767,15 @@ impl Editor { ); } MultibufferSelectionMode::All => { - editor.change_selections(None, window, cx, |selections| { - selections.clear_disjoint(); - selections.select_anchor_ranges(ranges); - }); + editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |selections| { + selections.clear_disjoint(); + selections.select_anchor_ranges(ranges); + }, + ); } } editor.register_buffers_with_language_servers(cx); @@ -15875,7 +15909,7 @@ impl Editor { if rename_selection_range.end > old_name.len() { editor.select_all(&SelectAll, window, cx); } else { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges([rename_selection_range]); }); } @@ -16048,7 +16082,7 @@ impl Editor { .min(rename_range.end); drop(snapshot); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(vec![cursor_in_editor..cursor_in_editor]) }); } else { @@ -16731,7 +16765,7 @@ impl Editor { pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context) { if self.selection_mark_mode { - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|_, sel| { sel.collapse_to(sel.head(), SelectionGoal::None); }); @@ -16747,7 +16781,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|_, sel| { if sel.start != sel.end { sel.reversed = !sel.reversed @@ -17486,7 +17520,7 @@ impl Editor { let autoscroll = Autoscroll::center(); self.unfold_ranges(&[destination..destination], false, false, cx); - self.change_selections(Some(autoscroll), window, cx, |s| { + self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| { s.select_ranges([destination..destination]); }); } @@ -20021,9 +20055,14 @@ impl Editor { None => Autoscroll::newest(), }; let nav_history = editor.nav_history.take(); - editor.change_selections(Some(autoscroll), window, cx, |s| { - s.select_ranges(ranges); - }); + editor.change_selections( + SelectionEffects::scroll(autoscroll), + window, + cx, + |s| { + s.select_ranges(ranges); + }, + ); editor.nav_history = nav_history; }); } @@ -20224,7 +20263,7 @@ impl Editor { } if let Some(relative_utf16_range) = relative_utf16_range { let selections = self.selections.all::(cx); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { let new_ranges = selections.into_iter().map(|range| { let start = OffsetUtf16( range @@ -20367,7 +20406,7 @@ impl Editor { .iter() .map(|selection| (selection.end..selection.end, pending.clone())); this.edit(edits, cx); - this.change_selections(None, window, cx, |s| { + this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| { sel.start + ix * pending.len()..sel.end + ix * pending.len() })); @@ -20523,7 +20562,9 @@ impl Editor { } }) .detach(); - self.change_selections(None, window, cx, |selections| selections.refresh()); + self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + selections.refresh() + }); } pub fn to_pixel_point( @@ -20648,7 +20689,7 @@ impl Editor { buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx)); // skip adding the initial selection to selection history self.selection_history.mode = SelectionHistoryMode::Skipping; - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(selections.into_iter().map(|(start, end)| { snapshot.clip_offset(start, Bias::Left) ..snapshot.clip_offset(end, Bias::Right) @@ -22462,7 +22503,7 @@ impl EntityInputHandler for Editor { }); if let Some(new_selected_ranges) = new_selected_ranges { - this.change_selections(None, window, cx, |selections| { + this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges(new_selected_ranges) }); this.backspace(&Default::default(), window, cx); @@ -22537,7 +22578,9 @@ impl EntityInputHandler for Editor { }); if let Some(ranges) = ranges_to_replace { - this.change_selections(None, window, cx, |s| s.select_ranges(ranges)); + this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(ranges) + }); } let marked_ranges = { @@ -22591,7 +22634,7 @@ impl EntityInputHandler for Editor { .collect::>(); drop(snapshot); - this.change_selections(None, window, cx, |selections| { + this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges(new_selected_ranges) }); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 1ef2294d41d2815b2bfadb21257a0cc3132ebf3a..376effa91dce14f4703eec657d9fb6e04ae3d8d0 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -179,7 +179,9 @@ fn test_edit_events(cx: &mut TestAppContext) { // No event is emitted when the mutation is a no-op. _ = editor2.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([0..0])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([0..0]) + }); editor.backspace(&Backspace, window, cx); }); @@ -202,7 +204,9 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { _ = editor.update(cx, |editor, window, cx| { editor.start_transaction_at(now, window, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges([2..4])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([2..4]) + }); editor.insert("cd", window, cx); editor.end_transaction_at(now, cx); @@ -210,14 +214,18 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { assert_eq!(editor.selections.ranges(cx), vec![4..4]); editor.start_transaction_at(now, window, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges([4..5])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([4..5]) + }); editor.insert("e", window, cx); editor.end_transaction_at(now, cx); assert_eq!(editor.text(cx), "12cde6"); assert_eq!(editor.selections.ranges(cx), vec![5..5]); now += group_interval + Duration::from_millis(1); - editor.change_selections(None, window, cx, |s| s.select_ranges([2..2])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([2..2]) + }); // Simulate an edit in another editor buffer.update(cx, |buffer, cx| { @@ -325,7 +333,7 @@ fn test_ime_composition(cx: &mut TestAppContext) { assert_eq!(editor.marked_text_ranges(cx), None); // Start a new IME composition with multiple cursors. - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ OffsetUtf16(1)..OffsetUtf16(1), OffsetUtf16(3)..OffsetUtf16(3), @@ -623,7 +631,7 @@ fn test_clone(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(selection_ranges.clone()) }); editor.fold_creases( @@ -709,12 +717,12 @@ async fn test_navigation_history(cx: &mut TestAppContext) { // Move the cursor a small distance. // Nothing is added to the navigation history. - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0) ]) }); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0) ]) @@ -723,7 +731,7 @@ async fn test_navigation_history(cx: &mut TestAppContext) { // Move the cursor a large distance. // The history can jump back to the previous position. - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3) ]) @@ -893,7 +901,7 @@ fn test_fold_action(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0) ]); @@ -984,7 +992,7 @@ fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0) ]); @@ -1069,7 +1077,7 @@ fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0) ]); @@ -1301,7 +1309,7 @@ fn test_move_cursor(cx: &mut TestAppContext) { &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)] ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2) ]); @@ -1446,7 +1454,7 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { build_editor(buffer.clone(), window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]); }); @@ -1536,7 +1544,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4), @@ -1731,7 +1739,7 @@ fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) { // First, let's assert behavior on the first line, that was not soft-wrapped. // Start the cursor at the `k` on the first line - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7) ]); @@ -1753,7 +1761,7 @@ fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) { // Now, let's assert behavior on the second line, that ended up being soft-wrapped. // Start the cursor at the last line (`y` that was wrapped to a new line) - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0) ]); @@ -1819,7 +1827,7 @@ fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4), @@ -1901,7 +1909,7 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11), DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4), @@ -1971,7 +1979,7 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { "use one::{\n two::three::\n four::five\n};" ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7) ]); @@ -2234,7 +2242,7 @@ async fn test_autoscroll(cx: &mut TestAppContext) { // on screen, the editor autoscrolls to reveal the newest cursor, and // allows the vertical scroll margin below that cursor. cx.update_editor(|editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges([ Point::new(0, 0)..Point::new(0, 0), Point::new(6, 0)..Point::new(6, 0), @@ -2262,7 +2270,7 @@ async fn test_autoscroll(cx: &mut TestAppContext) { // Add a cursor above the visible area. Since both cursors fit on screen, // the editor scrolls to show both. cx.update_editor(|editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges([ Point::new(1, 0)..Point::new(1, 0), Point::new(6, 0)..Point::new(6, 0), @@ -2429,7 +2437,7 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ // an empty selection - the preceding word fragment is deleted DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -2448,7 +2456,7 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ // an empty selection - the following word fragment is deleted DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3), @@ -2483,7 +2491,7 @@ fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) { }; _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1) ]) @@ -2519,7 +2527,7 @@ fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) { }; _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0) ]) @@ -2558,7 +2566,7 @@ fn test_newline(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2), @@ -2591,7 +2599,7 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) { cx, ); let mut editor = build_editor(buffer.clone(), window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ Point::new(2, 4)..Point::new(2, 5), Point::new(5, 4)..Point::new(5, 5), @@ -3078,7 +3086,7 @@ fn test_insert_with_old_selections(cx: &mut TestAppContext) { let editor = cx.add_window(|window, cx| { let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx); let mut editor = build_editor(buffer.clone(), window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([3..4, 11..12, 19..20]) }); editor @@ -3727,7 +3735,7 @@ fn test_delete_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1), @@ -3750,7 +3758,7 @@ fn test_delete_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1) ]) @@ -3787,7 +3795,7 @@ fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { ); // When multiple lines are selected, remove newlines that are spanned by the selection - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(0, 5)..Point::new(2, 2)]) }); editor.join_lines(&JoinLines, window, cx); @@ -3806,7 +3814,7 @@ fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { ); // When joining an empty line don't insert a space - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(2, 1)..Point::new(2, 2)]) }); editor.join_lines(&JoinLines, window, cx); @@ -3846,7 +3854,7 @@ fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { // We remove any leading spaces assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td"); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(0, 1)..Point::new(0, 1)]) }); editor.join_lines(&JoinLines, window, cx); @@ -3873,7 +3881,7 @@ fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) { let mut editor = build_editor(buffer.clone(), window, cx); let buffer = buffer.read(cx).as_singleton().unwrap(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ Point::new(0, 2)..Point::new(1, 1), Point::new(1, 2)..Point::new(1, 2), @@ -4713,7 +4721,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -4739,7 +4747,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1), DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1), @@ -4763,7 +4771,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -4789,7 +4797,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1), DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1), @@ -4811,7 +4819,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1), DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1), @@ -4848,7 +4856,7 @@ fn test_move_line_up_down(cx: &mut TestAppContext) { window, cx, ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1), @@ -4951,7 +4959,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { Some(Autoscroll::fit()), cx, ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(2, 0)..Point::new(2, 0)]) }); editor.move_line_down(&MoveLineDown, window, cx); @@ -5036,7 +5044,9 @@ fn test_transpose(cx: &mut TestAppContext) { _ = cx.add_window(|window, cx| { let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx); editor.set_style(EditorStyle::default(), window, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges([1..1])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([1..1]) + }); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "bac"); assert_eq!(editor.selections.ranges(cx), [2..2]); @@ -5055,12 +5065,16 @@ fn test_transpose(cx: &mut TestAppContext) { _ = cx.add_window(|window, cx| { let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx); editor.set_style(EditorStyle::default(), window, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges([3..3])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([3..3]) + }); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "acb\nde"); assert_eq!(editor.selections.ranges(cx), [3..3]); - editor.change_selections(None, window, cx, |s| s.select_ranges([4..4])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([4..4]) + }); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "acbd\ne"); assert_eq!(editor.selections.ranges(cx), [5..5]); @@ -5079,7 +5093,9 @@ fn test_transpose(cx: &mut TestAppContext) { _ = cx.add_window(|window, cx| { let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx); editor.set_style(EditorStyle::default(), window, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([1..1, 2..2, 4..4]) + }); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "bacd\ne"); assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]); @@ -5106,7 +5122,9 @@ fn test_transpose(cx: &mut TestAppContext) { _ = cx.add_window(|window, cx| { let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx); editor.set_style(EditorStyle::default(), window, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges([4..4])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([4..4]) + }); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "🏀🍐✋"); assert_eq!(editor.selections.ranges(cx), [8..8]); @@ -6085,7 +6103,7 @@ fn test_select_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -6212,7 +6230,7 @@ async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestA }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -6231,7 +6249,7 @@ async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestA .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ"); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1) ]) @@ -6977,7 +6995,7 @@ async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) { // Move cursor to a different position cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]); }); }); @@ -7082,7 +7100,7 @@ async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext "}); cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]); }); }); @@ -7342,7 +7360,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25), DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12), @@ -7524,7 +7542,7 @@ async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContex // Test case 1: Cursor at end of word editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5) ]); @@ -7548,7 +7566,7 @@ async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContex // Test case 2: Cursor at end of statement editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11) ]); @@ -7593,7 +7611,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte // Test 1: Cursor on a letter of a string word editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17) ]); @@ -7627,7 +7645,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte // Test 2: Partial selection within a word editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19) ]); @@ -7661,7 +7679,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte // Test 3: Complete word already selected editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21) ]); @@ -7695,7 +7713,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte // Test 4: Selection spanning across words editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24) ]); @@ -7897,7 +7915,9 @@ async fn test_autoindent(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([5..5, 8..8, 9..9]) + }); editor.newline(&Newline, window, cx); assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n"); assert_eq!( @@ -8679,7 +8699,7 @@ async fn test_surround_with_pair(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1), @@ -8829,7 +8849,7 @@ async fn test_delete_autoclose_pair(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ Point::new(0, 1)..Point::new(0, 1), Point::new(1, 1)..Point::new(1, 1), @@ -9511,16 +9531,22 @@ async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) { }); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(1..2)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(1..2)), + ); editor.insert("|one|two|three|", window, cx); }); assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx))); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(60..70)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(60..70)), + ); editor.insert("|four|five|six|", window, cx); }); assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx))); @@ -9683,9 +9709,12 @@ async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) { // Edit only the first buffer editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(10..10)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(10..10)), + ); editor.insert("// edited", window, cx); }); @@ -11097,7 +11126,9 @@ async fn test_signature_help(cx: &mut TestAppContext) { "}); cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([0..0])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([0..0]) + }); }); let mocked_response = lsp::SignatureHelp { @@ -11184,7 +11215,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { // When selecting a range, the popover is gone. // Avoid using `cx.set_state` to not actually edit the document, just change its selections. cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19))); }) }); @@ -11201,7 +11232,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { // When unselecting again, the popover is back if within the brackets. cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19))); }) }); @@ -11221,7 +11252,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape. cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0))); s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19))); }) @@ -11262,7 +11293,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { cx.condition(|editor, _| !editor.signature_help_state.is_shown()) .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19))); }) }); @@ -11274,7 +11305,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { fn sample(param1: u8, param2: u8) {} "}); cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19))); }) }); @@ -11930,7 +11961,7 @@ async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppConte let fake_server = fake_servers.next().await.unwrap(); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ Point::new(1, 11)..Point::new(1, 11), Point::new(7, 11)..Point::new(7, 11), @@ -13571,7 +13602,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx)); editor.update_in(cx, |editor, window, cx| { assert_eq!(editor.text(cx), "aaaa\nbbbb"); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ Point::new(0, 0)..Point::new(0, 0), Point::new(1, 0)..Point::new(1, 0), @@ -13589,7 +13620,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { ); // Ensure the cursor's head is respected when deleting across an excerpt boundary. - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(0, 2)..Point::new(1, 2)]) }); editor.backspace(&Default::default(), window, cx); @@ -13599,7 +13630,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { [Point::new(1, 0)..Point::new(1, 0)] ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 1)..Point::new(0, 1)]) }); editor.backspace(&Default::default(), window, cx); @@ -13647,7 +13678,9 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) { true, ); assert_eq!(editor.text(cx), expected_text); - editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges)); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(selection_ranges) + }); editor.handle_input("X", window, cx); @@ -13708,7 +13741,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) { let editor = cx.add_window(|window, cx| { let mut editor = build_editor(multibuffer.clone(), window, cx); let snapshot = editor.snapshot(window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 3)..Point::new(1, 3)]) }); editor.begin_selection( @@ -13730,7 +13763,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) { // Refreshing selections is a no-op when excerpts haven't changed. _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.refresh()); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh()); assert_eq!( editor.selections.ranges(cx), [ @@ -13755,7 +13788,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) { // Refreshing selections will relocate the first selection to the original buffer // location. - editor.change_selections(None, window, cx, |s| s.refresh()); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh()); assert_eq!( editor.selections.ranges(cx), [ @@ -13817,7 +13850,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) { ); // Ensure we don't panic when selections are refreshed and that the pending selection is finalized. - editor.change_selections(None, window, cx, |s| s.refresh()); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh()); assert_eq!( editor.selections.ranges(cx), [Point::new(0, 3)..Point::new(0, 3)] @@ -13876,7 +13909,7 @@ async fn test_extra_newline_insertion(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3), DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5), @@ -14055,7 +14088,9 @@ async fn test_following(cx: &mut TestAppContext) { // Update the selections only _ = leader.update(cx, |leader, window, cx| { - leader.change_selections(None, window, cx, |s| s.select_ranges([1..1])); + leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([1..1]) + }); }); follower .update(cx, |follower, window, cx| { @@ -14103,7 +14138,9 @@ async fn test_following(cx: &mut TestAppContext) { // Update the selections and scroll position. The follower's scroll position is updated // via autoscroll, not via the leader's exact scroll position. _ = leader.update(cx, |leader, window, cx| { - leader.change_selections(None, window, cx, |s| s.select_ranges([0..0])); + leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([0..0]) + }); leader.request_autoscroll(Autoscroll::newest(), cx); leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx); }); @@ -14127,7 +14164,9 @@ async fn test_following(cx: &mut TestAppContext) { // Creating a pending selection that precedes another selection _ = leader.update(cx, |leader, window, cx| { - leader.change_selections(None, window, cx, |s| s.select_ranges([1..1])); + leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([1..1]) + }); leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx); }); follower @@ -14783,7 +14822,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) { editor_handle.update_in(cx, |editor, window, cx| { window.focus(&editor.focus_handle(cx)); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(0, 21)..Point::new(0, 20)]) }); editor.handle_input("{", window, cx); @@ -16398,7 +16437,7 @@ async fn test_multibuffer_reverts(cx: &mut TestAppContext) { }); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0))); }); editor.git_restore(&Default::default(), window, cx); @@ -16542,9 +16581,12 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) { cx.executor().run_until_parked(); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(1..2)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(1..2)), + ); editor.open_excerpts(&OpenExcerpts, window, cx); }); cx.executor().run_until_parked(); @@ -16594,9 +16636,12 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) { .unwrap(); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(39..40)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(39..40)), + ); editor.open_excerpts(&OpenExcerpts, window, cx); }); cx.executor().run_until_parked(); @@ -16650,9 +16695,12 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) { .unwrap(); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(70..70)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(70..70)), + ); editor.open_excerpts(&OpenExcerpts, window, cx); }); cx.executor().run_until_parked(); @@ -18254,7 +18302,7 @@ async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) { .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 0)..Point::new(1, 0)]) }); }); @@ -18282,7 +18330,7 @@ async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 0)..Point::new(1, 0)]) }); }); @@ -18298,7 +18346,7 @@ async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext ); cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(2, 0)..Point::new(2, 0)]) }); }); @@ -18314,7 +18362,7 @@ async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext ); cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]) }); }); @@ -18345,7 +18393,7 @@ async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) { .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(2, 0)..Point::new(2, 0)]) }); }); @@ -18371,7 +18419,7 @@ async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) { .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 0)..Point::new(1, 0)]) }); }); @@ -19309,14 +19357,14 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) { ); // Test finding task when cursor is inside function body - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(4, 5)..Point::new(4, 5)]) }); let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap(); assert_eq!(row, 3, "Should find task for cursor inside runnable_1"); // Test finding task when cursor is on function name - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(8, 4)..Point::new(8, 4)]) }); let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap(); @@ -19470,7 +19518,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) { .collect::(), "bbbb" ); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]); }); editor.handle_input("B", window, cx); @@ -19697,7 +19745,9 @@ async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut Test HighlightStyle::color(Hsla::green()), cx, ); - editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range))); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(Some(highlight_range)) + }); }); let full_text = format!("\n\n{sample_text}"); @@ -21067,7 +21117,7 @@ println!("5"); }) }); editor_1.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(expected_ranges.clone()); }); }); @@ -21513,7 +21563,7 @@ async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) { let fake_server = fake_servers.next().await.unwrap(); editor.update_in(cx, |editor, window, cx| { editor.set_text("", window, cx); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]); }); let Some((buffer, _)) = editor @@ -22519,7 +22569,7 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) { // Moving cursor should not trigger diagnostic request editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(0, 0)..Point::new(0, 0)]) }); }); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6fee347c17ea6b80a9767d5fcbf9094f6160ac5a..426053707649c01aa655902f1a94c302125ef103 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -5238,8 +5238,8 @@ impl EditorElement { paint_highlight(range.start, range.end, color, edges); } - let scroll_left = layout.position_map.snapshot.scroll_position().x - * layout.position_map.em_advance; + let scroll_left = + layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width; for (wrap_position, active) in layout.wrap_guides.iter() { let x = (layout.position_map.text_hitbox.origin.x @@ -6676,7 +6676,7 @@ impl EditorElement { let position_map: &PositionMap = &position_map; let line_height = position_map.line_height; - let max_glyph_advance = position_map.em_advance; + let max_glyph_width = position_map.em_width; let (delta, axis) = match delta { gpui::ScrollDelta::Pixels(mut pixels) => { //Trackpad @@ -6687,15 +6687,15 @@ impl EditorElement { gpui::ScrollDelta::Lines(lines) => { //Not trackpad let pixels = - point(lines.x * max_glyph_advance, lines.y * line_height); + point(lines.x * max_glyph_width, lines.y * line_height); (pixels, None) } }; let current_scroll_position = position_map.snapshot.scroll_position(); - let x = (current_scroll_position.x * max_glyph_advance + let x = (current_scroll_position.x * max_glyph_width - (delta.x * scroll_sensitivity)) - / max_glyph_advance; + / max_glyph_width; let y = (current_scroll_position.y * line_height - (delta.y * scroll_sensitivity)) / line_height; @@ -8591,7 +8591,7 @@ impl Element for EditorElement { start_row, editor_content_width, scroll_width, - em_advance, + em_width, &line_layouts, cx, ) @@ -10051,7 +10051,7 @@ fn compute_auto_height_layout( mod tests { use super::*; use crate::{ - Editor, MultiBuffer, + Editor, MultiBuffer, SelectionEffects, display_map::{BlockPlacement, BlockProperties}, editor_tests::{init_test, update_test_language_settings}, }; @@ -10176,7 +10176,7 @@ mod tests { window .update(cx, |editor, window, cx| { editor.cursor_shape = CursorShape::Block; - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ Point::new(0, 0)..Point::new(1, 0), Point::new(3, 2)..Point::new(3, 3), diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index a716b2e0314223aa81338942da063d87919a71fe..02f93e6829a3f7ac08ec7dfa390cd846560bb7d5 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -1257,7 +1257,7 @@ mod tests { let snapshot = editor.buffer().read(cx).snapshot(cx); let anchor_range = snapshot.anchor_before(selection_range.start) ..snapshot.anchor_after(selection_range.end); - editor.change_selections(Some(crate::Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character) }); }); diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 9e6fc356ea6ee840824b174fd216d0ea10828d59..cae47895356c4fbd6ffc94779952475ce6f18dd6 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -3,7 +3,7 @@ use crate::{ EditorSnapshot, GlobalDiagnosticRenderer, Hover, display_map::{InlayOffset, ToDisplayPoint, invisibles::is_invisible}, hover_links::{InlayHighlight, RangeInEditor}, - scroll::{Autoscroll, ScrollAmount}, + scroll::ScrollAmount, }; use anyhow::Context as _; use gpui::{ @@ -746,7 +746,7 @@ pub fn open_markdown_url(link: SharedString, window: &mut Window, cx: &mut App) }; editor.update_in(cx, |editor, window, cx| { editor.change_selections( - Some(Autoscroll::fit()), + Default::default(), window, cx, |selections| { diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index dcfa8429a0da818679965dac4cdbc6875a16118f..647f34487ffc3cd8e688dffa9051737b3e44321e 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1302,6 +1302,7 @@ fn apply_hint_update( #[cfg(test)] pub mod tests { + use crate::SelectionEffects; use crate::editor_tests::update_test_language_settings; use crate::scroll::ScrollAmount; use crate::{ExcerptRange, scroll::Autoscroll, test::editor_lsp_test_context::rust_lang}; @@ -1384,7 +1385,9 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input("some change", window, cx); }) .unwrap(); @@ -1698,7 +1701,9 @@ pub mod tests { rs_editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input("some rs change", window, cx); }) .unwrap(); @@ -1733,7 +1738,9 @@ pub mod tests { md_editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input("some md change", window, cx); }) .unwrap(); @@ -2155,7 +2162,9 @@ pub mod tests { ] { editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input(change_after_opening, window, cx); }) .unwrap(); @@ -2199,7 +2208,9 @@ pub mod tests { edits.push(cx.spawn(|mut cx| async move { task_editor .update(&mut cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input(async_later_change, window, cx); }) .unwrap(); @@ -2447,9 +2458,12 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_ranges([selection_in_cached_range..selection_in_cached_range]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_ranges([selection_in_cached_range..selection_in_cached_range]), + ); }) .unwrap(); cx.executor().advance_clock(Duration::from_millis( @@ -2712,15 +2726,24 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) - }); - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]) - }); - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges([Point::new(50, 0)..Point::new(50, 0)]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]), + ); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]), + ); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges([Point::new(50, 0)..Point::new(50, 0)]), + ); }) .unwrap(); cx.executor().run_until_parked(); @@ -2745,9 +2768,12 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]), + ); }) .unwrap(); cx.executor().advance_clock(Duration::from_millis( @@ -2778,9 +2804,12 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]), + ); }) .unwrap(); cx.executor().advance_clock(Duration::from_millis( @@ -2812,7 +2841,7 @@ pub mod tests { editor_edited.store(true, Ordering::Release); editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(57, 0)..Point::new(57, 0)]) }); editor.handle_input("++++more text++++", window, cx); @@ -3130,7 +3159,7 @@ pub mod tests { cx.executor().run_until_parked(); editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) }) }) @@ -3412,7 +3441,7 @@ pub mod tests { cx.executor().run_until_parked(); editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) }) }) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index ec3590dba217677bbaf2c8aa36bfd3147b9d6cbf..fa6bd93ab8558628670cb315e672ddf4fb3ebcab 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1352,7 +1352,7 @@ impl ProjectItem for Editor { cx, ); if !restoration_data.selections.is_empty() { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(clip_ranges(&restoration_data.selections, &snapshot)); }); } @@ -1558,7 +1558,7 @@ impl SearchableItem for Editor { ) { self.unfold_ranges(&[matches[index].clone()], false, true, cx); let range = self.range_for_match(&matches[index]); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges([range]); }) } @@ -1570,7 +1570,7 @@ impl SearchableItem for Editor { cx: &mut Context, ) { self.unfold_ranges(matches, false, false, cx); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(matches.iter().cloned()) }); } diff --git a/crates/editor/src/jsx_tag_auto_close.rs b/crates/editor/src/jsx_tag_auto_close.rs index f24fe46100879ce885d7bf863e797458c8bac52d..95a792583953e02a77e592ea957b752f0f8042bb 100644 --- a/crates/editor/src/jsx_tag_auto_close.rs +++ b/crates/editor/src/jsx_tag_auto_close.rs @@ -843,7 +843,7 @@ mod jsx_tag_autoclose_tests { let mut cx = EditorTestContext::for_editor(editor, cx).await; cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select(vec![ Selection::from_offset(4), Selection::from_offset(9), diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index b9b8cbe997b2c6bbdd4f45e50e25621c037badf1..4780f1f56582bf675d7cd7deb7b8f8effb98bfae 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -1,8 +1,8 @@ use crate::{ Copy, CopyAndTrim, CopyPermalinkToLine, Cut, DisplayPoint, DisplaySnapshot, Editor, EvaluateSelectedText, FindAllReferences, GoToDeclaration, GoToDefinition, GoToImplementation, - GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, SelectionExt, - ToDisplayPoint, ToggleCodeActions, + GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, SelectionEffects, + SelectionExt, ToDisplayPoint, ToggleCodeActions, actions::{Format, FormatSelections}, selections_collection::SelectionsCollection, }; @@ -177,7 +177,7 @@ pub fn deploy_context_menu( let anchor = buffer.anchor_before(point.to_point(&display_map)); if !display_ranges(&display_map, &editor.selections).any(|r| r.contains(&point)) { // Move the cursor to the clicked location so that dispatched actions make sense - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.clear_disjoint(); s.set_pending_anchor_range(anchor..anchor, SelectMode::Character); }); diff --git a/crates/editor/src/proposed_changes_editor.rs b/crates/editor/src/proposed_changes_editor.rs index c5f937f20c3c56b16f42b8e5b501b4a21e0e987f..1ead45b3de89c0705510f8afc55ecf6176a4d7a2 100644 --- a/crates/editor/src/proposed_changes_editor.rs +++ b/crates/editor/src/proposed_changes_editor.rs @@ -1,4 +1,4 @@ -use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SemanticsProvider}; +use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SelectionEffects, SemanticsProvider}; use buffer_diff::BufferDiff; use collections::HashSet; use futures::{channel::mpsc, future::join_all}; @@ -213,7 +213,9 @@ impl ProposedChangesEditor { self.buffer_entries = buffer_entries; self.editor.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |selections| selections.refresh()); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + selections.refresh() + }); editor.buffer.update(cx, |buffer, cx| { for diff in new_diffs { buffer.add_diff(diff, cx) diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 9e20d14b61c6413fda35bdc7c3e0f2d0521f7aa4..0a9d5e9535d2b2d29e33ee49a8afa46a387d773e 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -5,7 +5,7 @@ use std::{rc::Rc, sync::LazyLock}; pub use crate::rust_analyzer_ext::expand_macro_recursively; use crate::{ - DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, + DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, SelectionEffects, display_map::{ Block, BlockPlacement, CustomBlockId, DisplayMap, DisplayRow, DisplaySnapshot, ToDisplayPoint, @@ -93,7 +93,9 @@ pub fn select_ranges( ) { let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true); assert_eq!(editor.text(cx), unmarked_text); - editor.change_selections(None, window, cx, |s| s.select_ranges(text_ranges)); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(text_ranges) + }); } #[track_caller] diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 195abbe6d98acafb0fa5a874362dd41a2e0fc630..bdf73da5fbfd5d4c29826859790493fbb8494239 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -1,5 +1,5 @@ use crate::{ - AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, RowExt, + AnchorRangeExt, DisplayPoint, Editor, MultiBuffer, RowExt, display_map::{HighlightKey, ToDisplayPoint}, }; use buffer_diff::DiffHunkStatusKind; @@ -362,7 +362,7 @@ impl EditorTestContext { let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true); self.editor.update_in(&mut self.cx, |editor, window, cx| { editor.set_text(unmarked_text, window, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(selection_ranges) }) }); @@ -379,7 +379,7 @@ impl EditorTestContext { let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true); self.editor.update_in(&mut self.cx, |editor, window, cx| { assert_eq!(editor.text(cx), unmarked_text); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(selection_ranges) }) }); diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index e07f84ba0272cb05572e404106af637788510a6e..c8c237fe90f12f2ac4ead04e0f2f0b4955f8bc1c 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, Result}; use buffer_diff::{BufferDiff, BufferDiffSnapshot}; -use editor::{Editor, EditorEvent, MultiBuffer}; +use editor::{Editor, EditorEvent, MultiBuffer, SelectionEffects}; use git::repository::{CommitDetails, CommitDiff, CommitSummary, RepoPath}; use gpui::{ AnyElement, AnyView, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, @@ -154,7 +154,7 @@ impl CommitView { }); editor.update(cx, |editor, cx| { editor.disable_header_for_buffer(metadata_buffer_id.unwrap(), cx); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges(vec![0..0]); }); }); diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index 371759bd24eb21ae53995648cf86a794b114e156..f858bea94c288efc5dd24c3c17c63bc4b3c63aa2 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -8,7 +8,7 @@ use anyhow::Result; use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus}; use collections::HashSet; use editor::{ - Editor, EditorEvent, + Editor, EditorEvent, SelectionEffects, actions::{GoToHunk, GoToPreviousHunk}, scroll::Autoscroll, }; @@ -255,9 +255,14 @@ impl ProjectDiff { fn move_to_path(&mut self, path_key: PathKey, window: &mut Window, cx: &mut Context) { if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) { self.editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| { - s.select_ranges([position..position]); - }) + editor.change_selections( + SelectionEffects::scroll(Autoscroll::focused()), + window, + cx, + |s| { + s.select_ranges([position..position]); + }, + ) }); } else { self.pending_scroll = Some(path_key); @@ -463,7 +468,7 @@ impl ProjectDiff { self.editor.update(cx, |editor, cx| { if was_empty { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { // TODO select the very beginning (possibly inside a deletion) selections.select_ranges([0..0]) }); diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index bba9617975774883ba869e4a6e607cd66cebee5a..1ac933e316bcde24384139c851a8bedb63388611 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -2,8 +2,8 @@ pub mod cursor_position; use cursor_position::{LineIndicatorFormat, UserCaretPosition}; use editor::{ - Anchor, Editor, MultiBufferSnapshot, RowHighlightOptions, ToOffset, ToPoint, actions::Tab, - scroll::Autoscroll, + Anchor, Editor, MultiBufferSnapshot, RowHighlightOptions, SelectionEffects, ToOffset, ToPoint, + actions::Tab, scroll::Autoscroll, }; use gpui::{ App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, SharedString, Styled, @@ -249,9 +249,12 @@ impl GoToLine { let Some(start) = self.anchor_from_query(&snapshot, cx) else { return; }; - editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_anchor_ranges([start..start]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_anchor_ranges([start..start]), + ); editor.focus_handle(cx).focus(window); cx.notify() }); diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index 4ff793cbaf47a80bff266d21aebd273849c97875..4e9c887124d4583c0123db94508c3f2026fddc97 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/crates/inline_completion_button/src/inline_completion_button.rs @@ -2,7 +2,7 @@ use anyhow::Result; use client::{UserStore, zed_urls}; use copilot::{Copilot, Status}; use editor::{ - Editor, + Editor, SelectionEffects, actions::{ShowEditPrediction, ToggleEditPrediction}, scroll::Autoscroll, }; @@ -929,9 +929,14 @@ async fn open_disabled_globs_setting_in_editor( .map(|inner_match| inner_match.start()..inner_match.end()) }); if let Some(range) = range { - item.change_selections(Some(Autoscroll::newest()), window, cx, |selections| { - selections.select_ranges(vec![range]); - }); + item.change_selections( + SelectionEffects::scroll(Autoscroll::newest()), + window, + cx, + |selections| { + selections.select_ranges(vec![range]); + }, + ); } })?; diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 0aed317a0b80f0d0bb52095a9d6d5f95489bce2f..08bdb8e04f620518ef7955361979f28d83353718 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -1,7 +1,7 @@ use anyhow::Result; use chrono::{Datelike, Local, NaiveTime, Timelike}; -use editor::Editor; use editor::scroll::Autoscroll; +use editor::{Editor, SelectionEffects}; use gpui::{App, AppContext as _, Context, Window, actions}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -168,9 +168,12 @@ pub fn new_journal_entry(workspace: &Workspace, window: &mut Window, cx: &mut Ap if let Some(editor) = item.downcast::().map(|editor| editor.downgrade()) { editor.update_in(cx, |editor, window, cx| { let len = editor.buffer().read(cx).len(cx); - editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_ranges([len..len]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_ranges([len..len]), + ); if len > 0 { editor.insert("\n\n", window, cx); } diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index 99132ce452e4680c8a7302f4c1afbc9d62b613a9..6f74e76e261b7b5f33463fe7932c7eaf0fa2a9fe 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -1,4 +1,4 @@ -use editor::{Anchor, Editor, ExcerptId, scroll::Autoscroll}; +use editor::{Anchor, Editor, ExcerptId, SelectionEffects, scroll::Autoscroll}; use gpui::{ App, AppContext as _, Context, Div, Entity, EventEmitter, FocusHandle, Focusable, Hsla, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, @@ -340,7 +340,7 @@ impl Render for SyntaxTreeView { mem::swap(&mut range.start, &mut range.end); editor.change_selections( - Some(Autoscroll::newest()), + SelectionEffects::scroll(Autoscroll::newest()), window, cx, |selections| { selections.select_ranges(vec![range]); diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index bf1a1da5727a9143e844921dabd770728dc8bcf0..f22671d5dfaf2badafb9a7be5b372c91bd0b1ef6 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -4,7 +4,7 @@ use std::{ops::Range, path::PathBuf}; use anyhow::Result; use editor::scroll::Autoscroll; -use editor::{Editor, EditorEvent}; +use editor::{Editor, EditorEvent, SelectionEffects}; use gpui::{ App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ListState, ParentElement, Render, RetainAllImageCache, Styled, Subscription, Task, @@ -468,9 +468,12 @@ impl MarkdownPreviewView { ) { if let Some(state) = &self.active_editor { state.editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::center()), window, cx, |selections| { - selections.select_ranges(vec![selection]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |selections| selections.select_ranges(vec![selection]), + ); window.focus(&editor.focus_handle(cx)); }); } diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 3fec1d616ab5cbe577d4f3fec7fff1449c62fec6..8c5e78d77bce76e62ef94d2501dbef588cd76f00 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -4,8 +4,8 @@ use std::{ sync::Arc, }; -use editor::RowHighlightOptions; use editor::{Anchor, AnchorRangeExt, Editor, scroll::Autoscroll}; +use editor::{RowHighlightOptions, SelectionEffects}; use fuzzy::StringMatch; use gpui::{ App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, HighlightStyle, @@ -288,9 +288,12 @@ impl PickerDelegate for OutlineViewDelegate { .highlighted_rows::() .next(); if let Some((rows, _)) = highlight { - active_editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_ranges([rows.start..rows.start]) - }); + active_editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_ranges([rows.start..rows.start]), + ); active_editor.clear_row_highlights::(); window.focus(&active_editor.focus_handle(cx)); } diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 5bb771c1e9fc8e1e7d605e1583b52137f0181bd4..0be05d458908e3d7b1317ea205664a349eb6ef5f 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -19,10 +19,10 @@ use collections::{BTreeSet, HashMap, HashSet, hash_map}; use db::kvp::KEY_VALUE_STORE; use editor::{ AnchorRangeExt, Bias, DisplayPoint, Editor, EditorEvent, EditorSettings, ExcerptId, - ExcerptRange, MultiBufferSnapshot, RangeToAnchorExt, ShowScrollbar, + ExcerptRange, MultiBufferSnapshot, RangeToAnchorExt, SelectionEffects, ShowScrollbar, display_map::ToDisplayPoint, items::{entry_git_aware_label_color, entry_label_color}, - scroll::{Autoscroll, AutoscrollStrategy, ScrollAnchor, ScrollbarAutoHide}, + scroll::{Autoscroll, ScrollAnchor, ScrollbarAutoHide}, }; use file_icons::FileIcons; use fuzzy::{StringMatch, StringMatchCandidate, match_strings}; @@ -1099,7 +1099,7 @@ impl OutlinePanel { if change_selection { active_editor.update(cx, |editor, cx| { editor.change_selections( - Some(Autoscroll::Strategy(AutoscrollStrategy::Center, None)), + SelectionEffects::scroll(Autoscroll::center()), window, cx, |s| s.select_ranges(Some(anchor..anchor)), diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index c1ebe25538c4db1f02539f5138c065661be47085..4a122ac7316ed1a7552eda41ef223c62bc3ba910 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -4,7 +4,7 @@ pub mod popover_menu; use anyhow::Result; use editor::{ - Editor, + Editor, SelectionEffects, actions::{MoveDown, MoveUp}, scroll::Autoscroll, }; @@ -695,9 +695,12 @@ impl Picker { editor.update(cx, |editor, cx| { editor.set_text(query, window, cx); let editor_offset = editor.buffer().read(cx).len(cx); - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(editor_offset..editor_offset)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(editor_offset..editor_offset)), + ); }); } } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 3bcc881f9d8a39ddbf1285e0deffe6b2907a4aa5..4db83bcf4c897d3a9bddf304ee96b3de600899bb 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -12,7 +12,7 @@ use editor::{ entry_diagnostic_aware_icon_decoration_and_color, entry_diagnostic_aware_icon_name_and_color, entry_git_aware_label_color, }, - scroll::{Autoscroll, ScrollbarAutoHide}, + scroll::ScrollbarAutoHide, }; use file_icons::FileIcons; use git::status::GitSummary; @@ -1589,7 +1589,7 @@ impl ProjectPanel { }); self.filename_editor.update(cx, |editor, cx| { editor.set_text(file_name, window, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges([selection]) }); window.focus(&editor.focus_handle(cx)); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index a9ba14264ff4a1c30536f6b400f0336bc49a1631..47aed8f470f3538f34bff0a0accdd55d9f1ac70e 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -1,4 +1,4 @@ -use editor::{Bias, Editor, scroll::Autoscroll, styled_runs_for_code_label}; +use editor::{Bias, Editor, SelectionEffects, scroll::Autoscroll, styled_runs_for_code_label}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ App, Context, DismissEvent, Entity, FontWeight, ParentElement, StyledText, Task, WeakEntity, @@ -136,9 +136,12 @@ impl PickerDelegate for ProjectSymbolsDelegate { workspace.open_project_item::(pane, buffer, true, true, window, cx); editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_ranges([position..position]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_ranges([position..position]), + ); }); })?; anyhow::Ok(()) diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index 20518fb12cc39c54993a077decd0ee1ff5f81c8b..18d41f3eae97ce4288d95e1e0eabb57d4b47adec 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -8,6 +8,7 @@ use crate::{ }; use anyhow::Context as _; use collections::{HashMap, HashSet}; +use editor::SelectionEffects; use editor::{ Anchor, AnchorRangeExt as _, Editor, MultiBuffer, ToPoint, display_map::{ @@ -477,7 +478,7 @@ impl Session { if move_down { editor.update(cx, move |editor, cx| { editor.change_selections( - Some(Autoscroll::top_relative(8)), + SelectionEffects::scroll(Autoscroll::top_relative(8)), window, cx, |selections| { diff --git a/crates/rules_library/src/rules_library.rs b/crates/rules_library/src/rules_library.rs index 231647ef5a930da03a50b21eb571d0f19e039e7a..5e249162d3286e777ba28f8c645f8e2918bc9acf 100644 --- a/crates/rules_library/src/rules_library.rs +++ b/crates/rules_library/src/rules_library.rs @@ -1,6 +1,6 @@ use anyhow::Result; use collections::{HashMap, HashSet}; -use editor::CompletionProvider; +use editor::{CompletionProvider, SelectionEffects}; use editor::{CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle, actions::Tab}; use gpui::{ Action, App, Bounds, Entity, EventEmitter, Focusable, PromptLevel, Subscription, Task, @@ -895,10 +895,15 @@ impl RulesLibrary { } EditorEvent::Blurred => { title_editor.update(cx, |title_editor, cx| { - title_editor.change_selections(None, window, cx, |selections| { - let cursor = selections.oldest_anchor().head(); - selections.select_anchor_ranges([cursor..cursor]); - }); + title_editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |selections| { + let cursor = selections.oldest_anchor().head(); + selections.select_anchor_ranges([cursor..cursor]); + }, + ); }); } _ => {} @@ -920,10 +925,15 @@ impl RulesLibrary { } EditorEvent::Blurred => { body_editor.update(cx, |body_editor, cx| { - body_editor.change_selections(None, window, cx, |selections| { - let cursor = selections.oldest_anchor().head(); - selections.select_anchor_ranges([cursor..cursor]); - }); + body_editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |selections| { + let cursor = selections.oldest_anchor().head(); + selections.select_anchor_ranges([cursor..cursor]); + }, + ); }); } _ => {} diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index fa7a3ba915896d52f1d2f60f55d5ab13746edda8..715cb451ddc6b0ea662234bd99dfeb4ba876f767 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1540,7 +1540,10 @@ mod tests { use std::ops::Range; use super::*; - use editor::{DisplayPoint, Editor, MultiBuffer, SearchSettings, display_map::DisplayRow}; + use editor::{ + DisplayPoint, Editor, MultiBuffer, SearchSettings, SelectionEffects, + display_map::DisplayRow, + }; use gpui::{Hsla, TestAppContext, UpdateGlobal, VisualTestContext}; use language::{Buffer, Point}; use project::Project; @@ -1677,7 +1680,7 @@ mod tests { }); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0) ]) @@ -1764,7 +1767,7 @@ mod tests { // Park the cursor in between matches and ensure that going to the previous match selects // the closest match to the left. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0) ]) @@ -1785,7 +1788,7 @@ mod tests { // Park the cursor in between matches and ensure that going to the next match selects the // closest match to the right. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0) ]) @@ -1806,7 +1809,7 @@ mod tests { // Park the cursor after the last match and ensure that going to the previous match selects // the last match. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 60)..DisplayPoint::new(DisplayRow(3), 60) ]) @@ -1827,7 +1830,7 @@ mod tests { // Park the cursor after the last match and ensure that going to the next match selects the // first match. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 60)..DisplayPoint::new(DisplayRow(3), 60) ]) @@ -1848,7 +1851,7 @@ mod tests { // Park the cursor before the first match and ensure that going to the previous match // selects the last match. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0) ]) @@ -2625,7 +2628,7 @@ mod tests { }); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(vec![Point::new(1, 0)..Point::new(2, 4)]) }) }); @@ -2708,7 +2711,7 @@ mod tests { }); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(vec![ Point::new(1, 0)..Point::new(1, 4), Point::new(5, 3)..Point::new(6, 4), diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 8e1ea3d7733cd18412b1330551301864df981ec8..fd2cc3a1ced907921698081c8c124c8132ba3692 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -7,7 +7,7 @@ use anyhow::Context as _; use collections::{HashMap, HashSet}; use editor::{ Anchor, Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MAX_TAB_TITLE_LEN, - MultiBuffer, actions::SelectAll, items::active_match_index, scroll::Autoscroll, + MultiBuffer, SelectionEffects, actions::SelectAll, items::active_match_index, }; use futures::{StreamExt, stream::FuturesOrdered}; use gpui::{ @@ -1303,7 +1303,7 @@ impl ProjectSearchView { self.results_editor.update(cx, |editor, cx| { let range_to_select = editor.range_for_match(&range_to_select); editor.unfold_ranges(std::slice::from_ref(&range_to_select), false, true, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges([range_to_select]) }); }); @@ -1350,7 +1350,9 @@ impl ProjectSearchView { fn focus_results_editor(&mut self, window: &mut Window, cx: &mut Context) { self.query_editor.update(cx, |query_editor, cx| { let cursor = query_editor.selections.newest_anchor().head(); - query_editor.change_selections(None, window, cx, |s| s.select_ranges([cursor..cursor])); + query_editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([cursor..cursor]) + }); }); let results_handle = self.results_editor.focus_handle(cx); window.focus(&results_handle); @@ -1370,7 +1372,7 @@ impl ProjectSearchView { let range_to_select = match_ranges .first() .map(|range| editor.range_for_match(range)); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(range_to_select) }); editor.scroll(Point::default(), Some(Axis::Vertical), window, cx); diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index d3b8d927b3cf9114bc341b795f31e1ee4ad8e6b7..1510f613e34ef7bfc78bbfad23b7843787432491 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -751,7 +751,7 @@ fn string_match_candidates<'a>( mod tests { use std::{path::PathBuf, sync::Arc}; - use editor::Editor; + use editor::{Editor, SelectionEffects}; use gpui::{TestAppContext, VisualTestContext}; use language::{Language, LanguageConfig, LanguageMatcher, Point}; use project::{ContextProviderWithTasks, FakeFs, Project}; @@ -1028,7 +1028,7 @@ mod tests { .update(|_window, cx| second_item.act_as::(cx)) .unwrap(); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(1, 2)..Point::new(1, 5))) }) }); diff --git a/crates/tasks_ui/src/tasks_ui.rs b/crates/tasks_ui/src/tasks_ui.rs index acdc7d0298490b2765b828c5bc468796deb6b3c3..0b3f70e6bcc5402bae3af09effb5bebc1a574977 100644 --- a/crates/tasks_ui/src/tasks_ui.rs +++ b/crates/tasks_ui/src/tasks_ui.rs @@ -393,7 +393,7 @@ fn worktree_context(worktree_abs_path: &Path) -> TaskContext { mod tests { use std::{collections::HashMap, sync::Arc}; - use editor::Editor; + use editor::{Editor, SelectionEffects}; use gpui::TestAppContext; use language::{Language, LanguageConfig}; use project::{BasicContextProvider, FakeFs, Project, task_store::TaskStore}; @@ -538,7 +538,7 @@ mod tests { // And now, let's select an identifier. editor2.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges([14..18]) }) }); diff --git a/crates/vim/src/change_list.rs b/crates/vim/src/change_list.rs index 3332239631ae836111fe34431e807a21381b970f..25da3e09b8f6115273176cdb74e10e52aaeb951c 100644 --- a/crates/vim/src/change_list.rs +++ b/crates/vim/src/change_list.rs @@ -1,4 +1,4 @@ -use editor::{Bias, Direction, Editor, display_map::ToDisplayPoint, movement, scroll::Autoscroll}; +use editor::{Bias, Direction, Editor, display_map::ToDisplayPoint, movement}; use gpui::{Context, Window, actions}; use crate::{Vim, state::Mode}; @@ -29,7 +29,7 @@ impl Vim { .next_change(count, direction) .map(|s| s.to_vec()) { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let map = s.display_map(); s.select_display_ranges(selections.iter().map(|a| { let point = a.to_display_point(&map); diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 40e8fcffa3c90be95f1421548a19c3a1a444035c..839a0392d4d3b18edb6449b15c9a310c387c5ad7 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -2,10 +2,9 @@ use anyhow::Result; use collections::{HashMap, HashSet}; use command_palette_hooks::CommandInterceptResult; use editor::{ - Bias, Editor, ToPoint, + Bias, Editor, SelectionEffects, ToPoint, actions::{SortLinesCaseInsensitive, SortLinesCaseSensitive}, display_map::ToDisplayPoint, - scroll::Autoscroll, }; use gpui::{Action, App, AppContext as _, Context, Global, Window, actions}; use itertools::Itertools; @@ -422,7 +421,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { let target = snapshot .buffer_snapshot .clip_point(Point::new(buffer_row.0, current.head().column), Bias::Left); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges([target..target]); }); @@ -493,7 +492,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { .disjoint_anchor_ranges() .collect::>() }); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { let end = Point::new(range.end.0, s.buffer().line_len(range.end)); s.select_ranges([end..Point::new(range.start.0, 0)]); }); @@ -503,7 +502,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { window.dispatch_action(action.action.boxed_clone(), cx); cx.defer_in(window, move |vim, window, cx| { vim.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { if let Some(previous_selections) = previous_selections { s.select_ranges(previous_selections); } else { @@ -1455,15 +1454,20 @@ impl OnMatchingLines { editor .update_in(cx, |editor, window, cx| { editor.start_transaction_at(Instant::now(), window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.replace_cursors_with(|_| new_selections); }); window.dispatch_action(action, cx); cx.defer_in(window, move |editor, window, cx| { let newest = editor.selections.newest::(cx).clone(); - editor.change_selections(None, window, cx, |s| { - s.select(vec![newest]); - }); + editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |s| { + s.select(vec![newest]); + }, + ); editor.end_transaction_at(Instant::now(), cx); }) }) @@ -1566,7 +1570,7 @@ impl Vim { ) .unwrap_or((start.range(), MotionKind::Exclusive)); if range.start != start.start { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ range.start.to_point(&snapshot)..range.start.to_point(&snapshot) ]); @@ -1606,7 +1610,7 @@ impl Vim { .range(&snapshot, start.clone(), around) .unwrap_or(start.range()); if range.start != start.start { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ range.start.to_point(&snapshot)..range.start.to_point(&snapshot) ]); @@ -1799,7 +1803,7 @@ impl ShellExec { editor.transact(window, cx, |editor, window, cx| { editor.edit([(range.clone(), text)], cx); let snapshot = editor.buffer().read(cx).snapshot(cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let point = if is_read { let point = range.end.to_point(&snapshot); Point::new(point.row.saturating_sub(1), 0) diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index d5312934e477d2d5ddea089695a5055858cd391b..d0bbf5f17f3bf39dd1a7d02d0b54d2512a32e913 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -1,4 +1,4 @@ -use editor::{DisplayPoint, Editor, movement, scroll::Autoscroll}; +use editor::{DisplayPoint, Editor, movement}; use gpui::{Action, actions}; use gpui::{Context, Window}; use language::{CharClassifier, CharKind}; @@ -47,7 +47,7 @@ impl Vim { mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool, ) { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let times = times.unwrap_or(1); let new_goal = SelectionGoal::None; @@ -100,7 +100,7 @@ impl Vim { mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool, ) { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let times = times.unwrap_or(1); let new_goal = SelectionGoal::None; @@ -161,7 +161,7 @@ impl Vim { ) { self.update_editor(window, cx, |_, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let goal = selection.goal; let cursor = if selection.is_empty() || selection.reversed { @@ -239,7 +239,7 @@ impl Vim { Motion::FindForward { .. } => { self.update_editor(window, cx, |_, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let goal = selection.goal; let cursor = if selection.is_empty() || selection.reversed { @@ -266,7 +266,7 @@ impl Vim { Motion::FindBackward { .. } => { self.update_editor(window, cx, |_, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let goal = selection.goal; let cursor = if selection.is_empty() || selection.reversed { diff --git a/crates/vim/src/indent.rs b/crates/vim/src/indent.rs index ac708a7e8932f98502a2b969fa9ca68153765e8b..c8762c563a63479b6f187d3d7d0648ee2d2a92be 100644 --- a/crates/vim/src/indent.rs +++ b/crates/vim/src/indent.rs @@ -1,5 +1,6 @@ use crate::{Vim, motion::Motion, object::Object, state::Mode}; use collections::HashMap; +use editor::SelectionEffects; use editor::{Bias, Editor, display_map::ToDisplayPoint}; use gpui::actions; use gpui::{Context, Window}; @@ -88,7 +89,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); selection_starts.insert(selection.id, anchor); @@ -106,7 +107,7 @@ impl Vim { IndentDirection::Out => editor.outdent(&Default::default(), window, cx), IndentDirection::Auto => editor.autoindent(&Default::default(), window, cx), } - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = selection_starts.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); @@ -128,7 +129,7 @@ impl Vim { self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { let mut original_positions: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); original_positions.insert(selection.id, anchor); @@ -140,7 +141,7 @@ impl Vim { IndentDirection::Out => editor.outdent(&Default::default(), window, cx), IndentDirection::Auto => editor.autoindent(&Default::default(), window, cx), } - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = original_positions.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); diff --git a/crates/vim/src/insert.rs b/crates/vim/src/insert.rs index a30af8769fac99ac1d1b8c131b32e8c440e0b180..7b38bed2be087085bf66e632c027af7aa858e6f3 100644 --- a/crates/vim/src/insert.rs +++ b/crates/vim/src/insert.rs @@ -1,5 +1,5 @@ use crate::{Vim, state::Mode}; -use editor::{Bias, Editor, scroll::Autoscroll}; +use editor::{Bias, Editor}; use gpui::{Action, Context, Window, actions}; use language::SelectionGoal; use settings::Settings; @@ -34,7 +34,7 @@ impl Vim { editor.dismiss_menus_and_popups(false, window, cx); if !HelixModeSetting::get_global(cx).0 { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, mut cursor, _| { *cursor.column_mut() = cursor.column().saturating_sub(1); (map.clip_point(cursor, Bias::Left), SelectionGoal::None) diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index e9b01f5a674f8736b0379ca20d8907e1ac3782c6..2a6e5196bc01da9f8e6f3b6e12a9e0690757580f 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -4,7 +4,6 @@ use editor::{ movement::{ self, FindRange, TextLayoutDetails, find_boundary, find_preceding_boundary_display_point, }, - scroll::Autoscroll, }; use gpui::{Action, Context, Window, actions, px}; use language::{CharKind, Point, Selection, SelectionGoal}; @@ -626,7 +625,7 @@ impl Vim { Mode::Visual | Mode::VisualLine | Mode::VisualBlock => { if !prior_selections.is_empty() { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(prior_selections.iter().cloned()) }) }); diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 1d70227e0ba8791ebe6ebecd6e1202eae44d91db..2003c8b754613ffd288fac6166d20c700f3d1884 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -26,7 +26,6 @@ use collections::BTreeSet; use convert::ConvertTarget; use editor::Bias; use editor::Editor; -use editor::scroll::Autoscroll; use editor::{Anchor, SelectionEffects}; use editor::{display_map::ToDisplayPoint, movement}; use gpui::{Context, Window, actions}; @@ -103,7 +102,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, |vim, _: &HelixDelete, window, cx| { vim.record_current_action(cx); vim.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { selection.end = movement::right(map, selection.end) @@ -377,7 +376,7 @@ impl Vim { self.start_recording(cx); self.switch_mode(Mode::Insert, false, window, cx); self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, cursor, _| (right(map, cursor, 1), SelectionGoal::None)); }); }); @@ -388,7 +387,7 @@ impl Vim { if self.mode.is_visual() { let current_mode = self.mode; self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if current_mode == Mode::VisualLine { let start_of_line = motion::start_of_line(map, false, selection.start); @@ -412,7 +411,7 @@ impl Vim { self.start_recording(cx); self.switch_mode(Mode::Insert, false, window, cx); self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { ( first_non_whitespace(map, false, cursor), @@ -432,7 +431,7 @@ impl Vim { self.start_recording(cx); self.switch_mode(Mode::Insert, false, window, cx); self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { (next_line_end(map, cursor, 1), SelectionGoal::None) }); @@ -453,7 +452,7 @@ impl Vim { return; }; - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges(marks.iter().map(|mark| *mark..*mark)) }); }); @@ -489,7 +488,7 @@ impl Vim { }) .collect::>(); editor.edit_with_autoindent(edits, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { let previous_line = motion::start_of_relative_buffer_row(map, cursor, -1); let insert_point = motion::end_of_line(map, false, previous_line, 1); @@ -530,7 +529,7 @@ impl Vim { (end_of_line..end_of_line, "\n".to_string() + &indent) }) .collect::>(); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.maybe_move_cursors_with(|map, cursor, goal| { Motion::CurrentLine.move_point( map, @@ -607,7 +606,7 @@ impl Vim { .collect::>(); editor.edit(edits, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|_, selection| { if let Some(position) = original_positions.get(&selection.id) { selection.collapse_to(*position, SelectionGoal::None); @@ -755,7 +754,7 @@ impl Vim { editor.newline(&editor::actions::Newline, window, cx); } editor.set_clip_at_line_ends(true, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let point = movement::saturating_left(map, selection.head()); selection.collapse_to(point, SelectionGoal::None) @@ -791,7 +790,7 @@ impl Vim { cx: &mut Context, mut positions: HashMap, ) { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if let Some(anchor) = positions.remove(&selection.id) { selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index e6ecf309f198891ba05370a9270d52978c73ea52..da8d38ea13518945b4ba7ca5c416477b99a05b6e 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -8,7 +8,6 @@ use editor::{ Bias, DisplayPoint, display_map::{DisplaySnapshot, ToDisplayPoint}, movement::TextLayoutDetails, - scroll::Autoscroll, }; use gpui::{Context, Window}; use language::Selection; @@ -40,7 +39,7 @@ impl Vim { editor.transact(window, cx, |editor, window, cx| { // We are swapping to insert mode anyway. Just set the line end clipping behavior now editor.set_clip_at_line_ends(false, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let kind = match motion { Motion::NextWordStart { ignore_punctuation } @@ -114,7 +113,7 @@ impl Vim { // We are swapping to insert mode anyway. Just set the line end clipping behavior now editor.set_clip_at_line_ends(false, cx); editor.transact(window, cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { objects_found |= object.expand_selection(map, selection, around); }); diff --git a/crates/vim/src/normal/convert.rs b/crates/vim/src/normal/convert.rs index 5295e79edb4c08c1b7ee869d0014168df2f40787..4621e3ab896c0e487d9e05323e362642d684573a 100644 --- a/crates/vim/src/normal/convert.rs +++ b/crates/vim/src/normal/convert.rs @@ -1,5 +1,5 @@ use collections::HashMap; -use editor::{display_map::ToDisplayPoint, scroll::Autoscroll}; +use editor::{SelectionEffects, display_map::ToDisplayPoint}; use gpui::{Context, Window}; use language::{Bias, Point, SelectionGoal}; use multi_buffer::MultiBufferRow; @@ -36,7 +36,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Left); selection_starts.insert(selection.id, anchor); @@ -66,7 +66,7 @@ impl Vim { editor.convert_to_rot47(&Default::default(), window, cx) } } - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = selection_starts.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); @@ -90,7 +90,7 @@ impl Vim { editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); let mut original_positions: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around); original_positions.insert( @@ -116,7 +116,7 @@ impl Vim { editor.convert_to_rot47(&Default::default(), window, cx) } } - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = original_positions.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); @@ -239,7 +239,7 @@ impl Vim { .collect::(); editor.edit([(range, text)], cx) } - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(cursor_positions) }) }); diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index f52d9bebe05d517a5dda8d8080d47a9588c9ed9d..141346c99fcdc1f155e8628596c3e6805f5086aa 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -7,7 +7,6 @@ use collections::{HashMap, HashSet}; use editor::{ Bias, DisplayPoint, display_map::{DisplaySnapshot, ToDisplayPoint}, - scroll::Autoscroll, }; use gpui::{Context, Window}; use language::{Point, Selection}; @@ -30,7 +29,7 @@ impl Vim { let mut original_columns: HashMap<_, _> = Default::default(); let mut motion_kind = None; let mut ranges_to_copy = Vec::new(); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let original_head = selection.head(); original_columns.insert(selection.id, original_head.column()); @@ -71,7 +70,7 @@ impl Vim { // Fixup cursor position after the deletion editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let mut cursor = selection.head(); if kind.linewise() { @@ -102,7 +101,7 @@ impl Vim { // Emulates behavior in vim where if we expanded backwards to include a newline // the cursor gets set back to the start of the line let mut should_move_to_start: HashSet<_> = Default::default(); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around); let offset_range = selection.map(|p| p.to_offset(map, Bias::Left)).range(); @@ -159,7 +158,7 @@ impl Vim { // Fixup cursor position after the deletion editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let mut cursor = selection.head(); if should_move_to_start.contains(&selection.id) { diff --git a/crates/vim/src/normal/increment.rs b/crates/vim/src/normal/increment.rs index e2a0d282673a6f1ccb96d7c0a2d63f55d3dd78c1..09e6e85a5ccd057111dddca9e1bc76ebfacc1b63 100644 --- a/crates/vim/src/normal/increment.rs +++ b/crates/vim/src/normal/increment.rs @@ -1,4 +1,4 @@ -use editor::{Editor, MultiBufferSnapshot, ToOffset, ToPoint, scroll::Autoscroll}; +use editor::{Editor, MultiBufferSnapshot, ToOffset, ToPoint}; use gpui::{Action, Context, Window}; use language::{Bias, Point}; use schemars::JsonSchema; @@ -97,7 +97,7 @@ impl Vim { editor.edit(edits, cx); let snapshot = editor.buffer().read(cx).snapshot(cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let mut new_ranges = Vec::new(); for (visual, anchor) in new_anchors.iter() { let mut point = anchor.to_point(&snapshot); diff --git a/crates/vim/src/normal/mark.rs b/crates/vim/src/normal/mark.rs index af4b71f4278a35a1e6462d833d46a247f025fda4..57a6108841e49d0461ff343e000969839287d6c7 100644 --- a/crates/vim/src/normal/mark.rs +++ b/crates/vim/src/normal/mark.rs @@ -4,7 +4,6 @@ use editor::{ Anchor, Bias, DisplayPoint, Editor, MultiBuffer, display_map::{DisplaySnapshot, ToDisplayPoint}, movement, - scroll::Autoscroll, }; use gpui::{Context, Entity, EntityId, UpdateGlobal, Window}; use language::SelectionGoal; @@ -116,7 +115,7 @@ impl Vim { } } - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges(ranges) }); }) @@ -169,7 +168,7 @@ impl Vim { } }) .collect(); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(points.into_iter().map(|p| p..p)) }) }) @@ -251,7 +250,7 @@ impl Vim { } if !should_jump && !ranges.is_empty() { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges(ranges) }); } diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 41337f07074e56e17b35bc72addf3c0ce3ae0f39..0dade838f5d5edbdca89dcea945da16a9fc89c63 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -1,4 +1,4 @@ -use editor::{DisplayPoint, RowExt, display_map::ToDisplayPoint, movement, scroll::Autoscroll}; +use editor::{DisplayPoint, RowExt, SelectionEffects, display_map::ToDisplayPoint, movement}; use gpui::{Action, Context, Window}; use language::{Bias, SelectionGoal}; use schemars::JsonSchema; @@ -187,7 +187,7 @@ impl Vim { // and put the cursor on the first non-blank character of the first inserted line (or at the end if the first line is blank). // otherwise vim will insert the next text at (or before) the current cursor position, // the cursor will go to the last (or first, if is_multiline) inserted character. - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.replace_cursors_with(|map| { let mut cursors = Vec::new(); for (anchor, line_mode, is_multiline) in &new_selections { @@ -238,7 +238,7 @@ impl Vim { self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around); }); @@ -252,7 +252,7 @@ impl Vim { }; editor.insert(&text, window, cx); editor.set_clip_at_line_ends(true, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { selection.start = map.clip_point(selection.start, Bias::Left); selection.end = selection.start @@ -276,7 +276,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { motion.expand_selection( map, @@ -296,7 +296,7 @@ impl Vim { }; editor.insert(&text, window, cx); editor.set_clip_at_line_ends(true, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { selection.start = map.clip_point(selection.start, Bias::Left); selection.end = selection.start diff --git a/crates/vim/src/normal/substitute.rs b/crates/vim/src/normal/substitute.rs index 1199356995df9be3e8425d7c7d3ad0f1ae4c76b7..96df61e528d3df3a480b978c78154d8c0c3a0150 100644 --- a/crates/vim/src/normal/substitute.rs +++ b/crates/vim/src/normal/substitute.rs @@ -1,4 +1,4 @@ -use editor::{Editor, movement}; +use editor::{Editor, SelectionEffects, movement}; use gpui::{Context, Window, actions}; use language::Point; @@ -41,7 +41,7 @@ impl Vim { editor.set_clip_at_line_ends(false, cx); editor.transact(window, cx, |editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { if selection.start == selection.end { Motion::Right.expand_selection( diff --git a/crates/vim/src/normal/toggle_comments.rs b/crates/vim/src/normal/toggle_comments.rs index 1df381acbeea2fdc9cc691ebadcc4a429f7745ec..3b578c44cbed080758e5598bc910ed5431ade956 100644 --- a/crates/vim/src/normal/toggle_comments.rs +++ b/crates/vim/src/normal/toggle_comments.rs @@ -1,6 +1,6 @@ use crate::{Vim, motion::Motion, object::Object}; use collections::HashMap; -use editor::{Bias, display_map::ToDisplayPoint}; +use editor::{Bias, SelectionEffects, display_map::ToDisplayPoint}; use gpui::{Context, Window}; use language::SelectionGoal; @@ -18,7 +18,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); selection_starts.insert(selection.id, anchor); @@ -32,7 +32,7 @@ impl Vim { }); }); editor.toggle_comments(&Default::default(), window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = selection_starts.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); @@ -53,7 +53,7 @@ impl Vim { self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { let mut original_positions: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); original_positions.insert(selection.id, anchor); @@ -61,7 +61,7 @@ impl Vim { }); }); editor.toggle_comments(&Default::default(), window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = original_positions.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); diff --git a/crates/vim/src/normal/yank.rs b/crates/vim/src/normal/yank.rs index 3525b0d43fbc215fe0d469e1536398177c925653..6beb81b2b6d09f2dcd696929b6858af50cb16f90 100644 --- a/crates/vim/src/normal/yank.rs +++ b/crates/vim/src/normal/yank.rs @@ -7,7 +7,7 @@ use crate::{ state::{Mode, Register}, }; use collections::HashMap; -use editor::{ClipboardSelection, Editor}; +use editor::{ClipboardSelection, Editor, SelectionEffects}; use gpui::Context; use gpui::Window; use language::Point; @@ -31,7 +31,7 @@ impl Vim { editor.set_clip_at_line_ends(false, cx); let mut original_positions: HashMap<_, _> = Default::default(); let mut kind = None; - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let original_position = (selection.head(), selection.goal); kind = motion.expand_selection( @@ -51,7 +51,7 @@ impl Vim { }); let Some(kind) = kind else { return }; vim.yank_selections_content(editor, kind, window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|_, selection| { let (head, goal) = original_positions.remove(&selection.id).unwrap(); selection.collapse_to(head, goal); @@ -73,7 +73,7 @@ impl Vim { editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); let mut start_positions: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around); let start_position = (selection.start, selection.goal); @@ -81,7 +81,7 @@ impl Vim { }); }); vim.yank_selections_content(editor, MotionKind::Exclusive, window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|_, selection| { let (head, goal) = start_positions.remove(&selection.id).unwrap(); selection.collapse_to(head, goal); diff --git a/crates/vim/src/replace.rs b/crates/vim/src/replace.rs index 5f407db5cb816a30aa83875d19e48bf4bb856473..bf0d977531e55565173d3164c15d11f18d31c360 100644 --- a/crates/vim/src/replace.rs +++ b/crates/vim/src/replace.rs @@ -5,8 +5,8 @@ use crate::{ state::Mode, }; use editor::{ - Anchor, Bias, Editor, EditorSnapshot, ToOffset, ToPoint, display_map::ToDisplayPoint, - scroll::Autoscroll, + Anchor, Bias, Editor, EditorSnapshot, SelectionEffects, ToOffset, ToPoint, + display_map::ToDisplayPoint, }; use gpui::{Context, Window, actions}; use language::{Point, SelectionGoal}; @@ -72,7 +72,7 @@ impl Vim { editor.edit_with_block_indent(edits.clone(), Vec::new(), cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_anchor_ranges(edits.iter().map(|(range, _)| range.end..range.end)); }); editor.set_clip_at_line_ends(true, cx); @@ -124,7 +124,7 @@ impl Vim { editor.edit(edits, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(new_selections); }); editor.set_clip_at_line_ends(true, cx); @@ -251,7 +251,7 @@ impl Vim { } if let Some(position) = final_cursor_position { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|_map, selection| { selection.collapse_to(position, SelectionGoal::None); }); diff --git a/crates/vim/src/rewrap.rs b/crates/vim/src/rewrap.rs index b5d69ef0ae73d87deb49dde9d852457d910be075..e03a3308fca52c6d11766ccb7731cbd6ec7883c4 100644 --- a/crates/vim/src/rewrap.rs +++ b/crates/vim/src/rewrap.rs @@ -1,6 +1,6 @@ use crate::{Vim, motion::Motion, object::Object, state::Mode}; use collections::HashMap; -use editor::{Bias, Editor, RewrapOptions, display_map::ToDisplayPoint, scroll::Autoscroll}; +use editor::{Bias, Editor, RewrapOptions, SelectionEffects, display_map::ToDisplayPoint}; use gpui::{Context, Window, actions}; use language::SelectionGoal; @@ -22,7 +22,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context) { }, cx, ); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if let Some(anchor) = positions.remove(&selection.id) { let mut point = anchor.to_display_point(map); @@ -53,7 +53,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); selection_starts.insert(selection.id, anchor); @@ -73,7 +73,7 @@ impl Vim { }, cx, ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = selection_starts.remove(&selection.id).unwrap(); let mut point = anchor.to_display_point(map); @@ -96,7 +96,7 @@ impl Vim { self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { let mut original_positions: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); original_positions.insert(selection.id, anchor); @@ -110,7 +110,7 @@ impl Vim { }, cx, ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = original_positions.remove(&selection.id).unwrap(); let mut point = anchor.to_display_point(map); diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index 6697742e4d318bb3a790e59e3404cf1f19a8c4ff..852433bc8e42ebe97d3b0f140139e20d9f8b4d6f 100644 --- a/crates/vim/src/surrounds.rs +++ b/crates/vim/src/surrounds.rs @@ -4,7 +4,7 @@ use crate::{ object::Object, state::Mode, }; -use editor::{Bias, movement, scroll::Autoscroll}; +use editor::{Bias, movement}; use gpui::{Context, Window}; use language::BracketPair; @@ -109,7 +109,7 @@ impl Vim { editor.edit(edits, cx); editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { if mode == Mode::VisualBlock { s.select_anchor_ranges(anchors.into_iter().take(1)) } else { @@ -207,7 +207,7 @@ impl Vim { } } - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(anchors); }); edits.sort_by_key(|(range, _)| range.start); @@ -317,7 +317,7 @@ impl Vim { edits.sort_by_key(|(range, _)| range.start); editor.edit(edits, cx); editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges(stable_anchors); }); }); @@ -375,7 +375,7 @@ impl Vim { anchors.push(start..start) } } - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(anchors); }); editor.set_clip_at_line_ends(true, cx); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 6b5d41f12ebf732781f6cb3234924c6ea48e92b5..2c2d60004e7aae6771906ff718c73b1dc0539723 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -22,7 +22,8 @@ mod visual; use anyhow::Result; use collections::HashMap; use editor::{ - Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, ToPoint, + Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, SelectionEffects, + ToPoint, movement::{self, FindRange}, }; use gpui::{ @@ -963,7 +964,7 @@ impl Vim { } } - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { // we cheat with visual block mode and use multiple cursors. // the cost of this cheat is we need to convert back to a single // cursor whenever vim would. @@ -1163,7 +1164,7 @@ impl Vim { } else { self.update_editor(window, cx, |_, editor, window, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|_, selection| { selection.collapse_to(selection.start, selection.goal) }) @@ -1438,27 +1439,29 @@ impl Vim { Mode::VisualLine | Mode::VisualBlock | Mode::Visual => { self.update_editor(window, cx, |vim, editor, window, cx| { let original_mode = vim.undo_modes.get(transaction_id); - editor.change_selections(None, window, cx, |s| match original_mode { - Some(Mode::VisualLine) => { - s.move_with(|map, selection| { - selection.collapse_to( - map.prev_line_boundary(selection.start.to_point(map)).1, - SelectionGoal::None, - ) - }); - } - Some(Mode::VisualBlock) => { - let mut first = s.first_anchor(); - first.collapse_to(first.start, first.goal); - s.select_anchors(vec![first]); - } - _ => { - s.move_with(|map, selection| { - selection.collapse_to( - map.clip_at_line_end(selection.start), - selection.goal, - ); - }); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + match original_mode { + Some(Mode::VisualLine) => { + s.move_with(|map, selection| { + selection.collapse_to( + map.prev_line_boundary(selection.start.to_point(map)).1, + SelectionGoal::None, + ) + }); + } + Some(Mode::VisualBlock) => { + let mut first = s.first_anchor(); + first.collapse_to(first.start, first.goal); + s.select_anchors(vec![first]); + } + _ => { + s.move_with(|map, selection| { + selection.collapse_to( + map.clip_at_line_end(selection.start), + selection.goal, + ); + }); + } } }); }); @@ -1466,7 +1469,7 @@ impl Vim { } Mode::Normal => { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { selection .collapse_to(map.clip_at_line_end(selection.end), selection.goal) diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 29ef3943b57086021844d8f65644fbe24e80392d..2d72881b7aed3894b48771fa7396ea8597f620e1 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -2,10 +2,9 @@ use std::sync::Arc; use collections::HashMap; use editor::{ - Bias, DisplayPoint, Editor, + Bias, DisplayPoint, Editor, SelectionEffects, display_map::{DisplaySnapshot, ToDisplayPoint}, movement, - scroll::Autoscroll, }; use gpui::{Context, Window, actions}; use language::{Point, Selection, SelectionGoal}; @@ -133,7 +132,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { vim.update_editor(window, cx, |_, editor, window, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let map = s.display_map(); let ranges = ranges .into_iter() @@ -187,7 +186,7 @@ impl Vim { motion.move_point(map, point, goal, times, &text_layout_details) }) } else { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let was_reversed = selection.reversed; let mut current_head = selection.head(); @@ -259,7 +258,7 @@ impl Vim { ) -> Option<(DisplayPoint, SelectionGoal)>, ) { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let map = &s.display_map(); let mut head = s.newest_anchor().head().to_display_point(map); let mut tail = s.oldest_anchor().tail().to_display_point(map); @@ -375,7 +374,7 @@ impl Vim { } self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let mut mut_selection = selection.clone(); @@ -454,7 +453,7 @@ impl Vim { ) { self.update_editor(window, cx, |_, editor, window, cx| { editor.split_selection_into_lines(&Default::default(), window, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { (next_line_end(map, cursor, 1), SelectionGoal::None) }); @@ -472,7 +471,7 @@ impl Vim { ) { self.update_editor(window, cx, |_, editor, window, cx| { editor.split_selection_into_lines(&Default::default(), window, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { ( first_non_whitespace(map, false, cursor), @@ -495,7 +494,7 @@ impl Vim { pub fn other_end(&mut self, _: &OtherEnd, window: &mut Window, cx: &mut Context) { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|_, selection| { selection.reversed = !selection.reversed; }); @@ -511,7 +510,7 @@ impl Vim { ) { let mode = self.mode; self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|_, selection| { selection.reversed = !selection.reversed; }); @@ -530,7 +529,7 @@ impl Vim { editor.selections.line_mode = false; editor.transact(window, cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if line_mode { let mut position = selection.head(); @@ -567,7 +566,7 @@ impl Vim { vim.copy_selections_content(editor, kind, window, cx); if line_mode && vim.mode != Mode::VisualBlock { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let end = selection.end.to_point(map); let start = selection.start.to_point(map); @@ -587,7 +586,7 @@ impl Vim { // Fixup cursor position after the deletion editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let mut cursor = selection.head().to_point(map); @@ -613,7 +612,7 @@ impl Vim { // For visual line mode, adjust selections to avoid yanking the next line when on \n if line_mode && vim.mode != Mode::VisualBlock { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let start = selection.start.to_point(map); let end = selection.end.to_point(map); @@ -634,7 +633,7 @@ impl Vim { MotionKind::Exclusive }; vim.yank_selections_content(editor, kind, window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { if line_mode { selection.start = start_of_line(map, false, selection.start); @@ -687,7 +686,9 @@ impl Vim { } editor.edit(edits, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges(stable_anchors)); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(stable_anchors) + }); }); }); self.switch_mode(Mode::Normal, false, window, cx); @@ -799,7 +800,7 @@ impl Vim { if direction == Direction::Prev { std::mem::swap(&mut start_selection, &mut end_selection); } - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges([start_selection..end_selection]); }); editor.set_collapse_matches(true); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2bbe3d0bcb6d119033b4fcc6ed6794faec914ca7..ea3f327ff07c54d0d2816947613859ed8bff2b1c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -18,7 +18,7 @@ use client::zed_urls; use collections::VecDeque; use debugger_ui::debugger_panel::DebugPanel; use editor::ProposedChangesEditorToolbar; -use editor::{Editor, MultiBuffer, scroll::Autoscroll}; +use editor::{Editor, MultiBuffer}; use futures::future::Either; use futures::{StreamExt, channel::mpsc, select_biased}; use git_ui::git_panel::GitPanel; @@ -1125,7 +1125,7 @@ fn open_log_file(workspace: &mut Workspace, window: &mut Window, cx: &mut Contex editor.update(cx, |editor, cx| { let last_multi_buffer_offset = editor.buffer().read(cx).len(cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(Some( last_multi_buffer_offset..last_multi_buffer_offset, )); @@ -1774,7 +1774,7 @@ mod tests { use super::*; use assets::Assets; use collections::HashSet; - use editor::{DisplayPoint, Editor, display_map::DisplayRow, scroll::Autoscroll}; + use editor::{DisplayPoint, Editor, SelectionEffects, display_map::DisplayRow}; use gpui::{ Action, AnyWindowHandle, App, AssetSource, BorrowAppContext, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowHandle, actions, @@ -3348,7 +3348,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(10), 0) ..DisplayPoint::new(DisplayRow(10), 0)]) }); @@ -3378,7 +3378,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor3.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(12), 0) ..DisplayPoint::new(DisplayRow(12), 0)]) }); @@ -3593,7 +3593,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(15), 0) ..DisplayPoint::new(DisplayRow(15), 0)]) }) @@ -3604,7 +3604,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(3), 0) ..DisplayPoint::new(DisplayRow(3), 0)]) }); @@ -3615,7 +3615,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(13), 0) ..DisplayPoint::new(DisplayRow(13), 0)]) }) @@ -3627,7 +3627,7 @@ mod tests { .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { editor.transact(window, cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(2), 0) ..DisplayPoint::new(DisplayRow(14), 0)]) }); @@ -3640,7 +3640,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(1), 0) ..DisplayPoint::new(DisplayRow(1), 0)]) }) From 6e762d9c05a8f5688b112c1e2dbfbf19c2ae4293 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 27 Jun 2025 14:06:17 -0600 Subject: [PATCH 033/107] Revert "Remove `into SelectionEffects` from .change_selections" This reverts commit 28380d714d6dd32f5d7e242d690483714fa3f969. --- crates/agent_ui/src/active_thread.rs | 24 +- crates/agent_ui/src/agent_diff.rs | 25 +- crates/agent_ui/src/inline_assistant.rs | 3 +- crates/agent_ui/src/text_thread_editor.rs | 13 +- crates/assistant_tools/src/edit_file_tool.rs | 4 +- .../collab/src/tests/channel_buffer_tests.rs | 10 +- crates/collab/src/tests/editor_tests.rs | 40 +- crates/collab/src/tests/following_tests.rs | 22 +- crates/collab_ui/src/channel_view.rs | 17 +- .../src/copilot_completion_provider.rs | 13 +- crates/debugger_ui/src/stack_trace_view.rs | 9 +- crates/diagnostics/src/diagnostic_renderer.rs | 3 +- crates/diagnostics/src/diagnostics.rs | 3 +- crates/editor/src/editor.rs | 381 ++++++++---------- crates/editor/src/editor_tests.rs | 278 ++++++------- crates/editor/src/element.rs | 18 +- crates/editor/src/hover_links.rs | 2 +- crates/editor/src/hover_popover.rs | 4 +- crates/editor/src/inlay_hint_cache.rs | 81 ++-- crates/editor/src/items.rs | 6 +- crates/editor/src/jsx_tag_auto_close.rs | 2 +- crates/editor/src/mouse_context_menu.rs | 6 +- crates/editor/src/proposed_changes_editor.rs | 6 +- crates/editor/src/test.rs | 6 +- crates/editor/src/test/editor_test_context.rs | 6 +- crates/git_ui/src/commit_view.rs | 4 +- crates/git_ui/src/project_diff.rs | 15 +- crates/go_to_line/src/go_to_line.rs | 13 +- .../src/inline_completion_button.rs | 13 +- crates/journal/src/journal.rs | 11 +- crates/language_tools/src/syntax_tree_view.rs | 4 +- .../src/markdown_preview_view.rs | 11 +- crates/outline/src/outline.rs | 11 +- crates/outline_panel/src/outline_panel.rs | 6 +- crates/picker/src/picker.rs | 11 +- crates/project_panel/src/project_panel.rs | 4 +- crates/project_symbols/src/project_symbols.rs | 11 +- crates/repl/src/session.rs | 3 +- crates/rules_library/src/rules_library.rs | 28 +- crates/search/src/buffer_search.rs | 21 +- crates/search/src/project_search.rs | 10 +- crates/tasks_ui/src/modal.rs | 4 +- crates/tasks_ui/src/tasks_ui.rs | 4 +- crates/vim/src/change_list.rs | 4 +- crates/vim/src/command.rs | 28 +- crates/vim/src/helix.rs | 12 +- crates/vim/src/indent.rs | 9 +- crates/vim/src/insert.rs | 4 +- crates/vim/src/motion.rs | 3 +- crates/vim/src/normal.rs | 23 +- crates/vim/src/normal/change.rs | 5 +- crates/vim/src/normal/convert.rs | 12 +- crates/vim/src/normal/delete.rs | 9 +- crates/vim/src/normal/increment.rs | 4 +- crates/vim/src/normal/mark.rs | 7 +- crates/vim/src/normal/paste.rs | 12 +- crates/vim/src/normal/substitute.rs | 4 +- crates/vim/src/normal/toggle_comments.rs | 10 +- crates/vim/src/normal/yank.rs | 10 +- crates/vim/src/replace.rs | 10 +- crates/vim/src/rewrap.rs | 12 +- crates/vim/src/surrounds.rs | 10 +- crates/vim/src/vim.rs | 53 ++- crates/vim/src/visual.rs | 35 +- crates/zed/src/zed.rs | 20 +- 65 files changed, 625 insertions(+), 837 deletions(-) diff --git a/crates/agent_ui/src/active_thread.rs b/crates/agent_ui/src/active_thread.rs index 7ee3b7158b6f9f8db6788c80f93123bd1ad463c6..5f9dfc7ab2ee844d7a8f4b6077861ff24e6d03cf 100644 --- a/crates/agent_ui/src/active_thread.rs +++ b/crates/agent_ui/src/active_thread.rs @@ -19,7 +19,7 @@ use audio::{Audio, Sound}; use collections::{HashMap, HashSet}; use editor::actions::{MoveUp, Paste}; use editor::scroll::Autoscroll; -use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, SelectionEffects}; +use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer}; use gpui::{ AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry, ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla, @@ -689,12 +689,9 @@ fn open_markdown_link( }) .context("Could not find matching symbol")?; - editor.change_selections( - SelectionEffects::scroll(Autoscroll::center()), - window, - cx, - |s| s.select_anchor_ranges([symbol_range.start..symbol_range.start]), - ); + editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { + s.select_anchor_ranges([symbol_range.start..symbol_range.start]) + }); anyhow::Ok(()) }) }) @@ -711,15 +708,10 @@ fn open_markdown_link( .downcast::() .context("Item is not an editor")?; active_editor.update_in(cx, |editor, window, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::center()), - window, - cx, - |s| { - s.select_ranges([Point::new(line_range.start as u32, 0) - ..Point::new(line_range.start as u32, 0)]) - }, - ); + editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { + s.select_ranges([Point::new(line_range.start as u32, 0) + ..Point::new(line_range.start as u32, 0)]) + }); anyhow::Ok(()) }) }) diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs index 1a0f3ff27d83a98d343985b3f827aab26afd192a..b8e67512e2b069f2a4f19c4903512f385c4eeab7 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -5,8 +5,7 @@ use anyhow::Result; use buffer_diff::DiffHunkStatus; use collections::{HashMap, HashSet}; use editor::{ - Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot, - SelectionEffects, ToPoint, + Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot, ToPoint, actions::{GoToHunk, GoToPreviousHunk}, scroll::Autoscroll, }; @@ -172,9 +171,15 @@ impl AgentDiffPane { if let Some(first_hunk) = first_hunk { let first_hunk_start = first_hunk.multi_buffer_range().start; - editor.change_selections(Default::default(), window, cx, |selections| { - selections.select_anchor_ranges([first_hunk_start..first_hunk_start]); - }) + editor.change_selections( + Some(Autoscroll::fit()), + window, + cx, + |selections| { + selections + .select_anchor_ranges([first_hunk_start..first_hunk_start]); + }, + ) } } @@ -237,7 +242,7 @@ impl AgentDiffPane { if let Some(first_hunk) = first_hunk { let first_hunk_start = first_hunk.multi_buffer_range().start; - editor.change_selections(Default::default(), window, cx, |selections| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { selections.select_anchor_ranges([first_hunk_start..first_hunk_start]); }) } @@ -411,7 +416,7 @@ fn update_editor_selection( }; if let Some(target_hunk) = target_hunk { - editor.change_selections(Default::default(), window, cx, |selections| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { let next_hunk_start = target_hunk.multi_buffer_range().start; selections.select_anchor_ranges([next_hunk_start..next_hunk_start]); }) @@ -1539,7 +1544,7 @@ impl AgentDiff { let first_hunk_start = first_hunk.multi_buffer_range().start; editor.change_selections( - SelectionEffects::scroll(Autoscroll::center()), + Some(Autoscroll::center()), window, cx, |selections| { @@ -1863,7 +1868,7 @@ mod tests { // Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) }); }); @@ -2119,7 +2124,7 @@ mod tests { // Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end. editor1.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) }); }); diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs index c9c173a68be5191e77690e826378ca52d3db9684..6e77e764a5ed172f0948d7d76f476377cafd04b7 100644 --- a/crates/agent_ui/src/inline_assistant.rs +++ b/crates/agent_ui/src/inline_assistant.rs @@ -18,7 +18,6 @@ use agent_settings::AgentSettings; use anyhow::{Context as _, Result}; use client::telemetry::Telemetry; use collections::{HashMap, HashSet, VecDeque, hash_map}; -use editor::SelectionEffects; use editor::{ Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint, @@ -1160,7 +1159,7 @@ impl InlineAssistant { let position = assist.range.start; editor.update(cx, |editor, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_anchor_ranges([position..position]) }); diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index dcb239a46ddec79d7aa52c4180cb511e8b74ac71..645bc451fcb8fbb91d05eb0bfe72814ea630c988 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -21,6 +21,7 @@ use editor::{ BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint, }, + scroll::Autoscroll, }; use editor::{FoldPlaceholder, display_map::CreaseId}; use fs::Fs; @@ -388,7 +389,7 @@ impl TextThreadEditor { cursor..cursor }; self.editor.update(cx, |editor, cx| { - editor.change_selections(Default::default(), window, cx, |selections| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { selections.select_ranges([new_selection]) }); }); @@ -448,7 +449,8 @@ impl TextThreadEditor { if let Some(command) = self.slash_commands.command(name, cx) { self.editor.update(cx, |editor, cx| { editor.transact(window, cx, |editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| s.try_cancel()); + editor + .change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel()); let snapshot = editor.buffer().read(cx).snapshot(cx); let newest_cursor = editor.selections.newest::(cx).head(); if newest_cursor.column > 0 @@ -1581,7 +1583,7 @@ impl TextThreadEditor { self.editor.update(cx, |editor, cx| { editor.transact(window, cx, |this, window, cx| { - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select(selections); }); this.insert("", window, cx); @@ -3139,7 +3141,6 @@ pub fn make_lsp_adapter_delegate( #[cfg(test)] mod tests { use super::*; - use editor::SelectionEffects; use fs::FakeFs; use gpui::{App, TestAppContext, VisualTestContext}; use indoc::indoc; @@ -3365,9 +3366,7 @@ mod tests { ) { context_editor.update_in(cx, |context_editor, window, cx| { context_editor.editor.update(cx, |editor, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([range]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([range])); }); context_editor.copy(&Default::default(), window, cx); diff --git a/crates/assistant_tools/src/edit_file_tool.rs b/crates/assistant_tools/src/edit_file_tool.rs index 8c7728b4b72c9aa52c717e58fbdd63591dd88f0f..fcf82856922c2e1c78345cc129aaea871a63ecfa 100644 --- a/crates/assistant_tools/src/edit_file_tool.rs +++ b/crates/assistant_tools/src/edit_file_tool.rs @@ -10,7 +10,7 @@ use assistant_tool::{ ToolUseStatus, }; use buffer_diff::{BufferDiff, BufferDiffSnapshot}; -use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey}; +use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey, scroll::Autoscroll}; use futures::StreamExt; use gpui::{ Animation, AnimationExt, AnyWindowHandle, App, AppContext, AsyncApp, Entity, Task, @@ -823,7 +823,7 @@ impl ToolCard for EditFileToolCard { let first_hunk_start = first_hunk.multi_buffer_range().start; editor.change_selections( - Default::default(), + Some(Autoscroll::fit()), window, cx, |selections| { diff --git a/crates/collab/src/tests/channel_buffer_tests.rs b/crates/collab/src/tests/channel_buffer_tests.rs index 0b331ff1e66279f5e2f5e52f9d83f0eaca6cfcdb..4069f61f90b48bfedfd4780f0865a061e4ab6971 100644 --- a/crates/collab/src/tests/channel_buffer_tests.rs +++ b/crates/collab/src/tests/channel_buffer_tests.rs @@ -178,7 +178,7 @@ async fn test_channel_notes_participant_indices( channel_view_a.update_in(cx_a, |notes, window, cx| { notes.editor.update(cx, |editor, cx| { editor.insert("a", window, cx); - editor.change_selections(Default::default(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges(vec![0..1]); }); }); @@ -188,7 +188,7 @@ async fn test_channel_notes_participant_indices( notes.editor.update(cx, |editor, cx| { editor.move_down(&Default::default(), window, cx); editor.insert("b", window, cx); - editor.change_selections(Default::default(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges(vec![1..2]); }); }); @@ -198,7 +198,7 @@ async fn test_channel_notes_participant_indices( notes.editor.update(cx, |editor, cx| { editor.move_down(&Default::default(), window, cx); editor.insert("c", window, cx); - editor.change_selections(Default::default(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges(vec![2..3]); }); }); @@ -273,12 +273,12 @@ async fn test_channel_notes_participant_indices( .unwrap(); editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges(vec![0..1]); }); }); editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges(vec![2..3]); }); }); diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 2cc3ca76d1b639cc479cb44cde93a73570d5eb7f..7a51caefa1c2f7f6a3e7f702ae9594b790760d7d 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -4,7 +4,7 @@ use crate::{ }; use call::ActiveCall; use editor::{ - DocumentColorsRenderMode, Editor, EditorSettings, RowInfo, SelectionEffects, + DocumentColorsRenderMode, Editor, EditorSettings, RowInfo, actions::{ ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo, @@ -348,9 +348,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu // Type a completion trigger character as the guest. editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([13..13]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); editor.handle_input(".", window, cx); }); cx_b.focus(&editor_b); @@ -463,9 +461,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu // Now we do a second completion, this time to ensure that documentation/snippets are // resolved editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([46..46]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([46..46])); editor.handle_input("; a", window, cx); editor.handle_input(".", window, cx); }); @@ -617,7 +613,7 @@ async fn test_collaborating_with_code_actions( // Move cursor to a location that contains code actions. editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(1, 31)..Point::new(1, 31)]) }); }); @@ -821,9 +817,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T // Move cursor to a location that can be renamed. let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([7..7]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([7..7])); editor.rename(&Rename, window, cx).unwrap() }); @@ -869,9 +863,7 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T editor.cancel(&editor::actions::Cancel, window, cx); }); let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([7..8]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([7..8])); editor.rename(&Rename, window, cx).unwrap() }); @@ -1372,9 +1364,7 @@ async fn test_on_input_format_from_host_to_guest( // Type a on type formatting trigger character as the guest. cx_a.focus(&editor_a); editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([13..13]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); editor.handle_input(">", window, cx); }); @@ -1470,9 +1460,7 @@ async fn test_on_input_format_from_guest_to_host( // Type a on type formatting trigger character as the guest. cx_b.focus(&editor_b); editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([13..13]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); editor.handle_input(":", window, cx); }); @@ -1709,9 +1697,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1; editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([13..13].clone()) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone())); editor.handle_input(":", window, cx); }); cx_b.focus(&editor_b); @@ -1732,9 +1718,7 @@ async fn test_mutual_editor_inlay_hint_cache_update( let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1; editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([13..13]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); editor.handle_input("a change to increment both buffers' versions", window, cx); }); cx_a.focus(&editor_a); @@ -2137,9 +2121,7 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo }); editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([13..13].clone()) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone())); editor.handle_input(":", window, cx); }); color_request_handle.next().await.unwrap(); diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index a77112213f195190e613c2382300bfbbeca70066..99f9b3350512f8d7eb126cb7a427979ab360d509 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -6,7 +6,7 @@ use collab_ui::{ channel_view::ChannelView, notifications::project_shared_notification::ProjectSharedNotification, }; -use editor::{Editor, MultiBuffer, PathKey, SelectionEffects}; +use editor::{Editor, MultiBuffer, PathKey}; use gpui::{ AppContext as _, BackgroundExecutor, BorrowAppContext, Entity, SharedString, TestAppContext, VisualContext, VisualTestContext, point, @@ -376,9 +376,7 @@ async fn test_basic_following( // Changes to client A's editor are reflected on client B. editor_a1.update_in(cx_a, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([1..1, 2..2]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2])); }); executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); executor.run_until_parked(); @@ -395,9 +393,7 @@ async fn test_basic_following( editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO")); editor_a1.update_in(cx_a, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([3..3]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([3..3])); editor.set_scroll_position(point(0., 100.), window, cx); }); executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); @@ -1651,9 +1647,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T // b should follow a to position 1 editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([1..1]) - }) + editor.change_selections(None, window, cx, |s| s.select_ranges([1..1])) }); cx_a.executor() .advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); @@ -1673,9 +1667,7 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T // b should not follow a to position 2 editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([2..2]) - }) + editor.change_selections(None, window, cx, |s| s.select_ranges([2..2])) }); cx_a.executor() .advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); @@ -1976,7 +1968,7 @@ async fn test_following_to_channel_notes_without_a_shared_project( assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); notes.editor.update(cx, |editor, cx| { editor.insert("Hello from A.", window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges(vec![3..4]); }); }); @@ -2117,7 +2109,7 @@ async fn test_following_after_replacement(cx_a: &mut TestAppContext, cx_b: &mut workspace.add_item_to_center(Box::new(editor.clone()) as _, window, cx) }); editor.update_in(cx_a, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::row_range(4..4)]); }) }); diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index c872f99aa10ee160ed499621d9aceb2aa7c06a05..80cc504308b30579d80e42e35e3267117a8bc456 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -7,8 +7,8 @@ use client::{ }; use collections::HashMap; use editor::{ - CollaborationHub, DisplayPoint, Editor, EditorEvent, SelectionEffects, - display_map::ToDisplayPoint, scroll::Autoscroll, + CollaborationHub, DisplayPoint, Editor, EditorEvent, display_map::ToDisplayPoint, + scroll::Autoscroll, }; use gpui::{ AnyView, App, ClipboardItem, Context, Entity, EventEmitter, Focusable, Pixels, Point, Render, @@ -260,16 +260,9 @@ impl ChannelView { .find(|item| &Channel::slug(&item.text).to_lowercase() == &position) { self.editor.update(cx, |editor, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::focused()), - window, - cx, - |s| { - s.replace_cursors_with(|map| { - vec![item.range.start.to_display_point(map)] - }) - }, - ) + editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| { + s.replace_cursors_with(|map| vec![item.range.start.to_display_point(map)]) + }) }); return; } diff --git a/crates/copilot/src/copilot_completion_provider.rs b/crates/copilot/src/copilot_completion_provider.rs index 8dc04622f9020c2fe175304764157b409c7936c1..ff636178753b11bbe3be920a27a27a5c467cef5e 100644 --- a/crates/copilot/src/copilot_completion_provider.rs +++ b/crates/copilot/src/copilot_completion_provider.rs @@ -264,8 +264,7 @@ fn common_prefix, T2: Iterator>(a: T1, b: mod tests { use super::*; use editor::{ - Editor, ExcerptRange, MultiBuffer, SelectionEffects, - test::editor_lsp_test_context::EditorLspTestContext, + Editor, ExcerptRange, MultiBuffer, test::editor_lsp_test_context::EditorLspTestContext, }; use fs::FakeFs; use futures::StreamExt; @@ -479,7 +478,7 @@ mod tests { // Reset the editor to verify how suggestions behave when tabbing on leading indentation. cx.update_editor(|editor, window, cx| { editor.set_text("fn foo() {\n \n}", window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(1, 2)..Point::new(1, 2)]) }); }); @@ -768,7 +767,7 @@ mod tests { ); _ = editor.update(cx, |editor, window, cx| { // Ensure copilot suggestions are shown for the first excerpt. - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(1, 5)..Point::new(1, 5)]) }); editor.next_edit_prediction(&Default::default(), window, cx); @@ -794,7 +793,7 @@ mod tests { ); _ = editor.update(cx, |editor, window, cx| { // Move to another excerpt, ensuring the suggestion gets cleared. - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(4, 5)..Point::new(4, 5)]) }); assert!(!editor.has_active_inline_completion()); @@ -1020,7 +1019,7 @@ mod tests { ); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)]) }); editor.refresh_inline_completion(true, false, window, cx); @@ -1030,7 +1029,7 @@ mod tests { assert!(copilot_requests.try_next().is_err()); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(5, 0)..Point::new(5, 0)]) }); editor.refresh_inline_completion(true, false, window, cx); diff --git a/crates/debugger_ui/src/stack_trace_view.rs b/crates/debugger_ui/src/stack_trace_view.rs index aef053df4a1ea930fb09a779e08afecfa08ddde9..675522e99996b276b5f62eeb88297dfe7d592579 100644 --- a/crates/debugger_ui/src/stack_trace_view.rs +++ b/crates/debugger_ui/src/stack_trace_view.rs @@ -4,7 +4,7 @@ use collections::HashMap; use dap::StackFrameId; use editor::{ Anchor, Bias, DebugStackFrameLine, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, - RowHighlightOptions, SelectionEffects, ToPoint, scroll::Autoscroll, + RowHighlightOptions, ToPoint, scroll::Autoscroll, }; use gpui::{ AnyView, App, AppContext, Entity, EventEmitter, Focusable, IntoElement, Render, SharedString, @@ -99,11 +99,10 @@ impl StackTraceView { if frame_anchor.excerpt_id != editor.selections.newest_anchor().head().excerpt_id { - let effects = SelectionEffects::scroll( - Autoscroll::center().for_anchor(frame_anchor), - ); + let auto_scroll = + Some(Autoscroll::center().for_anchor(frame_anchor)); - editor.change_selections(effects, window, cx, |selections| { + editor.change_selections(auto_scroll, window, cx, |selections| { let selection_id = selections.new_selection_id(); let selection = Selection { diff --git a/crates/diagnostics/src/diagnostic_renderer.rs b/crates/diagnostics/src/diagnostic_renderer.rs index 77bb249733f612ede3017e1cff592927b40e8d43..9524f97ff1e14599576df549844ee7c164d6d017 100644 --- a/crates/diagnostics/src/diagnostic_renderer.rs +++ b/crates/diagnostics/src/diagnostic_renderer.rs @@ -4,6 +4,7 @@ use editor::{ Anchor, Editor, EditorSnapshot, ToOffset, display_map::{BlockContext, BlockPlacement, BlockProperties, BlockStyle}, hover_popover::diagnostics_markdown_style, + scroll::Autoscroll, }; use gpui::{AppContext, Entity, Focusable, WeakEntity}; use language::{BufferId, Diagnostic, DiagnosticEntry}; @@ -310,7 +311,7 @@ impl DiagnosticBlock { let range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot); editor.unfold_ranges(&[range.start..range.end], true, false, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges([range.start..range.start]); }); window.focus(&editor.focus_handle(cx)); diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 8b49c536245a2509cb73254eca8de6d1be1cfd75..4f66a5a8839ddd8a3a2405a2b57114b73a1cf9f8 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -12,6 +12,7 @@ use diagnostic_renderer::DiagnosticBlock; use editor::{ DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey, display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId}, + scroll::Autoscroll, }; use futures::future::join_all; use gpui::{ @@ -625,7 +626,7 @@ impl ProjectDiagnosticsEditor { if let Some(anchor_range) = anchor_ranges.first() { let range_to_select = anchor_range.start..anchor_range.start; this.editor.update(cx, |editor, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_anchor_ranges([range_to_select]); }) }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 48ceaec18b40b5453901d804c8a06efae5b122b5..376aa60ba42f275acbdb8fe5e1f59fdf1d7be711 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1262,19 +1262,6 @@ impl Default for SelectionHistoryMode { } #[derive(Debug)] -/// SelectionEffects controls the side-effects of updating the selection. -/// -/// The default behaviour does "what you mostly want": -/// - it pushes to the nav history if the cursor moved by >10 lines -/// - it re-triggers completion requests -/// - it scrolls to fit -/// -/// You might want to modify these behaviours. For example when doing a "jump" -/// like go to definition, we always want to add to nav history; but when scrolling -/// in vim mode we never do. -/// -/// Similarly, you might want to disable scrolling if you don't want the viewport to -/// move. pub struct SelectionEffects { nav_history: Option, completions: bool, @@ -3177,11 +3164,12 @@ impl Editor { /// effects of selection change occur at the end of the transaction. pub fn change_selections( &mut self, - effects: SelectionEffects, + effects: impl Into, window: &mut Window, cx: &mut Context, change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R, ) -> R { + let effects = effects.into(); if let Some(state) = &mut self.deferred_selection_effects_state { state.effects.scroll = effects.scroll.or(state.effects.scroll); state.effects.completions = effects.completions; @@ -3461,13 +3449,8 @@ impl Editor { }; let selections_count = self.selections.count(); - let effects = if auto_scroll { - SelectionEffects::default() - } else { - SelectionEffects::no_scroll() - }; - self.change_selections(effects, window, cx, |s| { + self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| { if let Some(point_to_delete) = point_to_delete { s.delete(point_to_delete); @@ -3505,18 +3488,13 @@ impl Editor { .buffer_snapshot .anchor_before(position.to_point(&display_map)); - self.change_selections( - SelectionEffects::scroll(Autoscroll::newest()), - window, - cx, - |s| { - s.clear_disjoint(); - s.set_pending_anchor_range( - pointer_position..pointer_position, - SelectMode::Character, - ); - }, - ); + self.change_selections(Some(Autoscroll::newest()), window, cx, |s| { + s.clear_disjoint(); + s.set_pending_anchor_range( + pointer_position..pointer_position, + SelectMode::Character, + ); + }); }; let tail = self.selections.newest::(cx).tail(); @@ -3631,7 +3609,7 @@ impl Editor { pending.reversed = false; } - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.set_pending(pending, mode); }); } else { @@ -3647,7 +3625,7 @@ impl Editor { self.columnar_selection_state.take(); if self.selections.pending_anchor().is_some() { let selections = self.selections.all::(cx); - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.select(selections); s.clear_pending(); }); @@ -3721,7 +3699,7 @@ impl Editor { _ => selection_ranges, }; - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.select_ranges(ranges); }); cx.notify(); @@ -3761,7 +3739,7 @@ impl Editor { } if self.mode.is_full() - && self.change_selections(Default::default(), window, cx, |s| s.try_cancel()) + && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel()) { return; } @@ -4564,7 +4542,9 @@ impl Editor { }) .collect(); - this.change_selections(Default::default(), window, cx, |s| s.select(new_selections)); + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.select(new_selections) + }); this.refresh_inline_completion(true, false, window, cx); }); } @@ -4593,7 +4573,7 @@ impl Editor { self.transact(window, cx, |editor, window, cx| { editor.edit(edits, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { let mut index = 0; s.move_cursors_with(|map, _, _| { let row = rows[index]; @@ -4655,7 +4635,7 @@ impl Editor { self.transact(window, cx, |editor, window, cx| { editor.edit(edits, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { let mut index = 0; s.move_cursors_with(|map, _, _| { let row = rows[index]; @@ -4732,7 +4712,7 @@ impl Editor { anchors }); - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_anchors(selection_anchors); }); @@ -4876,7 +4856,7 @@ impl Editor { .collect(); drop(buffer); - self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + self.change_selections(None, window, cx, |selections| { selections.select(new_selections) }); } @@ -7180,7 +7160,7 @@ impl Editor { self.unfold_ranges(&[target..target], true, false, cx); // Note that this is also done in vim's handler of the Tab action. self.change_selections( - SelectionEffects::scroll(Autoscroll::newest()), + Some(Autoscroll::newest()), window, cx, |selections| { @@ -7225,7 +7205,7 @@ impl Editor { buffer.edit(edits.iter().cloned(), None, cx) }); - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.select_anchor_ranges([last_edit_end..last_edit_end]); }); @@ -7272,14 +7252,9 @@ impl Editor { match &active_inline_completion.completion { InlineCompletion::Move { target, .. } => { let target = *target; - self.change_selections( - SelectionEffects::scroll(Autoscroll::newest()), - window, - cx, - |selections| { - selections.select_anchor_ranges([target..target]); - }, - ); + self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| { + selections.select_anchor_ranges([target..target]); + }); } InlineCompletion::Edit { edits, .. } => { // Find an insertion that starts at the cursor position. @@ -7880,12 +7855,9 @@ impl Editor { this.entry("Run to cursor", None, move |window, cx| { weak_editor .update(cx, |editor, cx| { - editor.change_selections( - SelectionEffects::no_scroll(), - window, - cx, - |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]), - ); + editor.change_selections(None, window, cx, |s| { + s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]) + }); }) .ok(); @@ -9426,7 +9398,7 @@ impl Editor { .collect::>() }); if let Some(tabstop) = tabstops.first() { - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { // Reverse order so that the first range is the newest created selection. // Completions will use it and autoscroll will prioritize it. s.select_ranges(tabstop.ranges.iter().rev().cloned()); @@ -9544,7 +9516,7 @@ impl Editor { } } if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) { - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { // Reverse order so that the first range is the newest created selection. // Completions will use it and autoscroll will prioritize it. s.select_ranges(current_ranges.iter().rev().cloned()) @@ -9634,7 +9606,9 @@ impl Editor { } } - this.change_selections(Default::default(), window, cx, |s| s.select(selections)); + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.select(selections) + }); this.insert("", window, cx); let empty_str: Arc = Arc::from(""); for (buffer, edits) in linked_ranges { @@ -9670,7 +9644,7 @@ impl Editor { pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = movement::right(map, selection.head()); @@ -9813,7 +9787,9 @@ impl Editor { self.transact(window, cx, |this, window, cx| { this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); - this.change_selections(Default::default(), window, cx, |s| s.select(selections)); + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.select(selections) + }); this.refresh_inline_completion(true, false, window, cx); }); } @@ -9846,7 +9822,9 @@ impl Editor { self.transact(window, cx, |this, window, cx| { this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); - this.change_selections(Default::default(), window, cx, |s| s.select(selections)); + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.select(selections) + }); }); } @@ -9999,7 +9977,9 @@ impl Editor { ); }); let selections = this.selections.all::(cx); - this.change_selections(Default::default(), window, cx, |s| s.select(selections)); + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.select(selections) + }); }); } @@ -10024,7 +10004,9 @@ impl Editor { buffer.autoindent_ranges(selections, cx); }); let selections = this.selections.all::(cx); - this.change_selections(Default::default(), window, cx, |s| s.select(selections)); + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.select(selections) + }); }); } @@ -10105,7 +10087,7 @@ impl Editor { }) .collect(); - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select(new_selections); }); }); @@ -10171,7 +10153,7 @@ impl Editor { } } - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_anchor_ranges(cursor_positions) }); }); @@ -10758,7 +10740,7 @@ impl Editor { }) .collect(); - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select(new_selections); }); @@ -11109,7 +11091,7 @@ impl Editor { buffer.edit(edits, None, cx); }); - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select(new_selections); }); @@ -11145,7 +11127,7 @@ impl Editor { this.buffer.update(cx, |buffer, cx| { buffer.edit(edits, None, cx); }); - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_anchor_ranges([last_edit_start..last_edit_end]); }); }); @@ -11347,7 +11329,7 @@ impl Editor { } }); this.fold_creases(refold_creases, true, window, cx); - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select(new_selections); }) }); @@ -11448,7 +11430,9 @@ impl Editor { } }); this.fold_creases(refold_creases, true, window, cx); - this.change_selections(Default::default(), window, cx, |s| s.select(new_selections)); + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.select(new_selections) + }); }); } @@ -11456,7 +11440,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); let text_layout_details = &self.text_layout_details(window); self.transact(window, cx, |this, window, cx| { - let edits = this.change_selections(Default::default(), window, cx, |s| { + let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { let mut edits: Vec<(Range, String)> = Default::default(); s.move_with(|display_map, selection| { if !selection.is_empty() { @@ -11504,7 +11488,7 @@ impl Editor { this.buffer .update(cx, |buffer, cx| buffer.edit(edits, None, cx)); let selections = this.selections.all::(cx); - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select(selections); }); }); @@ -11760,7 +11744,7 @@ impl Editor { } self.transact(window, cx, |this, window, cx| { - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select(selections); }); this.insert("", window, cx); @@ -11776,7 +11760,7 @@ impl Editor { pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.move_with(|snapshot, sel| { if sel.is_empty() { sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row())) @@ -11980,7 +11964,9 @@ impl Editor { }); let selections = this.selections.all::(cx); - this.change_selections(Default::default(), window, cx, |s| s.select(selections)); + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.select(selections) + }); } else { this.insert(&clipboard_text, window, cx); } @@ -12019,7 +12005,7 @@ impl Editor { if let Some((selections, _)) = self.selection_history.transaction(transaction_id).cloned() { - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.select_anchors(selections.to_vec()); }); } else { @@ -12049,7 +12035,7 @@ impl Editor { if let Some((_, Some(selections))) = self.selection_history.transaction(transaction_id).cloned() { - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.select_anchors(selections.to_vec()); }); } else { @@ -12079,7 +12065,7 @@ impl Editor { pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let cursor = if selection.is_empty() { movement::left(map, selection.start) @@ -12093,14 +12079,14 @@ impl Editor { pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None)); }) } pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let cursor = if selection.is_empty() { movement::right(map, selection.end) @@ -12114,7 +12100,7 @@ impl Editor { pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None)); }) } @@ -12135,7 +12121,7 @@ impl Editor { let selection_count = self.selections.count(); let first_selection = self.selections.first_anchor(); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12176,7 +12162,7 @@ impl Editor { let text_layout_details = &self.text_layout_details(window); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12213,7 +12199,7 @@ impl Editor { let text_layout_details = &self.text_layout_details(window); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12239,7 +12225,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let text_layout_details = &self.text_layout_details(window); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details) }) @@ -12254,7 +12240,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let text_layout_details = &self.text_layout_details(window); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details) }) @@ -12275,7 +12261,7 @@ impl Editor { let text_layout_details = &self.text_layout_details(window); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::up_by_rows(map, head, row_count, goal, false, text_layout_details) }) @@ -12313,15 +12299,15 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let effects = if action.center_cursor { - SelectionEffects::scroll(Autoscroll::center()) + let autoscroll = if action.center_cursor { + Autoscroll::center() } else { - SelectionEffects::default() + Autoscroll::fit() }; let text_layout_details = &self.text_layout_details(window); - self.change_selections(effects, window, cx, |s| { + self.change_selections(Some(autoscroll), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12342,7 +12328,7 @@ impl Editor { pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let text_layout_details = &self.text_layout_details(window); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::up(map, head, goal, false, text_layout_details) }) @@ -12363,7 +12349,7 @@ impl Editor { let selection_count = self.selections.count(); let first_selection = self.selections.first_anchor(); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12399,7 +12385,7 @@ impl Editor { let text_layout_details = &self.text_layout_details(window); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::down_by_rows(map, head, row_count, goal, false, text_layout_details) }) @@ -12437,14 +12423,14 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let effects = if action.center_cursor { - SelectionEffects::scroll(Autoscroll::center()) + let autoscroll = if action.center_cursor { + Autoscroll::center() } else { - SelectionEffects::default() + Autoscroll::fit() }; let text_layout_details = &self.text_layout_details(window); - self.change_selections(effects, window, cx, |s| { + self.change_selections(Some(autoscroll), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12465,7 +12451,7 @@ impl Editor { pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let text_layout_details = &self.text_layout_details(window); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::down(map, head, goal, false, text_layout_details) }) @@ -12523,7 +12509,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, head, _| { ( movement::previous_word_start(map, head), @@ -12540,7 +12526,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, head, _| { ( movement::previous_subword_start(map, head), @@ -12557,7 +12543,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::previous_word_start(map, head), @@ -12574,7 +12560,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::previous_subword_start(map, head), @@ -12593,7 +12579,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { this.select_autoclose_pair(window, cx); - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = if action.ignore_newlines { @@ -12618,7 +12604,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { this.select_autoclose_pair(window, cx); - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = movement::previous_subword_start(map, selection.head()); @@ -12637,7 +12623,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, head, _| { (movement::next_word_end(map, head), SelectionGoal::None) }); @@ -12651,7 +12637,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, head, _| { (movement::next_subword_end(map, head), SelectionGoal::None) }); @@ -12665,7 +12651,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| { (movement::next_word_end(map, head), SelectionGoal::None) }); @@ -12679,7 +12665,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| { (movement::next_subword_end(map, head), SelectionGoal::None) }); @@ -12694,7 +12680,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = if action.ignore_newlines { @@ -12718,7 +12704,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = movement::next_subword_end(map, selection.head()); @@ -12737,7 +12723,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, head, _| { ( movement::indented_line_beginning( @@ -12759,7 +12745,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::indented_line_beginning( @@ -12782,7 +12768,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|_, selection| { selection.reversed = true; }); @@ -12807,7 +12793,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, head, _| { ( movement::line_end(map, head, action.stop_at_soft_wraps), @@ -12824,7 +12810,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::line_end(map, head, action.stop_at_soft_wraps), @@ -12883,7 +12869,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::start_of_paragraph(map, selection.head(), 1), @@ -12904,7 +12890,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::end_of_paragraph(map, selection.head(), 1), @@ -12925,7 +12911,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::start_of_paragraph(map, head, 1), @@ -12946,7 +12932,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::end_of_paragraph(map, head, 1), @@ -12967,7 +12953,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::start_of_excerpt( @@ -12992,7 +12978,7 @@ impl Editor { return; } - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::start_of_excerpt( @@ -13017,7 +13003,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::end_of_excerpt( @@ -13042,7 +13028,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::end_of_excerpt( @@ -13067,7 +13053,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev), @@ -13088,7 +13074,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next), @@ -13109,7 +13095,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next), @@ -13130,7 +13116,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev), @@ -13151,7 +13137,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(vec![0..0]); }); } @@ -13165,7 +13151,7 @@ impl Editor { let mut selection = self.selections.last::(cx); selection.set_head(Point::zero(), SelectionGoal::None); self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select(vec![selection]); }); } @@ -13177,7 +13163,7 @@ impl Editor { } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let cursor = self.buffer.read(cx).read(cx).len(); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(vec![cursor..cursor]) }); } @@ -13243,7 +13229,7 @@ impl Editor { let buffer = self.buffer.read(cx).snapshot(cx); let mut selection = self.selections.first::(cx); selection.set_head(buffer.len(), SelectionGoal::None); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select(vec![selection]); }); } @@ -13251,7 +13237,7 @@ impl Editor { pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let end = self.buffer.read(cx).read(cx).len(); - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.select_ranges(vec![0..end]); }); } @@ -13267,7 +13253,7 @@ impl Editor { selection.end = cmp::min(max_point, Point::new(rows.end.0, 0)); selection.reversed = false; } - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select(selections); }); } @@ -13304,7 +13290,7 @@ impl Editor { } } } - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(new_selection_ranges); }); } @@ -13452,7 +13438,7 @@ impl Editor { } } - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select(final_selections); }); @@ -13490,12 +13476,7 @@ impl Editor { auto_scroll.is_some(), cx, ); - let effects = if let Some(scroll) = auto_scroll { - SelectionEffects::scroll(scroll) - } else { - SelectionEffects::no_scroll() - }; - self.change_selections(effects, window, cx, |s| { + self.change_selections(auto_scroll, window, cx, |s| { if replace_newest { s.delete(s.newest_anchor().id); } @@ -13707,7 +13688,7 @@ impl Editor { } self.unfold_ranges(&new_selections.clone(), false, false, cx); - self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + self.change_selections(None, window, cx, |selections| { selections.select_ranges(new_selections) }); @@ -13878,7 +13859,7 @@ impl Editor { let selections = self.selections.disjoint_anchors(); match selections.first() { Some(first) if selections.len() >= 2 => { - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges([first.range()]); }); } @@ -13902,7 +13883,7 @@ impl Editor { let selections = self.selections.disjoint_anchors(); match selections.last() { Some(last) if selections.len() >= 2 => { - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges([last.range()]); }); } @@ -14181,7 +14162,9 @@ impl Editor { } drop(snapshot); - this.change_selections(Default::default(), window, cx, |s| s.select(selections)); + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.select(selections) + }); let selections = this.selections.all::(cx); let selections_on_single_row = selections.windows(2).all(|selections| { @@ -14200,7 +14183,7 @@ impl Editor { if advance_downwards { let snapshot = this.buffer.read(cx).snapshot(cx); - this.change_selections(Default::default(), window, cx, |s| { + this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|display_snapshot, display_point, _| { let mut point = display_point.to_point(display_snapshot); point.row += 1; @@ -14267,7 +14250,7 @@ impl Editor { .collect::>(); if selected_larger_symbol { - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select(new_selections); }); } @@ -14367,7 +14350,7 @@ impl Editor { if selected_larger_node { self.select_syntax_node_history.disable_clearing = true; - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.select(new_selections.clone()); }); self.select_syntax_node_history.disable_clearing = false; @@ -14413,7 +14396,7 @@ impl Editor { } self.select_syntax_node_history.disable_clearing = true; - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.select(selections.to_vec()); }); self.select_syntax_node_history.disable_clearing = false; @@ -14678,7 +14661,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_offsets_with(|snapshot, selection| { let Some(enclosing_bracket_ranges) = snapshot.enclosing_bracket_ranges(selection.start..selection.end) @@ -14739,12 +14722,9 @@ impl Editor { self.selection_history.mode = SelectionHistoryMode::Undoing; self.with_selection_effects_deferred(window, cx, |this, window, cx| { this.end_selection(window, cx); - this.change_selections( - SelectionEffects::scroll(Autoscroll::newest()), - window, - cx, - |s| s.select_anchors(entry.selections.to_vec()), - ); + this.change_selections(Some(Autoscroll::newest()), window, cx, |s| { + s.select_anchors(entry.selections.to_vec()) + }); }); self.selection_history.mode = SelectionHistoryMode::Normal; @@ -14765,12 +14745,9 @@ impl Editor { self.selection_history.mode = SelectionHistoryMode::Redoing; self.with_selection_effects_deferred(window, cx, |this, window, cx| { this.end_selection(window, cx); - this.change_selections( - SelectionEffects::scroll(Autoscroll::newest()), - window, - cx, - |s| s.select_anchors(entry.selections.to_vec()), - ); + this.change_selections(Some(Autoscroll::newest()), window, cx, |s| { + s.select_anchors(entry.selections.to_vec()) + }); }); self.selection_history.mode = SelectionHistoryMode::Normal; @@ -15003,7 +14980,7 @@ impl Editor { let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else { return; }; - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(vec![ next_diagnostic.range.start..next_diagnostic.range.start, ]) @@ -15045,7 +15022,7 @@ impl Editor { let autoscroll = Autoscroll::center(); self.unfold_ranges(&[destination..destination], false, false, cx); - self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| { + self.change_selections(Some(autoscroll), window, cx, |s| { s.select_ranges([destination..destination]); }); } @@ -15108,7 +15085,7 @@ impl Editor { .next_change(1, Direction::Next) .map(|s| s.to_vec()) { - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { let map = s.display_map(); s.select_display_ranges(selections.iter().map(|a| { let point = a.to_display_point(&map); @@ -15129,7 +15106,7 @@ impl Editor { .next_change(1, Direction::Prev) .map(|s| s.to_vec()) { - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { let map = s.display_map(); s.select_display_ranges(selections.iter().map(|a| { let point = a.to_display_point(&map); @@ -15749,16 +15726,10 @@ impl Editor { match multibuffer_selection_mode { MultibufferSelectionMode::First => { if let Some(first_range) = ranges.first() { - editor.change_selections( - SelectionEffects::no_scroll(), - window, - cx, - |selections| { - selections.clear_disjoint(); - selections - .select_anchor_ranges(std::iter::once(first_range.clone())); - }, - ); + editor.change_selections(None, window, cx, |selections| { + selections.clear_disjoint(); + selections.select_anchor_ranges(std::iter::once(first_range.clone())); + }); } editor.highlight_background::( &ranges, @@ -15767,15 +15738,10 @@ impl Editor { ); } MultibufferSelectionMode::All => { - editor.change_selections( - SelectionEffects::no_scroll(), - window, - cx, - |selections| { - selections.clear_disjoint(); - selections.select_anchor_ranges(ranges); - }, - ); + editor.change_selections(None, window, cx, |selections| { + selections.clear_disjoint(); + selections.select_anchor_ranges(ranges); + }); } } editor.register_buffers_with_language_servers(cx); @@ -15909,7 +15875,7 @@ impl Editor { if rename_selection_range.end > old_name.len() { editor.select_all(&SelectAll, window, cx); } else { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges([rename_selection_range]); }); } @@ -16082,7 +16048,7 @@ impl Editor { .min(rename_range.end); drop(snapshot); - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.select_ranges(vec![cursor_in_editor..cursor_in_editor]) }); } else { @@ -16765,7 +16731,7 @@ impl Editor { pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context) { if self.selection_mark_mode { - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.move_with(|_, sel| { sel.collapse_to(sel.head(), SelectionGoal::None); }); @@ -16781,7 +16747,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.move_with(|_, sel| { if sel.start != sel.end { sel.reversed = !sel.reversed @@ -17520,7 +17486,7 @@ impl Editor { let autoscroll = Autoscroll::center(); self.unfold_ranges(&[destination..destination], false, false, cx); - self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| { + self.change_selections(Some(autoscroll), window, cx, |s| { s.select_ranges([destination..destination]); }); } @@ -20055,14 +20021,9 @@ impl Editor { None => Autoscroll::newest(), }; let nav_history = editor.nav_history.take(); - editor.change_selections( - SelectionEffects::scroll(autoscroll), - window, - cx, - |s| { - s.select_ranges(ranges); - }, - ); + editor.change_selections(Some(autoscroll), window, cx, |s| { + s.select_ranges(ranges); + }); editor.nav_history = nav_history; }); } @@ -20263,7 +20224,7 @@ impl Editor { } if let Some(relative_utf16_range) = relative_utf16_range { let selections = self.selections.all::(cx); - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { let new_ranges = selections.into_iter().map(|range| { let start = OffsetUtf16( range @@ -20406,7 +20367,7 @@ impl Editor { .iter() .map(|selection| (selection.end..selection.end, pending.clone())); this.edit(edits, cx); - this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + this.change_selections(None, window, cx, |s| { s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| { sel.start + ix * pending.len()..sel.end + ix * pending.len() })); @@ -20562,9 +20523,7 @@ impl Editor { } }) .detach(); - self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { - selections.refresh() - }); + self.change_selections(None, window, cx, |selections| selections.refresh()); } pub fn to_pixel_point( @@ -20689,7 +20648,7 @@ impl Editor { buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx)); // skip adding the initial selection to selection history self.selection_history.mode = SelectionHistoryMode::Skipping; - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.select_ranges(selections.into_iter().map(|(start, end)| { snapshot.clip_offset(start, Bias::Left) ..snapshot.clip_offset(end, Bias::Right) @@ -22503,7 +22462,7 @@ impl EntityInputHandler for Editor { }); if let Some(new_selected_ranges) = new_selected_ranges { - this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + this.change_selections(None, window, cx, |selections| { selections.select_ranges(new_selected_ranges) }); this.backspace(&Default::default(), window, cx); @@ -22578,9 +22537,7 @@ impl EntityInputHandler for Editor { }); if let Some(ranges) = ranges_to_replace { - this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges(ranges) - }); + this.change_selections(None, window, cx, |s| s.select_ranges(ranges)); } let marked_ranges = { @@ -22634,7 +22591,7 @@ impl EntityInputHandler for Editor { .collect::>(); drop(snapshot); - this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + this.change_selections(None, window, cx, |selections| { selections.select_ranges(new_selected_ranges) }); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 376effa91dce14f4703eec657d9fb6e04ae3d8d0..1ef2294d41d2815b2bfadb21257a0cc3132ebf3a 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -179,9 +179,7 @@ fn test_edit_events(cx: &mut TestAppContext) { // No event is emitted when the mutation is a no-op. _ = editor2.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([0..0]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([0..0])); editor.backspace(&Backspace, window, cx); }); @@ -204,9 +202,7 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { _ = editor.update(cx, |editor, window, cx| { editor.start_transaction_at(now, window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([2..4]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([2..4])); editor.insert("cd", window, cx); editor.end_transaction_at(now, cx); @@ -214,18 +210,14 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { assert_eq!(editor.selections.ranges(cx), vec![4..4]); editor.start_transaction_at(now, window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([4..5]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([4..5])); editor.insert("e", window, cx); editor.end_transaction_at(now, cx); assert_eq!(editor.text(cx), "12cde6"); assert_eq!(editor.selections.ranges(cx), vec![5..5]); now += group_interval + Duration::from_millis(1); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([2..2]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([2..2])); // Simulate an edit in another editor buffer.update(cx, |buffer, cx| { @@ -333,7 +325,7 @@ fn test_ime_composition(cx: &mut TestAppContext) { assert_eq!(editor.marked_text_ranges(cx), None); // Start a new IME composition with multiple cursors. - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([ OffsetUtf16(1)..OffsetUtf16(1), OffsetUtf16(3)..OffsetUtf16(3), @@ -631,7 +623,7 @@ fn test_clone(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(selection_ranges.clone()) }); editor.fold_creases( @@ -717,12 +709,12 @@ async fn test_navigation_history(cx: &mut TestAppContext) { // Move the cursor a small distance. // Nothing is added to the navigation history. - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0) ]) }); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0) ]) @@ -731,7 +723,7 @@ async fn test_navigation_history(cx: &mut TestAppContext) { // Move the cursor a large distance. // The history can jump back to the previous position. - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3) ]) @@ -901,7 +893,7 @@ fn test_fold_action(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0) ]); @@ -992,7 +984,7 @@ fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0) ]); @@ -1077,7 +1069,7 @@ fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0) ]); @@ -1309,7 +1301,7 @@ fn test_move_cursor(cx: &mut TestAppContext) { &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)] ); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2) ]); @@ -1454,7 +1446,7 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { build_editor(buffer.clone(), window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]); }); @@ -1544,7 +1536,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4), @@ -1739,7 +1731,7 @@ fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) { // First, let's assert behavior on the first line, that was not soft-wrapped. // Start the cursor at the `k` on the first line - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7) ]); @@ -1761,7 +1753,7 @@ fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) { // Now, let's assert behavior on the second line, that ended up being soft-wrapped. // Start the cursor at the last line (`y` that was wrapped to a new line) - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0) ]); @@ -1827,7 +1819,7 @@ fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4), @@ -1909,7 +1901,7 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11), DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4), @@ -1979,7 +1971,7 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { "use one::{\n two::three::\n four::five\n};" ); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7) ]); @@ -2242,7 +2234,7 @@ async fn test_autoscroll(cx: &mut TestAppContext) { // on screen, the editor autoscrolls to reveal the newest cursor, and // allows the vertical scroll margin below that cursor. cx.update_editor(|editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |selections| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { selections.select_ranges([ Point::new(0, 0)..Point::new(0, 0), Point::new(6, 0)..Point::new(6, 0), @@ -2270,7 +2262,7 @@ async fn test_autoscroll(cx: &mut TestAppContext) { // Add a cursor above the visible area. Since both cursors fit on screen, // the editor scrolls to show both. cx.update_editor(|editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |selections| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { selections.select_ranges([ Point::new(1, 0)..Point::new(1, 0), Point::new(6, 0)..Point::new(6, 0), @@ -2437,7 +2429,7 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ // an empty selection - the preceding word fragment is deleted DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -2456,7 +2448,7 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ // an empty selection - the following word fragment is deleted DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3), @@ -2491,7 +2483,7 @@ fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) { }; _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1) ]) @@ -2527,7 +2519,7 @@ fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) { }; _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0) ]) @@ -2566,7 +2558,7 @@ fn test_newline(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2), @@ -2599,7 +2591,7 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) { cx, ); let mut editor = build_editor(buffer.clone(), window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([ Point::new(2, 4)..Point::new(2, 5), Point::new(5, 4)..Point::new(5, 5), @@ -3086,7 +3078,7 @@ fn test_insert_with_old_selections(cx: &mut TestAppContext) { let editor = cx.add_window(|window, cx| { let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx); let mut editor = build_editor(buffer.clone(), window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([3..4, 11..12, 19..20]) }); editor @@ -3735,7 +3727,7 @@ fn test_delete_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1), @@ -3758,7 +3750,7 @@ fn test_delete_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1) ]) @@ -3795,7 +3787,7 @@ fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { ); // When multiple lines are selected, remove newlines that are spanned by the selection - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(0, 5)..Point::new(2, 2)]) }); editor.join_lines(&JoinLines, window, cx); @@ -3814,7 +3806,7 @@ fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { ); // When joining an empty line don't insert a space - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(2, 1)..Point::new(2, 2)]) }); editor.join_lines(&JoinLines, window, cx); @@ -3854,7 +3846,7 @@ fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { // We remove any leading spaces assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td"); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(0, 1)..Point::new(0, 1)]) }); editor.join_lines(&JoinLines, window, cx); @@ -3881,7 +3873,7 @@ fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) { let mut editor = build_editor(buffer.clone(), window, cx); let buffer = buffer.read(cx).as_singleton().unwrap(); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([ Point::new(0, 2)..Point::new(1, 1), Point::new(1, 2)..Point::new(1, 2), @@ -4721,7 +4713,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -4747,7 +4739,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1), DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1), @@ -4771,7 +4763,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -4797,7 +4789,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1), DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1), @@ -4819,7 +4811,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1), DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1), @@ -4856,7 +4848,7 @@ fn test_move_line_up_down(cx: &mut TestAppContext) { window, cx, ); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1), @@ -4959,7 +4951,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { Some(Autoscroll::fit()), cx, ); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(2, 0)..Point::new(2, 0)]) }); editor.move_line_down(&MoveLineDown, window, cx); @@ -5044,9 +5036,7 @@ fn test_transpose(cx: &mut TestAppContext) { _ = cx.add_window(|window, cx| { let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx); editor.set_style(EditorStyle::default(), window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([1..1]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([1..1])); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "bac"); assert_eq!(editor.selections.ranges(cx), [2..2]); @@ -5065,16 +5055,12 @@ fn test_transpose(cx: &mut TestAppContext) { _ = cx.add_window(|window, cx| { let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx); editor.set_style(EditorStyle::default(), window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([3..3]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([3..3])); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "acb\nde"); assert_eq!(editor.selections.ranges(cx), [3..3]); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([4..4]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([4..4])); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "acbd\ne"); assert_eq!(editor.selections.ranges(cx), [5..5]); @@ -5093,9 +5079,7 @@ fn test_transpose(cx: &mut TestAppContext) { _ = cx.add_window(|window, cx| { let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx); editor.set_style(EditorStyle::default(), window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([1..1, 2..2, 4..4]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4])); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "bacd\ne"); assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]); @@ -5122,9 +5106,7 @@ fn test_transpose(cx: &mut TestAppContext) { _ = cx.add_window(|window, cx| { let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx); editor.set_style(EditorStyle::default(), window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([4..4]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([4..4])); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "🏀🍐✋"); assert_eq!(editor.selections.ranges(cx), [8..8]); @@ -6103,7 +6085,7 @@ fn test_select_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -6230,7 +6212,7 @@ async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestA }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -6249,7 +6231,7 @@ async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestA .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ"); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1) ]) @@ -6995,7 +6977,7 @@ async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) { // Move cursor to a different position cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]); }); }); @@ -7100,7 +7082,7 @@ async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext "}); cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]); }); }); @@ -7360,7 +7342,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25), DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12), @@ -7542,7 +7524,7 @@ async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContex // Test case 1: Cursor at end of word editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5) ]); @@ -7566,7 +7548,7 @@ async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContex // Test case 2: Cursor at end of statement editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11) ]); @@ -7611,7 +7593,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte // Test 1: Cursor on a letter of a string word editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17) ]); @@ -7645,7 +7627,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte // Test 2: Partial selection within a word editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19) ]); @@ -7679,7 +7661,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte // Test 3: Complete word already selected editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21) ]); @@ -7713,7 +7695,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte // Test 4: Selection spanning across words editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24) ]); @@ -7915,9 +7897,7 @@ async fn test_autoindent(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([5..5, 8..8, 9..9]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9])); editor.newline(&Newline, window, cx); assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n"); assert_eq!( @@ -8699,7 +8679,7 @@ async fn test_surround_with_pair(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1), @@ -8849,7 +8829,7 @@ async fn test_delete_autoclose_pair(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([ Point::new(0, 1)..Point::new(0, 1), Point::new(1, 1)..Point::new(1, 1), @@ -9531,22 +9511,16 @@ async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) { }); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::Next), - window, - cx, - |s| s.select_ranges(Some(1..2)), - ); + editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { + s.select_ranges(Some(1..2)) + }); editor.insert("|one|two|three|", window, cx); }); assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx))); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::Next), - window, - cx, - |s| s.select_ranges(Some(60..70)), - ); + editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { + s.select_ranges(Some(60..70)) + }); editor.insert("|four|five|six|", window, cx); }); assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx))); @@ -9709,12 +9683,9 @@ async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) { // Edit only the first buffer editor.update_in(cx, |editor, window, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::Next), - window, - cx, - |s| s.select_ranges(Some(10..10)), - ); + editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { + s.select_ranges(Some(10..10)) + }); editor.insert("// edited", window, cx); }); @@ -11126,9 +11097,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { "}); cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([0..0]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([0..0])); }); let mocked_response = lsp::SignatureHelp { @@ -11215,7 +11184,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { // When selecting a range, the popover is gone. // Avoid using `cx.set_state` to not actually edit the document, just change its selections. cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19))); }) }); @@ -11232,7 +11201,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { // When unselecting again, the popover is back if within the brackets. cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19))); }) }); @@ -11252,7 +11221,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape. cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0))); s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19))); }) @@ -11293,7 +11262,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { cx.condition(|editor, _| !editor.signature_help_state.is_shown()) .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19))); }) }); @@ -11305,7 +11274,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { fn sample(param1: u8, param2: u8) {} "}); cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19))); }) }); @@ -11961,7 +11930,7 @@ async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppConte let fake_server = fake_servers.next().await.unwrap(); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([ Point::new(1, 11)..Point::new(1, 11), Point::new(7, 11)..Point::new(7, 11), @@ -13602,7 +13571,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx)); editor.update_in(cx, |editor, window, cx| { assert_eq!(editor.text(cx), "aaaa\nbbbb"); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([ Point::new(0, 0)..Point::new(0, 0), Point::new(1, 0)..Point::new(1, 0), @@ -13620,7 +13589,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { ); // Ensure the cursor's head is respected when deleting across an excerpt boundary. - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(0, 2)..Point::new(1, 2)]) }); editor.backspace(&Default::default(), window, cx); @@ -13630,7 +13599,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { [Point::new(1, 0)..Point::new(1, 0)] ); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(1, 1)..Point::new(0, 1)]) }); editor.backspace(&Default::default(), window, cx); @@ -13678,9 +13647,7 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) { true, ); assert_eq!(editor.text(cx), expected_text); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges(selection_ranges) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges)); editor.handle_input("X", window, cx); @@ -13741,7 +13708,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) { let editor = cx.add_window(|window, cx| { let mut editor = build_editor(multibuffer.clone(), window, cx); let snapshot = editor.snapshot(window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(1, 3)..Point::new(1, 3)]) }); editor.begin_selection( @@ -13763,7 +13730,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) { // Refreshing selections is a no-op when excerpts haven't changed. _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh()); + editor.change_selections(None, window, cx, |s| s.refresh()); assert_eq!( editor.selections.ranges(cx), [ @@ -13788,7 +13755,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) { // Refreshing selections will relocate the first selection to the original buffer // location. - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh()); + editor.change_selections(None, window, cx, |s| s.refresh()); assert_eq!( editor.selections.ranges(cx), [ @@ -13850,7 +13817,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) { ); // Ensure we don't panic when selections are refreshed and that the pending selection is finalized. - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh()); + editor.change_selections(None, window, cx, |s| s.refresh()); assert_eq!( editor.selections.ranges(cx), [Point::new(0, 3)..Point::new(0, 3)] @@ -13909,7 +13876,7 @@ async fn test_extra_newline_insertion(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3), DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5), @@ -14088,9 +14055,7 @@ async fn test_following(cx: &mut TestAppContext) { // Update the selections only _ = leader.update(cx, |leader, window, cx| { - leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([1..1]) - }); + leader.change_selections(None, window, cx, |s| s.select_ranges([1..1])); }); follower .update(cx, |follower, window, cx| { @@ -14138,9 +14103,7 @@ async fn test_following(cx: &mut TestAppContext) { // Update the selections and scroll position. The follower's scroll position is updated // via autoscroll, not via the leader's exact scroll position. _ = leader.update(cx, |leader, window, cx| { - leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([0..0]) - }); + leader.change_selections(None, window, cx, |s| s.select_ranges([0..0])); leader.request_autoscroll(Autoscroll::newest(), cx); leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx); }); @@ -14164,9 +14127,7 @@ async fn test_following(cx: &mut TestAppContext) { // Creating a pending selection that precedes another selection _ = leader.update(cx, |leader, window, cx| { - leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([1..1]) - }); + leader.change_selections(None, window, cx, |s| s.select_ranges([1..1])); leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx); }); follower @@ -14822,7 +14783,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) { editor_handle.update_in(cx, |editor, window, cx| { window.focus(&editor.focus_handle(cx)); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(0, 21)..Point::new(0, 20)]) }); editor.handle_input("{", window, cx); @@ -16437,7 +16398,7 @@ async fn test_multibuffer_reverts(cx: &mut TestAppContext) { }); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0))); }); editor.git_restore(&Default::default(), window, cx); @@ -16581,12 +16542,9 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) { cx.executor().run_until_parked(); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::Next), - window, - cx, - |s| s.select_ranges(Some(1..2)), - ); + editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { + s.select_ranges(Some(1..2)) + }); editor.open_excerpts(&OpenExcerpts, window, cx); }); cx.executor().run_until_parked(); @@ -16636,12 +16594,9 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) { .unwrap(); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::Next), - window, - cx, - |s| s.select_ranges(Some(39..40)), - ); + editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { + s.select_ranges(Some(39..40)) + }); editor.open_excerpts(&OpenExcerpts, window, cx); }); cx.executor().run_until_parked(); @@ -16695,12 +16650,9 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) { .unwrap(); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::Next), - window, - cx, - |s| s.select_ranges(Some(70..70)), - ); + editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { + s.select_ranges(Some(70..70)) + }); editor.open_excerpts(&OpenExcerpts, window, cx); }); cx.executor().run_until_parked(); @@ -18302,7 +18254,7 @@ async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) { .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(1, 0)..Point::new(1, 0)]) }); }); @@ -18330,7 +18282,7 @@ async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(1, 0)..Point::new(1, 0)]) }); }); @@ -18346,7 +18298,7 @@ async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext ); cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(2, 0)..Point::new(2, 0)]) }); }); @@ -18362,7 +18314,7 @@ async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext ); cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]) }); }); @@ -18393,7 +18345,7 @@ async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) { .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(2, 0)..Point::new(2, 0)]) }); }); @@ -18419,7 +18371,7 @@ async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) { .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(1, 0)..Point::new(1, 0)]) }); }); @@ -19357,14 +19309,14 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) { ); // Test finding task when cursor is inside function body - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(4, 5)..Point::new(4, 5)]) }); let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap(); assert_eq!(row, 3, "Should find task for cursor inside runnable_1"); // Test finding task when cursor is on function name - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(8, 4)..Point::new(8, 4)]) }); let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap(); @@ -19518,7 +19470,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) { .collect::(), "bbbb" ); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]); }); editor.handle_input("B", window, cx); @@ -19745,9 +19697,7 @@ async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut Test HighlightStyle::color(Hsla::green()), cx, ); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges(Some(highlight_range)) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range))); }); let full_text = format!("\n\n{sample_text}"); @@ -21117,7 +21067,7 @@ println!("5"); }) }); editor_1.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(expected_ranges.clone()); }); }); @@ -21563,7 +21513,7 @@ async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) { let fake_server = fake_servers.next().await.unwrap(); editor.update_in(cx, |editor, window, cx| { editor.set_text("", window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]); }); let Some((buffer, _)) = editor @@ -22569,7 +22519,7 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) { // Moving cursor should not trigger diagnostic request editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(0, 0)..Point::new(0, 0)]) }); }); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 426053707649c01aa655902f1a94c302125ef103..6fee347c17ea6b80a9767d5fcbf9094f6160ac5a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -5238,8 +5238,8 @@ impl EditorElement { paint_highlight(range.start, range.end, color, edges); } - let scroll_left = - layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width; + let scroll_left = layout.position_map.snapshot.scroll_position().x + * layout.position_map.em_advance; for (wrap_position, active) in layout.wrap_guides.iter() { let x = (layout.position_map.text_hitbox.origin.x @@ -6676,7 +6676,7 @@ impl EditorElement { let position_map: &PositionMap = &position_map; let line_height = position_map.line_height; - let max_glyph_width = position_map.em_width; + let max_glyph_advance = position_map.em_advance; let (delta, axis) = match delta { gpui::ScrollDelta::Pixels(mut pixels) => { //Trackpad @@ -6687,15 +6687,15 @@ impl EditorElement { gpui::ScrollDelta::Lines(lines) => { //Not trackpad let pixels = - point(lines.x * max_glyph_width, lines.y * line_height); + point(lines.x * max_glyph_advance, lines.y * line_height); (pixels, None) } }; let current_scroll_position = position_map.snapshot.scroll_position(); - let x = (current_scroll_position.x * max_glyph_width + let x = (current_scroll_position.x * max_glyph_advance - (delta.x * scroll_sensitivity)) - / max_glyph_width; + / max_glyph_advance; let y = (current_scroll_position.y * line_height - (delta.y * scroll_sensitivity)) / line_height; @@ -8591,7 +8591,7 @@ impl Element for EditorElement { start_row, editor_content_width, scroll_width, - em_width, + em_advance, &line_layouts, cx, ) @@ -10051,7 +10051,7 @@ fn compute_auto_height_layout( mod tests { use super::*; use crate::{ - Editor, MultiBuffer, SelectionEffects, + Editor, MultiBuffer, display_map::{BlockPlacement, BlockProperties}, editor_tests::{init_test, update_test_language_settings}, }; @@ -10176,7 +10176,7 @@ mod tests { window .update(cx, |editor, window, cx| { editor.cursor_shape = CursorShape::Block; - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([ Point::new(0, 0)..Point::new(1, 0), Point::new(3, 2)..Point::new(3, 3), diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 02f93e6829a3f7ac08ec7dfa390cd846560bb7d5..a716b2e0314223aa81338942da063d87919a71fe 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -1257,7 +1257,7 @@ mod tests { let snapshot = editor.buffer().read(cx).snapshot(cx); let anchor_range = snapshot.anchor_before(selection_range.start) ..snapshot.anchor_after(selection_range.end); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(crate::Autoscroll::fit()), window, cx, |s| { s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character) }); }); diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index cae47895356c4fbd6ffc94779952475ce6f18dd6..9e6fc356ea6ee840824b174fd216d0ea10828d59 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -3,7 +3,7 @@ use crate::{ EditorSnapshot, GlobalDiagnosticRenderer, Hover, display_map::{InlayOffset, ToDisplayPoint, invisibles::is_invisible}, hover_links::{InlayHighlight, RangeInEditor}, - scroll::ScrollAmount, + scroll::{Autoscroll, ScrollAmount}, }; use anyhow::Context as _; use gpui::{ @@ -746,7 +746,7 @@ pub fn open_markdown_url(link: SharedString, window: &mut Window, cx: &mut App) }; editor.update_in(cx, |editor, window, cx| { editor.change_selections( - Default::default(), + Some(Autoscroll::fit()), window, cx, |selections| { diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 647f34487ffc3cd8e688dffa9051737b3e44321e..dcfa8429a0da818679965dac4cdbc6875a16118f 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1302,7 +1302,6 @@ fn apply_hint_update( #[cfg(test)] pub mod tests { - use crate::SelectionEffects; use crate::editor_tests::update_test_language_settings; use crate::scroll::ScrollAmount; use crate::{ExcerptRange, scroll::Autoscroll, test::editor_lsp_test_context::rust_lang}; @@ -1385,9 +1384,7 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([13..13]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); editor.handle_input("some change", window, cx); }) .unwrap(); @@ -1701,9 +1698,7 @@ pub mod tests { rs_editor .update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([13..13]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); editor.handle_input("some rs change", window, cx); }) .unwrap(); @@ -1738,9 +1733,7 @@ pub mod tests { md_editor .update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([13..13]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); editor.handle_input("some md change", window, cx); }) .unwrap(); @@ -2162,9 +2155,7 @@ pub mod tests { ] { editor .update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([13..13]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); editor.handle_input(change_after_opening, window, cx); }) .unwrap(); @@ -2208,9 +2199,7 @@ pub mod tests { edits.push(cx.spawn(|mut cx| async move { task_editor .update(&mut cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([13..13]) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); editor.handle_input(async_later_change, window, cx); }) .unwrap(); @@ -2458,12 +2447,9 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::center()), - window, - cx, - |s| s.select_ranges([selection_in_cached_range..selection_in_cached_range]), - ); + editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { + s.select_ranges([selection_in_cached_range..selection_in_cached_range]) + }); }) .unwrap(); cx.executor().advance_clock(Duration::from_millis( @@ -2726,24 +2712,15 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::Next), - window, - cx, - |s| s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]), - ); - editor.change_selections( - SelectionEffects::scroll(Autoscroll::Next), - window, - cx, - |s| s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]), - ); - editor.change_selections( - SelectionEffects::scroll(Autoscroll::Next), - window, - cx, - |s| s.select_ranges([Point::new(50, 0)..Point::new(50, 0)]), - ); + editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { + s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) + }); + editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { + s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]) + }); + editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { + s.select_ranges([Point::new(50, 0)..Point::new(50, 0)]) + }); }) .unwrap(); cx.executor().run_until_parked(); @@ -2768,12 +2745,9 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::Next), - window, - cx, - |s| s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]), - ); + editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { + s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]) + }); }) .unwrap(); cx.executor().advance_clock(Duration::from_millis( @@ -2804,12 +2778,9 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::Next), - window, - cx, - |s| s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]), - ); + editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { + s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) + }); }) .unwrap(); cx.executor().advance_clock(Duration::from_millis( @@ -2841,7 +2812,7 @@ pub mod tests { editor_edited.store(true, Ordering::Release); editor .update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(57, 0)..Point::new(57, 0)]) }); editor.handle_input("++++more text++++", window, cx); @@ -3159,7 +3130,7 @@ pub mod tests { cx.executor().run_until_parked(); editor .update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) }) }) @@ -3441,7 +3412,7 @@ pub mod tests { cx.executor().run_until_parked(); editor .update(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) }) }) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index fa6bd93ab8558628670cb315e672ddf4fb3ebcab..ec3590dba217677bbaf2c8aa36bfd3147b9d6cbf 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1352,7 +1352,7 @@ impl ProjectItem for Editor { cx, ); if !restoration_data.selections.is_empty() { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(clip_ranges(&restoration_data.selections, &snapshot)); }); } @@ -1558,7 +1558,7 @@ impl SearchableItem for Editor { ) { self.unfold_ranges(&[matches[index].clone()], false, true, cx); let range = self.range_for_match(&matches[index]); - self.change_selections(Default::default(), window, cx, |s| { + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges([range]); }) } @@ -1570,7 +1570,7 @@ impl SearchableItem for Editor { cx: &mut Context, ) { self.unfold_ranges(matches, false, false, cx); - self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + self.change_selections(None, window, cx, |s| { s.select_ranges(matches.iter().cloned()) }); } diff --git a/crates/editor/src/jsx_tag_auto_close.rs b/crates/editor/src/jsx_tag_auto_close.rs index 95a792583953e02a77e592ea957b752f0f8042bb..f24fe46100879ce885d7bf863e797458c8bac52d 100644 --- a/crates/editor/src/jsx_tag_auto_close.rs +++ b/crates/editor/src/jsx_tag_auto_close.rs @@ -843,7 +843,7 @@ mod jsx_tag_autoclose_tests { let mut cx = EditorTestContext::for_editor(editor, cx).await; cx.update_editor(|editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select(vec![ Selection::from_offset(4), Selection::from_offset(9), diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index 4780f1f56582bf675d7cd7deb7b8f8effb98bfae..b9b8cbe997b2c6bbdd4f45e50e25621c037badf1 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -1,8 +1,8 @@ use crate::{ Copy, CopyAndTrim, CopyPermalinkToLine, Cut, DisplayPoint, DisplaySnapshot, Editor, EvaluateSelectedText, FindAllReferences, GoToDeclaration, GoToDefinition, GoToImplementation, - GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, SelectionEffects, - SelectionExt, ToDisplayPoint, ToggleCodeActions, + GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, SelectionExt, + ToDisplayPoint, ToggleCodeActions, actions::{Format, FormatSelections}, selections_collection::SelectionsCollection, }; @@ -177,7 +177,7 @@ pub fn deploy_context_menu( let anchor = buffer.anchor_before(point.to_point(&display_map)); if !display_ranges(&display_map, &editor.selections).any(|r| r.contains(&point)) { // Move the cursor to the clicked location so that dispatched actions make sense - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.clear_disjoint(); s.set_pending_anchor_range(anchor..anchor, SelectMode::Character); }); diff --git a/crates/editor/src/proposed_changes_editor.rs b/crates/editor/src/proposed_changes_editor.rs index 1ead45b3de89c0705510f8afc55ecf6176a4d7a2..c5f937f20c3c56b16f42b8e5b501b4a21e0e987f 100644 --- a/crates/editor/src/proposed_changes_editor.rs +++ b/crates/editor/src/proposed_changes_editor.rs @@ -1,4 +1,4 @@ -use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SelectionEffects, SemanticsProvider}; +use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SemanticsProvider}; use buffer_diff::BufferDiff; use collections::HashSet; use futures::{channel::mpsc, future::join_all}; @@ -213,9 +213,7 @@ impl ProposedChangesEditor { self.buffer_entries = buffer_entries; self.editor.update(cx, |editor, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { - selections.refresh() - }); + editor.change_selections(None, window, cx, |selections| selections.refresh()); editor.buffer.update(cx, |buffer, cx| { for diff in new_diffs { buffer.add_diff(diff, cx) diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 0a9d5e9535d2b2d29e33ee49a8afa46a387d773e..9e20d14b61c6413fda35bdc7c3e0f2d0521f7aa4 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -5,7 +5,7 @@ use std::{rc::Rc, sync::LazyLock}; pub use crate::rust_analyzer_ext::expand_macro_recursively; use crate::{ - DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, SelectionEffects, + DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, display_map::{ Block, BlockPlacement, CustomBlockId, DisplayMap, DisplayRow, DisplaySnapshot, ToDisplayPoint, @@ -93,9 +93,7 @@ pub fn select_ranges( ) { let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true); assert_eq!(editor.text(cx), unmarked_text); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges(text_ranges) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges(text_ranges)); } #[track_caller] diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index bdf73da5fbfd5d4c29826859790493fbb8494239..195abbe6d98acafb0fa5a874362dd41a2e0fc630 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -1,5 +1,5 @@ use crate::{ - AnchorRangeExt, DisplayPoint, Editor, MultiBuffer, RowExt, + AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, RowExt, display_map::{HighlightKey, ToDisplayPoint}, }; use buffer_diff::DiffHunkStatusKind; @@ -362,7 +362,7 @@ impl EditorTestContext { let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true); self.editor.update_in(&mut self.cx, |editor, window, cx| { editor.set_text(unmarked_text, window, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(selection_ranges) }) }); @@ -379,7 +379,7 @@ impl EditorTestContext { let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true); self.editor.update_in(&mut self.cx, |editor, window, cx| { assert_eq!(editor.text(cx), unmarked_text); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(selection_ranges) }) }); diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index c8c237fe90f12f2ac4ead04e0f2f0b4955f8bc1c..e07f84ba0272cb05572e404106af637788510a6e 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, Result}; use buffer_diff::{BufferDiff, BufferDiffSnapshot}; -use editor::{Editor, EditorEvent, MultiBuffer, SelectionEffects}; +use editor::{Editor, EditorEvent, MultiBuffer}; use git::repository::{CommitDetails, CommitDiff, CommitSummary, RepoPath}; use gpui::{ AnyElement, AnyView, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, @@ -154,7 +154,7 @@ impl CommitView { }); editor.update(cx, |editor, cx| { editor.disable_header_for_buffer(metadata_buffer_id.unwrap(), cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges(vec![0..0]); }); }); diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index f858bea94c288efc5dd24c3c17c63bc4b3c63aa2..371759bd24eb21ae53995648cf86a794b114e156 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -8,7 +8,7 @@ use anyhow::Result; use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus}; use collections::HashSet; use editor::{ - Editor, EditorEvent, SelectionEffects, + Editor, EditorEvent, actions::{GoToHunk, GoToPreviousHunk}, scroll::Autoscroll, }; @@ -255,14 +255,9 @@ impl ProjectDiff { fn move_to_path(&mut self, path_key: PathKey, window: &mut Window, cx: &mut Context) { if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) { self.editor.update(cx, |editor, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::focused()), - window, - cx, - |s| { - s.select_ranges([position..position]); - }, - ) + editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| { + s.select_ranges([position..position]); + }) }); } else { self.pending_scroll = Some(path_key); @@ -468,7 +463,7 @@ impl ProjectDiff { self.editor.update(cx, |editor, cx| { if was_empty { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { // TODO select the very beginning (possibly inside a deletion) selections.select_ranges([0..0]) }); diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 1ac933e316bcde24384139c851a8bedb63388611..bba9617975774883ba869e4a6e607cd66cebee5a 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -2,8 +2,8 @@ pub mod cursor_position; use cursor_position::{LineIndicatorFormat, UserCaretPosition}; use editor::{ - Anchor, Editor, MultiBufferSnapshot, RowHighlightOptions, SelectionEffects, ToOffset, ToPoint, - actions::Tab, scroll::Autoscroll, + Anchor, Editor, MultiBufferSnapshot, RowHighlightOptions, ToOffset, ToPoint, actions::Tab, + scroll::Autoscroll, }; use gpui::{ App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, SharedString, Styled, @@ -249,12 +249,9 @@ impl GoToLine { let Some(start) = self.anchor_from_query(&snapshot, cx) else { return; }; - editor.change_selections( - SelectionEffects::scroll(Autoscroll::center()), - window, - cx, - |s| s.select_anchor_ranges([start..start]), - ); + editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { + s.select_anchor_ranges([start..start]) + }); editor.focus_handle(cx).focus(window); cx.notify() }); diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index 4e9c887124d4583c0123db94508c3f2026fddc97..4ff793cbaf47a80bff266d21aebd273849c97875 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/crates/inline_completion_button/src/inline_completion_button.rs @@ -2,7 +2,7 @@ use anyhow::Result; use client::{UserStore, zed_urls}; use copilot::{Copilot, Status}; use editor::{ - Editor, SelectionEffects, + Editor, actions::{ShowEditPrediction, ToggleEditPrediction}, scroll::Autoscroll, }; @@ -929,14 +929,9 @@ async fn open_disabled_globs_setting_in_editor( .map(|inner_match| inner_match.start()..inner_match.end()) }); if let Some(range) = range { - item.change_selections( - SelectionEffects::scroll(Autoscroll::newest()), - window, - cx, - |selections| { - selections.select_ranges(vec![range]); - }, - ); + item.change_selections(Some(Autoscroll::newest()), window, cx, |selections| { + selections.select_ranges(vec![range]); + }); } })?; diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 08bdb8e04f620518ef7955361979f28d83353718..0aed317a0b80f0d0bb52095a9d6d5f95489bce2f 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -1,7 +1,7 @@ use anyhow::Result; use chrono::{Datelike, Local, NaiveTime, Timelike}; +use editor::Editor; use editor::scroll::Autoscroll; -use editor::{Editor, SelectionEffects}; use gpui::{App, AppContext as _, Context, Window, actions}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -168,12 +168,9 @@ pub fn new_journal_entry(workspace: &Workspace, window: &mut Window, cx: &mut Ap if let Some(editor) = item.downcast::().map(|editor| editor.downgrade()) { editor.update_in(cx, |editor, window, cx| { let len = editor.buffer().read(cx).len(cx); - editor.change_selections( - SelectionEffects::scroll(Autoscroll::center()), - window, - cx, - |s| s.select_ranges([len..len]), - ); + editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { + s.select_ranges([len..len]) + }); if len > 0 { editor.insert("\n\n", window, cx); } diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index 6f74e76e261b7b5f33463fe7932c7eaf0fa2a9fe..99132ce452e4680c8a7302f4c1afbc9d62b613a9 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -1,4 +1,4 @@ -use editor::{Anchor, Editor, ExcerptId, SelectionEffects, scroll::Autoscroll}; +use editor::{Anchor, Editor, ExcerptId, scroll::Autoscroll}; use gpui::{ App, AppContext as _, Context, Div, Entity, EventEmitter, FocusHandle, Focusable, Hsla, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, @@ -340,7 +340,7 @@ impl Render for SyntaxTreeView { mem::swap(&mut range.start, &mut range.end); editor.change_selections( - SelectionEffects::scroll(Autoscroll::newest()), + Some(Autoscroll::newest()), window, cx, |selections| { selections.select_ranges(vec![range]); diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index f22671d5dfaf2badafb9a7be5b372c91bd0b1ef6..bf1a1da5727a9143e844921dabd770728dc8bcf0 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -4,7 +4,7 @@ use std::{ops::Range, path::PathBuf}; use anyhow::Result; use editor::scroll::Autoscroll; -use editor::{Editor, EditorEvent, SelectionEffects}; +use editor::{Editor, EditorEvent}; use gpui::{ App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ListState, ParentElement, Render, RetainAllImageCache, Styled, Subscription, Task, @@ -468,12 +468,9 @@ impl MarkdownPreviewView { ) { if let Some(state) = &self.active_editor { state.editor.update(cx, |editor, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::center()), - window, - cx, - |selections| selections.select_ranges(vec![selection]), - ); + editor.change_selections(Some(Autoscroll::center()), window, cx, |selections| { + selections.select_ranges(vec![selection]) + }); window.focus(&editor.focus_handle(cx)); }); } diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 8c5e78d77bce76e62ef94d2501dbef588cd76f00..3fec1d616ab5cbe577d4f3fec7fff1449c62fec6 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -4,8 +4,8 @@ use std::{ sync::Arc, }; +use editor::RowHighlightOptions; use editor::{Anchor, AnchorRangeExt, Editor, scroll::Autoscroll}; -use editor::{RowHighlightOptions, SelectionEffects}; use fuzzy::StringMatch; use gpui::{ App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, HighlightStyle, @@ -288,12 +288,9 @@ impl PickerDelegate for OutlineViewDelegate { .highlighted_rows::() .next(); if let Some((rows, _)) = highlight { - active_editor.change_selections( - SelectionEffects::scroll(Autoscroll::center()), - window, - cx, - |s| s.select_ranges([rows.start..rows.start]), - ); + active_editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { + s.select_ranges([rows.start..rows.start]) + }); active_editor.clear_row_highlights::(); window.focus(&active_editor.focus_handle(cx)); } diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 0be05d458908e3d7b1317ea205664a349eb6ef5f..5bb771c1e9fc8e1e7d605e1583b52137f0181bd4 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -19,10 +19,10 @@ use collections::{BTreeSet, HashMap, HashSet, hash_map}; use db::kvp::KEY_VALUE_STORE; use editor::{ AnchorRangeExt, Bias, DisplayPoint, Editor, EditorEvent, EditorSettings, ExcerptId, - ExcerptRange, MultiBufferSnapshot, RangeToAnchorExt, SelectionEffects, ShowScrollbar, + ExcerptRange, MultiBufferSnapshot, RangeToAnchorExt, ShowScrollbar, display_map::ToDisplayPoint, items::{entry_git_aware_label_color, entry_label_color}, - scroll::{Autoscroll, ScrollAnchor, ScrollbarAutoHide}, + scroll::{Autoscroll, AutoscrollStrategy, ScrollAnchor, ScrollbarAutoHide}, }; use file_icons::FileIcons; use fuzzy::{StringMatch, StringMatchCandidate, match_strings}; @@ -1099,7 +1099,7 @@ impl OutlinePanel { if change_selection { active_editor.update(cx, |editor, cx| { editor.change_selections( - SelectionEffects::scroll(Autoscroll::center()), + Some(Autoscroll::Strategy(AutoscrollStrategy::Center, None)), window, cx, |s| s.select_ranges(Some(anchor..anchor)), diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 4a122ac7316ed1a7552eda41ef223c62bc3ba910..c1ebe25538c4db1f02539f5138c065661be47085 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -4,7 +4,7 @@ pub mod popover_menu; use anyhow::Result; use editor::{ - Editor, SelectionEffects, + Editor, actions::{MoveDown, MoveUp}, scroll::Autoscroll, }; @@ -695,12 +695,9 @@ impl Picker { editor.update(cx, |editor, cx| { editor.set_text(query, window, cx); let editor_offset = editor.buffer().read(cx).len(cx); - editor.change_selections( - SelectionEffects::scroll(Autoscroll::Next), - window, - cx, - |s| s.select_ranges(Some(editor_offset..editor_offset)), - ); + editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { + s.select_ranges(Some(editor_offset..editor_offset)) + }); }); } } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 4db83bcf4c897d3a9bddf304ee96b3de600899bb..3bcc881f9d8a39ddbf1285e0deffe6b2907a4aa5 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -12,7 +12,7 @@ use editor::{ entry_diagnostic_aware_icon_decoration_and_color, entry_diagnostic_aware_icon_name_and_color, entry_git_aware_label_color, }, - scroll::ScrollbarAutoHide, + scroll::{Autoscroll, ScrollbarAutoHide}, }; use file_icons::FileIcons; use git::status::GitSummary; @@ -1589,7 +1589,7 @@ impl ProjectPanel { }); self.filename_editor.update(cx, |editor, cx| { editor.set_text(file_name, window, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges([selection]) }); window.focus(&editor.focus_handle(cx)); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 47aed8f470f3538f34bff0a0accdd55d9f1ac70e..a9ba14264ff4a1c30536f6b400f0336bc49a1631 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -1,4 +1,4 @@ -use editor::{Bias, Editor, SelectionEffects, scroll::Autoscroll, styled_runs_for_code_label}; +use editor::{Bias, Editor, scroll::Autoscroll, styled_runs_for_code_label}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ App, Context, DismissEvent, Entity, FontWeight, ParentElement, StyledText, Task, WeakEntity, @@ -136,12 +136,9 @@ impl PickerDelegate for ProjectSymbolsDelegate { workspace.open_project_item::(pane, buffer, true, true, window, cx); editor.update(cx, |editor, cx| { - editor.change_selections( - SelectionEffects::scroll(Autoscroll::center()), - window, - cx, - |s| s.select_ranges([position..position]), - ); + editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { + s.select_ranges([position..position]) + }); }); })?; anyhow::Ok(()) diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index 18d41f3eae97ce4288d95e1e0eabb57d4b47adec..20518fb12cc39c54993a077decd0ee1ff5f81c8b 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -8,7 +8,6 @@ use crate::{ }; use anyhow::Context as _; use collections::{HashMap, HashSet}; -use editor::SelectionEffects; use editor::{ Anchor, AnchorRangeExt as _, Editor, MultiBuffer, ToPoint, display_map::{ @@ -478,7 +477,7 @@ impl Session { if move_down { editor.update(cx, move |editor, cx| { editor.change_selections( - SelectionEffects::scroll(Autoscroll::top_relative(8)), + Some(Autoscroll::top_relative(8)), window, cx, |selections| { diff --git a/crates/rules_library/src/rules_library.rs b/crates/rules_library/src/rules_library.rs index 5e249162d3286e777ba28f8c645f8e2918bc9acf..231647ef5a930da03a50b21eb571d0f19e039e7a 100644 --- a/crates/rules_library/src/rules_library.rs +++ b/crates/rules_library/src/rules_library.rs @@ -1,6 +1,6 @@ use anyhow::Result; use collections::{HashMap, HashSet}; -use editor::{CompletionProvider, SelectionEffects}; +use editor::CompletionProvider; use editor::{CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle, actions::Tab}; use gpui::{ Action, App, Bounds, Entity, EventEmitter, Focusable, PromptLevel, Subscription, Task, @@ -895,15 +895,10 @@ impl RulesLibrary { } EditorEvent::Blurred => { title_editor.update(cx, |title_editor, cx| { - title_editor.change_selections( - SelectionEffects::no_scroll(), - window, - cx, - |selections| { - let cursor = selections.oldest_anchor().head(); - selections.select_anchor_ranges([cursor..cursor]); - }, - ); + title_editor.change_selections(None, window, cx, |selections| { + let cursor = selections.oldest_anchor().head(); + selections.select_anchor_ranges([cursor..cursor]); + }); }); } _ => {} @@ -925,15 +920,10 @@ impl RulesLibrary { } EditorEvent::Blurred => { body_editor.update(cx, |body_editor, cx| { - body_editor.change_selections( - SelectionEffects::no_scroll(), - window, - cx, - |selections| { - let cursor = selections.oldest_anchor().head(); - selections.select_anchor_ranges([cursor..cursor]); - }, - ); + body_editor.change_selections(None, window, cx, |selections| { + let cursor = selections.oldest_anchor().head(); + selections.select_anchor_ranges([cursor..cursor]); + }); }); } _ => {} diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 715cb451ddc6b0ea662234bd99dfeb4ba876f767..fa7a3ba915896d52f1d2f60f55d5ab13746edda8 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1540,10 +1540,7 @@ mod tests { use std::ops::Range; use super::*; - use editor::{ - DisplayPoint, Editor, MultiBuffer, SearchSettings, SelectionEffects, - display_map::DisplayRow, - }; + use editor::{DisplayPoint, Editor, MultiBuffer, SearchSettings, display_map::DisplayRow}; use gpui::{Hsla, TestAppContext, UpdateGlobal, VisualTestContext}; use language::{Buffer, Point}; use project::Project; @@ -1680,7 +1677,7 @@ mod tests { }); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0) ]) @@ -1767,7 +1764,7 @@ mod tests { // Park the cursor in between matches and ensure that going to the previous match selects // the closest match to the left. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0) ]) @@ -1788,7 +1785,7 @@ mod tests { // Park the cursor in between matches and ensure that going to the next match selects the // closest match to the right. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0) ]) @@ -1809,7 +1806,7 @@ mod tests { // Park the cursor after the last match and ensure that going to the previous match selects // the last match. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 60)..DisplayPoint::new(DisplayRow(3), 60) ]) @@ -1830,7 +1827,7 @@ mod tests { // Park the cursor after the last match and ensure that going to the next match selects the // first match. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 60)..DisplayPoint::new(DisplayRow(3), 60) ]) @@ -1851,7 +1848,7 @@ mod tests { // Park the cursor before the first match and ensure that going to the previous match // selects the last match. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0) ]) @@ -2628,7 +2625,7 @@ mod tests { }); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(vec![Point::new(1, 0)..Point::new(2, 4)]) }) }); @@ -2711,7 +2708,7 @@ mod tests { }); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(vec![ Point::new(1, 0)..Point::new(1, 4), Point::new(5, 3)..Point::new(6, 4), diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index fd2cc3a1ced907921698081c8c124c8132ba3692..8e1ea3d7733cd18412b1330551301864df981ec8 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -7,7 +7,7 @@ use anyhow::Context as _; use collections::{HashMap, HashSet}; use editor::{ Anchor, Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MAX_TAB_TITLE_LEN, - MultiBuffer, SelectionEffects, actions::SelectAll, items::active_match_index, + MultiBuffer, actions::SelectAll, items::active_match_index, scroll::Autoscroll, }; use futures::{StreamExt, stream::FuturesOrdered}; use gpui::{ @@ -1303,7 +1303,7 @@ impl ProjectSearchView { self.results_editor.update(cx, |editor, cx| { let range_to_select = editor.range_for_match(&range_to_select); editor.unfold_ranges(std::slice::from_ref(&range_to_select), false, true, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges([range_to_select]) }); }); @@ -1350,9 +1350,7 @@ impl ProjectSearchView { fn focus_results_editor(&mut self, window: &mut Window, cx: &mut Context) { self.query_editor.update(cx, |query_editor, cx| { let cursor = query_editor.selections.newest_anchor().head(); - query_editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges([cursor..cursor]) - }); + query_editor.change_selections(None, window, cx, |s| s.select_ranges([cursor..cursor])); }); let results_handle = self.results_editor.focus_handle(cx); window.focus(&results_handle); @@ -1372,7 +1370,7 @@ impl ProjectSearchView { let range_to_select = match_ranges .first() .map(|range| editor.range_for_match(range)); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(range_to_select) }); editor.scroll(Point::default(), Some(Axis::Vertical), window, cx); diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 1510f613e34ef7bfc78bbfad23b7843787432491..d3b8d927b3cf9114bc341b795f31e1ee4ad8e6b7 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -751,7 +751,7 @@ fn string_match_candidates<'a>( mod tests { use std::{path::PathBuf, sync::Arc}; - use editor::{Editor, SelectionEffects}; + use editor::Editor; use gpui::{TestAppContext, VisualTestContext}; use language::{Language, LanguageConfig, LanguageMatcher, Point}; use project::{ContextProviderWithTasks, FakeFs, Project}; @@ -1028,7 +1028,7 @@ mod tests { .update(|_window, cx| second_item.act_as::(cx)) .unwrap(); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(Some(Point::new(1, 2)..Point::new(1, 5))) }) }); diff --git a/crates/tasks_ui/src/tasks_ui.rs b/crates/tasks_ui/src/tasks_ui.rs index 0b3f70e6bcc5402bae3af09effb5bebc1a574977..acdc7d0298490b2765b828c5bc468796deb6b3c3 100644 --- a/crates/tasks_ui/src/tasks_ui.rs +++ b/crates/tasks_ui/src/tasks_ui.rs @@ -393,7 +393,7 @@ fn worktree_context(worktree_abs_path: &Path) -> TaskContext { mod tests { use std::{collections::HashMap, sync::Arc}; - use editor::{Editor, SelectionEffects}; + use editor::Editor; use gpui::TestAppContext; use language::{Language, LanguageConfig}; use project::{BasicContextProvider, FakeFs, Project, task_store::TaskStore}; @@ -538,7 +538,7 @@ mod tests { // And now, let's select an identifier. editor2.update_in(cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + editor.change_selections(None, window, cx, |selections| { selections.select_ranges([14..18]) }) }); diff --git a/crates/vim/src/change_list.rs b/crates/vim/src/change_list.rs index 25da3e09b8f6115273176cdb74e10e52aaeb951c..3332239631ae836111fe34431e807a21381b970f 100644 --- a/crates/vim/src/change_list.rs +++ b/crates/vim/src/change_list.rs @@ -1,4 +1,4 @@ -use editor::{Bias, Direction, Editor, display_map::ToDisplayPoint, movement}; +use editor::{Bias, Direction, Editor, display_map::ToDisplayPoint, movement, scroll::Autoscroll}; use gpui::{Context, Window, actions}; use crate::{Vim, state::Mode}; @@ -29,7 +29,7 @@ impl Vim { .next_change(count, direction) .map(|s| s.to_vec()) { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { let map = s.display_map(); s.select_display_ranges(selections.iter().map(|a| { let point = a.to_display_point(&map); diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 839a0392d4d3b18edb6449b15c9a310c387c5ad7..40e8fcffa3c90be95f1421548a19c3a1a444035c 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -2,9 +2,10 @@ use anyhow::Result; use collections::{HashMap, HashSet}; use command_palette_hooks::CommandInterceptResult; use editor::{ - Bias, Editor, SelectionEffects, ToPoint, + Bias, Editor, ToPoint, actions::{SortLinesCaseInsensitive, SortLinesCaseSensitive}, display_map::ToDisplayPoint, + scroll::Autoscroll, }; use gpui::{Action, App, AppContext as _, Context, Global, Window, actions}; use itertools::Itertools; @@ -421,7 +422,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { let target = snapshot .buffer_snapshot .clip_point(Point::new(buffer_row.0, current.head().column), Bias::Left); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges([target..target]); }); @@ -492,7 +493,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { .disjoint_anchor_ranges() .collect::>() }); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { let end = Point::new(range.end.0, s.buffer().line_len(range.end)); s.select_ranges([end..Point::new(range.start.0, 0)]); }); @@ -502,7 +503,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { window.dispatch_action(action.action.boxed_clone(), cx); cx.defer_in(window, move |vim, window, cx| { vim.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { if let Some(previous_selections) = previous_selections { s.select_ranges(previous_selections); } else { @@ -1454,20 +1455,15 @@ impl OnMatchingLines { editor .update_in(cx, |editor, window, cx| { editor.start_transaction_at(Instant::now(), window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.replace_cursors_with(|_| new_selections); }); window.dispatch_action(action, cx); cx.defer_in(window, move |editor, window, cx| { let newest = editor.selections.newest::(cx).clone(); - editor.change_selections( - SelectionEffects::no_scroll(), - window, - cx, - |s| { - s.select(vec![newest]); - }, - ); + editor.change_selections(None, window, cx, |s| { + s.select(vec![newest]); + }); editor.end_transaction_at(Instant::now(), cx); }) }) @@ -1570,7 +1566,7 @@ impl Vim { ) .unwrap_or((start.range(), MotionKind::Exclusive)); if range.start != start.start { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([ range.start.to_point(&snapshot)..range.start.to_point(&snapshot) ]); @@ -1610,7 +1606,7 @@ impl Vim { .range(&snapshot, start.clone(), around) .unwrap_or(start.range()); if range.start != start.start { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges([ range.start.to_point(&snapshot)..range.start.to_point(&snapshot) ]); @@ -1803,7 +1799,7 @@ impl ShellExec { editor.transact(window, cx, |editor, window, cx| { editor.edit([(range.clone(), text)], cx); let snapshot = editor.buffer().read(cx).snapshot(cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { let point = if is_read { let point = range.end.to_point(&snapshot); Point::new(point.row.saturating_sub(1), 0) diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index d0bbf5f17f3bf39dd1a7d02d0b54d2512a32e913..d5312934e477d2d5ddea089695a5055858cd391b 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -1,4 +1,4 @@ -use editor::{DisplayPoint, Editor, movement}; +use editor::{DisplayPoint, Editor, movement, scroll::Autoscroll}; use gpui::{Action, actions}; use gpui::{Context, Window}; use language::{CharClassifier, CharKind}; @@ -47,7 +47,7 @@ impl Vim { mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool, ) { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let times = times.unwrap_or(1); let new_goal = SelectionGoal::None; @@ -100,7 +100,7 @@ impl Vim { mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool, ) { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let times = times.unwrap_or(1); let new_goal = SelectionGoal::None; @@ -161,7 +161,7 @@ impl Vim { ) { self.update_editor(window, cx, |_, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let goal = selection.goal; let cursor = if selection.is_empty() || selection.reversed { @@ -239,7 +239,7 @@ impl Vim { Motion::FindForward { .. } => { self.update_editor(window, cx, |_, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let goal = selection.goal; let cursor = if selection.is_empty() || selection.reversed { @@ -266,7 +266,7 @@ impl Vim { Motion::FindBackward { .. } => { self.update_editor(window, cx, |_, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let goal = selection.goal; let cursor = if selection.is_empty() || selection.reversed { diff --git a/crates/vim/src/indent.rs b/crates/vim/src/indent.rs index c8762c563a63479b6f187d3d7d0648ee2d2a92be..ac708a7e8932f98502a2b969fa9ca68153765e8b 100644 --- a/crates/vim/src/indent.rs +++ b/crates/vim/src/indent.rs @@ -1,6 +1,5 @@ use crate::{Vim, motion::Motion, object::Object, state::Mode}; use collections::HashMap; -use editor::SelectionEffects; use editor::{Bias, Editor, display_map::ToDisplayPoint}; use gpui::actions; use gpui::{Context, Window}; @@ -89,7 +88,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); selection_starts.insert(selection.id, anchor); @@ -107,7 +106,7 @@ impl Vim { IndentDirection::Out => editor.outdent(&Default::default(), window, cx), IndentDirection::Auto => editor.autoindent(&Default::default(), window, cx), } - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = selection_starts.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); @@ -129,7 +128,7 @@ impl Vim { self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { let mut original_positions: HashMap<_, _> = Default::default(); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); original_positions.insert(selection.id, anchor); @@ -141,7 +140,7 @@ impl Vim { IndentDirection::Out => editor.outdent(&Default::default(), window, cx), IndentDirection::Auto => editor.autoindent(&Default::default(), window, cx), } - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = original_positions.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); diff --git a/crates/vim/src/insert.rs b/crates/vim/src/insert.rs index 7b38bed2be087085bf66e632c027af7aa858e6f3..a30af8769fac99ac1d1b8c131b32e8c440e0b180 100644 --- a/crates/vim/src/insert.rs +++ b/crates/vim/src/insert.rs @@ -1,5 +1,5 @@ use crate::{Vim, state::Mode}; -use editor::{Bias, Editor}; +use editor::{Bias, Editor, scroll::Autoscroll}; use gpui::{Action, Context, Window, actions}; use language::SelectionGoal; use settings::Settings; @@ -34,7 +34,7 @@ impl Vim { editor.dismiss_menus_and_popups(false, window, cx); if !HelixModeSetting::get_global(cx).0 { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, mut cursor, _| { *cursor.column_mut() = cursor.column().saturating_sub(1); (map.clip_point(cursor, Bias::Left), SelectionGoal::None) diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 2a6e5196bc01da9f8e6f3b6e12a9e0690757580f..e9b01f5a674f8736b0379ca20d8907e1ac3782c6 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -4,6 +4,7 @@ use editor::{ movement::{ self, FindRange, TextLayoutDetails, find_boundary, find_preceding_boundary_display_point, }, + scroll::Autoscroll, }; use gpui::{Action, Context, Window, actions, px}; use language::{CharKind, Point, Selection, SelectionGoal}; @@ -625,7 +626,7 @@ impl Vim { Mode::Visual | Mode::VisualLine | Mode::VisualBlock => { if !prior_selections.is_empty() { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(prior_selections.iter().cloned()) }) }); diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 2003c8b754613ffd288fac6166d20c700f3d1884..1d70227e0ba8791ebe6ebecd6e1202eae44d91db 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -26,6 +26,7 @@ use collections::BTreeSet; use convert::ConvertTarget; use editor::Bias; use editor::Editor; +use editor::scroll::Autoscroll; use editor::{Anchor, SelectionEffects}; use editor::{display_map::ToDisplayPoint, movement}; use gpui::{Context, Window, actions}; @@ -102,7 +103,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, |vim, _: &HelixDelete, window, cx| { vim.record_current_action(cx); vim.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { selection.end = movement::right(map, selection.end) @@ -376,7 +377,7 @@ impl Vim { self.start_recording(cx); self.switch_mode(Mode::Insert, false, window, cx); self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, cursor, _| (right(map, cursor, 1), SelectionGoal::None)); }); }); @@ -387,7 +388,7 @@ impl Vim { if self.mode.is_visual() { let current_mode = self.mode; self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if current_mode == Mode::VisualLine { let start_of_line = motion::start_of_line(map, false, selection.start); @@ -411,7 +412,7 @@ impl Vim { self.start_recording(cx); self.switch_mode(Mode::Insert, false, window, cx); self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { ( first_non_whitespace(map, false, cursor), @@ -431,7 +432,7 @@ impl Vim { self.start_recording(cx); self.switch_mode(Mode::Insert, false, window, cx); self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { (next_line_end(map, cursor, 1), SelectionGoal::None) }); @@ -452,7 +453,7 @@ impl Vim { return; }; - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_anchor_ranges(marks.iter().map(|mark| *mark..*mark)) }); }); @@ -488,7 +489,7 @@ impl Vim { }) .collect::>(); editor.edit_with_autoindent(edits, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { let previous_line = motion::start_of_relative_buffer_row(map, cursor, -1); let insert_point = motion::end_of_line(map, false, previous_line, 1); @@ -529,7 +530,7 @@ impl Vim { (end_of_line..end_of_line, "\n".to_string() + &indent) }) .collect::>(); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.maybe_move_cursors_with(|map, cursor, goal| { Motion::CurrentLine.move_point( map, @@ -606,7 +607,7 @@ impl Vim { .collect::>(); editor.edit(edits, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|_, selection| { if let Some(position) = original_positions.get(&selection.id) { selection.collapse_to(*position, SelectionGoal::None); @@ -754,7 +755,7 @@ impl Vim { editor.newline(&editor::actions::Newline, window, cx); } editor.set_clip_at_line_ends(true, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let point = movement::saturating_left(map, selection.head()); selection.collapse_to(point, SelectionGoal::None) @@ -790,7 +791,7 @@ impl Vim { cx: &mut Context, mut positions: HashMap, ) { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if let Some(anchor) = positions.remove(&selection.id) { selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index da8d38ea13518945b4ba7ca5c416477b99a05b6e..e6ecf309f198891ba05370a9270d52978c73ea52 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -8,6 +8,7 @@ use editor::{ Bias, DisplayPoint, display_map::{DisplaySnapshot, ToDisplayPoint}, movement::TextLayoutDetails, + scroll::Autoscroll, }; use gpui::{Context, Window}; use language::Selection; @@ -39,7 +40,7 @@ impl Vim { editor.transact(window, cx, |editor, window, cx| { // We are swapping to insert mode anyway. Just set the line end clipping behavior now editor.set_clip_at_line_ends(false, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let kind = match motion { Motion::NextWordStart { ignore_punctuation } @@ -113,7 +114,7 @@ impl Vim { // We are swapping to insert mode anyway. Just set the line end clipping behavior now editor.set_clip_at_line_ends(false, cx); editor.transact(window, cx, |editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { objects_found |= object.expand_selection(map, selection, around); }); diff --git a/crates/vim/src/normal/convert.rs b/crates/vim/src/normal/convert.rs index 4621e3ab896c0e487d9e05323e362642d684573a..5295e79edb4c08c1b7ee869d0014168df2f40787 100644 --- a/crates/vim/src/normal/convert.rs +++ b/crates/vim/src/normal/convert.rs @@ -1,5 +1,5 @@ use collections::HashMap; -use editor::{SelectionEffects, display_map::ToDisplayPoint}; +use editor::{display_map::ToDisplayPoint, scroll::Autoscroll}; use gpui::{Context, Window}; use language::{Bias, Point, SelectionGoal}; use multi_buffer::MultiBufferRow; @@ -36,7 +36,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Left); selection_starts.insert(selection.id, anchor); @@ -66,7 +66,7 @@ impl Vim { editor.convert_to_rot47(&Default::default(), window, cx) } } - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = selection_starts.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); @@ -90,7 +90,7 @@ impl Vim { editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); let mut original_positions: HashMap<_, _> = Default::default(); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around); original_positions.insert( @@ -116,7 +116,7 @@ impl Vim { editor.convert_to_rot47(&Default::default(), window, cx) } } - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = original_positions.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); @@ -239,7 +239,7 @@ impl Vim { .collect::(); editor.edit([(range, text)], cx) } - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(cursor_positions) }) }); diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index 141346c99fcdc1f155e8628596c3e6805f5086aa..f52d9bebe05d517a5dda8d8080d47a9588c9ed9d 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -7,6 +7,7 @@ use collections::{HashMap, HashSet}; use editor::{ Bias, DisplayPoint, display_map::{DisplaySnapshot, ToDisplayPoint}, + scroll::Autoscroll, }; use gpui::{Context, Window}; use language::{Point, Selection}; @@ -29,7 +30,7 @@ impl Vim { let mut original_columns: HashMap<_, _> = Default::default(); let mut motion_kind = None; let mut ranges_to_copy = Vec::new(); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let original_head = selection.head(); original_columns.insert(selection.id, original_head.column()); @@ -70,7 +71,7 @@ impl Vim { // Fixup cursor position after the deletion editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let mut cursor = selection.head(); if kind.linewise() { @@ -101,7 +102,7 @@ impl Vim { // Emulates behavior in vim where if we expanded backwards to include a newline // the cursor gets set back to the start of the line let mut should_move_to_start: HashSet<_> = Default::default(); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around); let offset_range = selection.map(|p| p.to_offset(map, Bias::Left)).range(); @@ -158,7 +159,7 @@ impl Vim { // Fixup cursor position after the deletion editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let mut cursor = selection.head(); if should_move_to_start.contains(&selection.id) { diff --git a/crates/vim/src/normal/increment.rs b/crates/vim/src/normal/increment.rs index 09e6e85a5ccd057111dddca9e1bc76ebfacc1b63..e2a0d282673a6f1ccb96d7c0a2d63f55d3dd78c1 100644 --- a/crates/vim/src/normal/increment.rs +++ b/crates/vim/src/normal/increment.rs @@ -1,4 +1,4 @@ -use editor::{Editor, MultiBufferSnapshot, ToOffset, ToPoint}; +use editor::{Editor, MultiBufferSnapshot, ToOffset, ToPoint, scroll::Autoscroll}; use gpui::{Action, Context, Window}; use language::{Bias, Point}; use schemars::JsonSchema; @@ -97,7 +97,7 @@ impl Vim { editor.edit(edits, cx); let snapshot = editor.buffer().read(cx).snapshot(cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { let mut new_ranges = Vec::new(); for (visual, anchor) in new_anchors.iter() { let mut point = anchor.to_point(&snapshot); diff --git a/crates/vim/src/normal/mark.rs b/crates/vim/src/normal/mark.rs index 57a6108841e49d0461ff343e000969839287d6c7..af4b71f4278a35a1e6462d833d46a247f025fda4 100644 --- a/crates/vim/src/normal/mark.rs +++ b/crates/vim/src/normal/mark.rs @@ -4,6 +4,7 @@ use editor::{ Anchor, Bias, DisplayPoint, Editor, MultiBuffer, display_map::{DisplaySnapshot, ToDisplayPoint}, movement, + scroll::Autoscroll, }; use gpui::{Context, Entity, EntityId, UpdateGlobal, Window}; use language::SelectionGoal; @@ -115,7 +116,7 @@ impl Vim { } } - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_anchor_ranges(ranges) }); }) @@ -168,7 +169,7 @@ impl Vim { } }) .collect(); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(points.into_iter().map(|p| p..p)) }) }) @@ -250,7 +251,7 @@ impl Vim { } if !should_jump && !ranges.is_empty() { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_anchor_ranges(ranges) }); } diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 0dade838f5d5edbdca89dcea945da16a9fc89c63..41337f07074e56e17b35bc72addf3c0ce3ae0f39 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -1,4 +1,4 @@ -use editor::{DisplayPoint, RowExt, SelectionEffects, display_map::ToDisplayPoint, movement}; +use editor::{DisplayPoint, RowExt, display_map::ToDisplayPoint, movement, scroll::Autoscroll}; use gpui::{Action, Context, Window}; use language::{Bias, SelectionGoal}; use schemars::JsonSchema; @@ -187,7 +187,7 @@ impl Vim { // and put the cursor on the first non-blank character of the first inserted line (or at the end if the first line is blank). // otherwise vim will insert the next text at (or before) the current cursor position, // the cursor will go to the last (or first, if is_multiline) inserted character. - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.replace_cursors_with(|map| { let mut cursors = Vec::new(); for (anchor, line_mode, is_multiline) in &new_selections { @@ -238,7 +238,7 @@ impl Vim { self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around); }); @@ -252,7 +252,7 @@ impl Vim { }; editor.insert(&text, window, cx); editor.set_clip_at_line_ends(true, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { selection.start = map.clip_point(selection.start, Bias::Left); selection.end = selection.start @@ -276,7 +276,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { motion.expand_selection( map, @@ -296,7 +296,7 @@ impl Vim { }; editor.insert(&text, window, cx); editor.set_clip_at_line_ends(true, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { selection.start = map.clip_point(selection.start, Bias::Left); selection.end = selection.start diff --git a/crates/vim/src/normal/substitute.rs b/crates/vim/src/normal/substitute.rs index 96df61e528d3df3a480b978c78154d8c0c3a0150..1199356995df9be3e8425d7c7d3ad0f1ae4c76b7 100644 --- a/crates/vim/src/normal/substitute.rs +++ b/crates/vim/src/normal/substitute.rs @@ -1,4 +1,4 @@ -use editor::{Editor, SelectionEffects, movement}; +use editor::{Editor, movement}; use gpui::{Context, Window, actions}; use language::Point; @@ -41,7 +41,7 @@ impl Vim { editor.set_clip_at_line_ends(false, cx); editor.transact(window, cx, |editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { if selection.start == selection.end { Motion::Right.expand_selection( diff --git a/crates/vim/src/normal/toggle_comments.rs b/crates/vim/src/normal/toggle_comments.rs index 3b578c44cbed080758e5598bc910ed5431ade956..1df381acbeea2fdc9cc691ebadcc4a429f7745ec 100644 --- a/crates/vim/src/normal/toggle_comments.rs +++ b/crates/vim/src/normal/toggle_comments.rs @@ -1,6 +1,6 @@ use crate::{Vim, motion::Motion, object::Object}; use collections::HashMap; -use editor::{Bias, SelectionEffects, display_map::ToDisplayPoint}; +use editor::{Bias, display_map::ToDisplayPoint}; use gpui::{Context, Window}; use language::SelectionGoal; @@ -18,7 +18,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); selection_starts.insert(selection.id, anchor); @@ -32,7 +32,7 @@ impl Vim { }); }); editor.toggle_comments(&Default::default(), window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = selection_starts.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); @@ -53,7 +53,7 @@ impl Vim { self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { let mut original_positions: HashMap<_, _> = Default::default(); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); original_positions.insert(selection.id, anchor); @@ -61,7 +61,7 @@ impl Vim { }); }); editor.toggle_comments(&Default::default(), window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = original_positions.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); diff --git a/crates/vim/src/normal/yank.rs b/crates/vim/src/normal/yank.rs index 6beb81b2b6d09f2dcd696929b6858af50cb16f90..3525b0d43fbc215fe0d469e1536398177c925653 100644 --- a/crates/vim/src/normal/yank.rs +++ b/crates/vim/src/normal/yank.rs @@ -7,7 +7,7 @@ use crate::{ state::{Mode, Register}, }; use collections::HashMap; -use editor::{ClipboardSelection, Editor, SelectionEffects}; +use editor::{ClipboardSelection, Editor}; use gpui::Context; use gpui::Window; use language::Point; @@ -31,7 +31,7 @@ impl Vim { editor.set_clip_at_line_ends(false, cx); let mut original_positions: HashMap<_, _> = Default::default(); let mut kind = None; - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let original_position = (selection.head(), selection.goal); kind = motion.expand_selection( @@ -51,7 +51,7 @@ impl Vim { }); let Some(kind) = kind else { return }; vim.yank_selections_content(editor, kind, window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|_, selection| { let (head, goal) = original_positions.remove(&selection.id).unwrap(); selection.collapse_to(head, goal); @@ -73,7 +73,7 @@ impl Vim { editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); let mut start_positions: HashMap<_, _> = Default::default(); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around); let start_position = (selection.start, selection.goal); @@ -81,7 +81,7 @@ impl Vim { }); }); vim.yank_selections_content(editor, MotionKind::Exclusive, window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|_, selection| { let (head, goal) = start_positions.remove(&selection.id).unwrap(); selection.collapse_to(head, goal); diff --git a/crates/vim/src/replace.rs b/crates/vim/src/replace.rs index bf0d977531e55565173d3164c15d11f18d31c360..5f407db5cb816a30aa83875d19e48bf4bb856473 100644 --- a/crates/vim/src/replace.rs +++ b/crates/vim/src/replace.rs @@ -5,8 +5,8 @@ use crate::{ state::Mode, }; use editor::{ - Anchor, Bias, Editor, EditorSnapshot, SelectionEffects, ToOffset, ToPoint, - display_map::ToDisplayPoint, + Anchor, Bias, Editor, EditorSnapshot, ToOffset, ToPoint, display_map::ToDisplayPoint, + scroll::Autoscroll, }; use gpui::{Context, Window, actions}; use language::{Point, SelectionGoal}; @@ -72,7 +72,7 @@ impl Vim { editor.edit_with_block_indent(edits.clone(), Vec::new(), cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_anchor_ranges(edits.iter().map(|(range, _)| range.end..range.end)); }); editor.set_clip_at_line_ends(true, cx); @@ -124,7 +124,7 @@ impl Vim { editor.edit(edits, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_ranges(new_selections); }); editor.set_clip_at_line_ends(true, cx); @@ -251,7 +251,7 @@ impl Vim { } if let Some(position) = final_cursor_position { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|_map, selection| { selection.collapse_to(position, SelectionGoal::None); }); diff --git a/crates/vim/src/rewrap.rs b/crates/vim/src/rewrap.rs index e03a3308fca52c6d11766ccb7731cbd6ec7883c4..b5d69ef0ae73d87deb49dde9d852457d910be075 100644 --- a/crates/vim/src/rewrap.rs +++ b/crates/vim/src/rewrap.rs @@ -1,6 +1,6 @@ use crate::{Vim, motion::Motion, object::Object, state::Mode}; use collections::HashMap; -use editor::{Bias, Editor, RewrapOptions, SelectionEffects, display_map::ToDisplayPoint}; +use editor::{Bias, Editor, RewrapOptions, display_map::ToDisplayPoint, scroll::Autoscroll}; use gpui::{Context, Window, actions}; use language::SelectionGoal; @@ -22,7 +22,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context) { }, cx, ); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if let Some(anchor) = positions.remove(&selection.id) { let mut point = anchor.to_display_point(map); @@ -53,7 +53,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); selection_starts.insert(selection.id, anchor); @@ -73,7 +73,7 @@ impl Vim { }, cx, ); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = selection_starts.remove(&selection.id).unwrap(); let mut point = anchor.to_display_point(map); @@ -96,7 +96,7 @@ impl Vim { self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { let mut original_positions: HashMap<_, _> = Default::default(); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); original_positions.insert(selection.id, anchor); @@ -110,7 +110,7 @@ impl Vim { }, cx, ); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let anchor = original_positions.remove(&selection.id).unwrap(); let mut point = anchor.to_display_point(map); diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index 852433bc8e42ebe97d3b0f140139e20d9f8b4d6f..6697742e4d318bb3a790e59e3404cf1f19a8c4ff 100644 --- a/crates/vim/src/surrounds.rs +++ b/crates/vim/src/surrounds.rs @@ -4,7 +4,7 @@ use crate::{ object::Object, state::Mode, }; -use editor::{Bias, movement}; +use editor::{Bias, movement, scroll::Autoscroll}; use gpui::{Context, Window}; use language::BracketPair; @@ -109,7 +109,7 @@ impl Vim { editor.edit(edits, cx); editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { if mode == Mode::VisualBlock { s.select_anchor_ranges(anchors.into_iter().take(1)) } else { @@ -207,7 +207,7 @@ impl Vim { } } - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(anchors); }); edits.sort_by_key(|(range, _)| range.start); @@ -317,7 +317,7 @@ impl Vim { edits.sort_by_key(|(range, _)| range.start); editor.edit(edits, cx); editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_anchor_ranges(stable_anchors); }); }); @@ -375,7 +375,7 @@ impl Vim { anchors.push(start..start) } } - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(anchors); }); editor.set_clip_at_line_ends(true, cx); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 2c2d60004e7aae6771906ff718c73b1dc0539723..6b5d41f12ebf732781f6cb3234924c6ea48e92b5 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -22,8 +22,7 @@ mod visual; use anyhow::Result; use collections::HashMap; use editor::{ - Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, SelectionEffects, - ToPoint, + Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, ToPoint, movement::{self, FindRange}, }; use gpui::{ @@ -964,7 +963,7 @@ impl Vim { } } - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { // we cheat with visual block mode and use multiple cursors. // the cost of this cheat is we need to convert back to a single // cursor whenever vim would. @@ -1164,7 +1163,7 @@ impl Vim { } else { self.update_editor(window, cx, |_, editor, window, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|_, selection| { selection.collapse_to(selection.start, selection.goal) }) @@ -1439,29 +1438,27 @@ impl Vim { Mode::VisualLine | Mode::VisualBlock | Mode::Visual => { self.update_editor(window, cx, |vim, editor, window, cx| { let original_mode = vim.undo_modes.get(transaction_id); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - match original_mode { - Some(Mode::VisualLine) => { - s.move_with(|map, selection| { - selection.collapse_to( - map.prev_line_boundary(selection.start.to_point(map)).1, - SelectionGoal::None, - ) - }); - } - Some(Mode::VisualBlock) => { - let mut first = s.first_anchor(); - first.collapse_to(first.start, first.goal); - s.select_anchors(vec![first]); - } - _ => { - s.move_with(|map, selection| { - selection.collapse_to( - map.clip_at_line_end(selection.start), - selection.goal, - ); - }); - } + editor.change_selections(None, window, cx, |s| match original_mode { + Some(Mode::VisualLine) => { + s.move_with(|map, selection| { + selection.collapse_to( + map.prev_line_boundary(selection.start.to_point(map)).1, + SelectionGoal::None, + ) + }); + } + Some(Mode::VisualBlock) => { + let mut first = s.first_anchor(); + first.collapse_to(first.start, first.goal); + s.select_anchors(vec![first]); + } + _ => { + s.move_with(|map, selection| { + selection.collapse_to( + map.clip_at_line_end(selection.start), + selection.goal, + ); + }); } }); }); @@ -1469,7 +1466,7 @@ impl Vim { } Mode::Normal => { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { selection .collapse_to(map.clip_at_line_end(selection.end), selection.goal) diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 2d72881b7aed3894b48771fa7396ea8597f620e1..29ef3943b57086021844d8f65644fbe24e80392d 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -2,9 +2,10 @@ use std::sync::Arc; use collections::HashMap; use editor::{ - Bias, DisplayPoint, Editor, SelectionEffects, + Bias, DisplayPoint, Editor, display_map::{DisplaySnapshot, ToDisplayPoint}, movement, + scroll::Autoscroll, }; use gpui::{Context, Window, actions}; use language::{Point, Selection, SelectionGoal}; @@ -132,7 +133,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { vim.update_editor(window, cx, |_, editor, window, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { let map = s.display_map(); let ranges = ranges .into_iter() @@ -186,7 +187,7 @@ impl Vim { motion.move_point(map, point, goal, times, &text_layout_details) }) } else { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let was_reversed = selection.reversed; let mut current_head = selection.head(); @@ -258,7 +259,7 @@ impl Vim { ) -> Option<(DisplayPoint, SelectionGoal)>, ) { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { let map = &s.display_map(); let mut head = s.newest_anchor().head().to_display_point(map); let mut tail = s.oldest_anchor().tail().to_display_point(map); @@ -374,7 +375,7 @@ impl Vim { } self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let mut mut_selection = selection.clone(); @@ -453,7 +454,7 @@ impl Vim { ) { self.update_editor(window, cx, |_, editor, window, cx| { editor.split_selection_into_lines(&Default::default(), window, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { (next_line_end(map, cursor, 1), SelectionGoal::None) }); @@ -471,7 +472,7 @@ impl Vim { ) { self.update_editor(window, cx, |_, editor, window, cx| { editor.split_selection_into_lines(&Default::default(), window, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { ( first_non_whitespace(map, false, cursor), @@ -494,7 +495,7 @@ impl Vim { pub fn other_end(&mut self, _: &OtherEnd, window: &mut Window, cx: &mut Context) { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|_, selection| { selection.reversed = !selection.reversed; }); @@ -510,7 +511,7 @@ impl Vim { ) { let mode = self.mode; self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|_, selection| { selection.reversed = !selection.reversed; }); @@ -529,7 +530,7 @@ impl Vim { editor.selections.line_mode = false; editor.transact(window, cx, |editor, window, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { if line_mode { let mut position = selection.head(); @@ -566,7 +567,7 @@ impl Vim { vim.copy_selections_content(editor, kind, window, cx); if line_mode && vim.mode != Mode::VisualBlock { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let end = selection.end.to_point(map); let start = selection.start.to_point(map); @@ -586,7 +587,7 @@ impl Vim { // Fixup cursor position after the deletion editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.move_with(|map, selection| { let mut cursor = selection.head().to_point(map); @@ -612,7 +613,7 @@ impl Vim { // For visual line mode, adjust selections to avoid yanking the next line when on \n if line_mode && vim.mode != Mode::VisualBlock { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { let start = selection.start.to_point(map); let end = selection.end.to_point(map); @@ -633,7 +634,7 @@ impl Vim { MotionKind::Exclusive }; vim.yank_selections_content(editor, kind, window, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.move_with(|map, selection| { if line_mode { selection.start = start_of_line(map, false, selection.start); @@ -686,9 +687,7 @@ impl Vim { } editor.edit(edits, cx); - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { - s.select_ranges(stable_anchors) - }); + editor.change_selections(None, window, cx, |s| s.select_ranges(stable_anchors)); }); }); self.switch_mode(Mode::Normal, false, window, cx); @@ -800,7 +799,7 @@ impl Vim { if direction == Direction::Prev { std::mem::swap(&mut start_selection, &mut end_selection); } - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges([start_selection..end_selection]); }); editor.set_collapse_matches(true); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index ea3f327ff07c54d0d2816947613859ed8bff2b1c..2bbe3d0bcb6d119033b4fcc6ed6794faec914ca7 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -18,7 +18,7 @@ use client::zed_urls; use collections::VecDeque; use debugger_ui::debugger_panel::DebugPanel; use editor::ProposedChangesEditorToolbar; -use editor::{Editor, MultiBuffer}; +use editor::{Editor, MultiBuffer, scroll::Autoscroll}; use futures::future::Either; use futures::{StreamExt, channel::mpsc, select_biased}; use git_ui::git_panel::GitPanel; @@ -1125,7 +1125,7 @@ fn open_log_file(workspace: &mut Workspace, window: &mut Window, cx: &mut Contex editor.update(cx, |editor, cx| { let last_multi_buffer_offset = editor.buffer().read(cx).len(cx); - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_ranges(Some( last_multi_buffer_offset..last_multi_buffer_offset, )); @@ -1774,7 +1774,7 @@ mod tests { use super::*; use assets::Assets; use collections::HashSet; - use editor::{DisplayPoint, Editor, SelectionEffects, display_map::DisplayRow}; + use editor::{DisplayPoint, Editor, display_map::DisplayRow, scroll::Autoscroll}; use gpui::{ Action, AnyWindowHandle, App, AssetSource, BorrowAppContext, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowHandle, actions, @@ -3348,7 +3348,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(10), 0) ..DisplayPoint::new(DisplayRow(10), 0)]) }); @@ -3378,7 +3378,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor3.update(cx, |editor, cx| { - editor.change_selections(Default::default(), window, cx, |s| { + editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(12), 0) ..DisplayPoint::new(DisplayRow(12), 0)]) }); @@ -3593,7 +3593,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(15), 0) ..DisplayPoint::new(DisplayRow(15), 0)]) }) @@ -3604,7 +3604,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(3), 0) ..DisplayPoint::new(DisplayRow(3), 0)]) }); @@ -3615,7 +3615,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(13), 0) ..DisplayPoint::new(DisplayRow(13), 0)]) }) @@ -3627,7 +3627,7 @@ mod tests { .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { editor.transact(window, cx, |editor, window, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(2), 0) ..DisplayPoint::new(DisplayRow(14), 0)]) }); @@ -3640,7 +3640,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + editor.change_selections(None, window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(1), 0) ..DisplayPoint::new(DisplayRow(1), 0)]) }) From a675ca7a1e61da03071755187cf69d1c34fdd152 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 27 Jun 2025 14:31:31 -0600 Subject: [PATCH 034/107] Remove `into SelectionEffects` from .change_selections (#33554) In #32656 I generalized the argument to change selections to allow controling both the scroll and the nav history (and the completion trigger). To avoid conflicting with ongoing debugger cherry-picks I left the argument as an `impl Into<>`, but I think it's clearer to make callers specify what they want here. I converted a lot of `None` arguments to `SelectionEffects::no_scroll()` to be exactly compatible; but I think many people used none as an "i don't care" value in which case Default::default() might be more appropraite Closes #ISSUE Release Notes: - N/A --- crates/agent_ui/src/active_thread.rs | 24 +- crates/agent_ui/src/agent_diff.rs | 25 +- crates/agent_ui/src/inline_assistant.rs | 3 +- crates/agent_ui/src/text_thread_editor.rs | 13 +- crates/assistant_tools/src/edit_file_tool.rs | 4 +- .../collab/src/tests/channel_buffer_tests.rs | 10 +- crates/collab/src/tests/editor_tests.rs | 40 +- crates/collab/src/tests/following_tests.rs | 22 +- crates/collab_ui/src/channel_view.rs | 17 +- .../src/copilot_completion_provider.rs | 13 +- crates/debugger_ui/src/stack_trace_view.rs | 9 +- crates/diagnostics/src/diagnostic_renderer.rs | 3 +- crates/diagnostics/src/diagnostics.rs | 3 +- crates/editor/src/editor.rs | 381 ++++++++++-------- crates/editor/src/editor_tests.rs | 278 +++++++------ crates/editor/src/element.rs | 18 +- crates/editor/src/hover_links.rs | 2 +- crates/editor/src/hover_popover.rs | 4 +- crates/editor/src/inlay_hint_cache.rs | 81 ++-- crates/editor/src/items.rs | 6 +- crates/editor/src/jsx_tag_auto_close.rs | 2 +- crates/editor/src/mouse_context_menu.rs | 6 +- crates/editor/src/proposed_changes_editor.rs | 6 +- crates/editor/src/test.rs | 6 +- crates/editor/src/test/editor_test_context.rs | 6 +- crates/git_ui/src/commit_view.rs | 4 +- crates/git_ui/src/project_diff.rs | 15 +- crates/go_to_line/src/go_to_line.rs | 13 +- .../src/inline_completion_button.rs | 13 +- crates/journal/src/journal.rs | 11 +- crates/language_tools/src/syntax_tree_view.rs | 4 +- .../src/markdown_preview_view.rs | 11 +- crates/outline/src/outline.rs | 11 +- crates/outline_panel/src/outline_panel.rs | 6 +- crates/picker/src/picker.rs | 11 +- crates/project_panel/src/project_panel.rs | 4 +- crates/project_symbols/src/project_symbols.rs | 11 +- crates/repl/src/session.rs | 3 +- crates/rules_library/src/rules_library.rs | 28 +- crates/search/src/buffer_search.rs | 21 +- crates/search/src/project_search.rs | 10 +- crates/tasks_ui/src/modal.rs | 4 +- crates/tasks_ui/src/tasks_ui.rs | 4 +- crates/vim/src/change_list.rs | 4 +- crates/vim/src/command.rs | 28 +- crates/vim/src/helix.rs | 12 +- crates/vim/src/indent.rs | 9 +- crates/vim/src/insert.rs | 4 +- crates/vim/src/motion.rs | 3 +- crates/vim/src/normal.rs | 23 +- crates/vim/src/normal/change.rs | 5 +- crates/vim/src/normal/convert.rs | 12 +- crates/vim/src/normal/delete.rs | 9 +- crates/vim/src/normal/increment.rs | 4 +- crates/vim/src/normal/mark.rs | 7 +- crates/vim/src/normal/paste.rs | 12 +- crates/vim/src/normal/substitute.rs | 4 +- crates/vim/src/normal/toggle_comments.rs | 10 +- crates/vim/src/normal/yank.rs | 10 +- crates/vim/src/replace.rs | 10 +- crates/vim/src/rewrap.rs | 12 +- crates/vim/src/surrounds.rs | 10 +- crates/vim/src/vim.rs | 53 +-- crates/vim/src/visual.rs | 35 +- crates/zed/src/zed.rs | 20 +- 65 files changed, 837 insertions(+), 625 deletions(-) diff --git a/crates/agent_ui/src/active_thread.rs b/crates/agent_ui/src/active_thread.rs index 5f9dfc7ab2ee844d7a8f4b6077861ff24e6d03cf..7ee3b7158b6f9f8db6788c80f93123bd1ad463c6 100644 --- a/crates/agent_ui/src/active_thread.rs +++ b/crates/agent_ui/src/active_thread.rs @@ -19,7 +19,7 @@ use audio::{Audio, Sound}; use collections::{HashMap, HashSet}; use editor::actions::{MoveUp, Paste}; use editor::scroll::Autoscroll; -use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer}; +use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, SelectionEffects}; use gpui::{ AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry, ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla, @@ -689,9 +689,12 @@ fn open_markdown_link( }) .context("Could not find matching symbol")?; - editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_anchor_ranges([symbol_range.start..symbol_range.start]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_anchor_ranges([symbol_range.start..symbol_range.start]), + ); anyhow::Ok(()) }) }) @@ -708,10 +711,15 @@ fn open_markdown_link( .downcast::() .context("Item is not an editor")?; active_editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_ranges([Point::new(line_range.start as u32, 0) - ..Point::new(line_range.start as u32, 0)]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| { + s.select_ranges([Point::new(line_range.start as u32, 0) + ..Point::new(line_range.start as u32, 0)]) + }, + ); anyhow::Ok(()) }) }) diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs index b8e67512e2b069f2a4f19c4903512f385c4eeab7..1a0f3ff27d83a98d343985b3f827aab26afd192a 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -5,7 +5,8 @@ use anyhow::Result; use buffer_diff::DiffHunkStatus; use collections::{HashMap, HashSet}; use editor::{ - Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot, ToPoint, + Direction, Editor, EditorEvent, EditorSettings, MultiBuffer, MultiBufferSnapshot, + SelectionEffects, ToPoint, actions::{GoToHunk, GoToPreviousHunk}, scroll::Autoscroll, }; @@ -171,15 +172,9 @@ impl AgentDiffPane { if let Some(first_hunk) = first_hunk { let first_hunk_start = first_hunk.multi_buffer_range().start; - editor.change_selections( - Some(Autoscroll::fit()), - window, - cx, - |selections| { - selections - .select_anchor_ranges([first_hunk_start..first_hunk_start]); - }, - ) + editor.change_selections(Default::default(), window, cx, |selections| { + selections.select_anchor_ranges([first_hunk_start..first_hunk_start]); + }) } } @@ -242,7 +237,7 @@ impl AgentDiffPane { if let Some(first_hunk) = first_hunk { let first_hunk_start = first_hunk.multi_buffer_range().start; - editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_anchor_ranges([first_hunk_start..first_hunk_start]); }) } @@ -416,7 +411,7 @@ fn update_editor_selection( }; if let Some(target_hunk) = target_hunk { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { let next_hunk_start = target_hunk.multi_buffer_range().start; selections.select_anchor_ranges([next_hunk_start..next_hunk_start]); }) @@ -1544,7 +1539,7 @@ impl AgentDiff { let first_hunk_start = first_hunk.multi_buffer_range().start; editor.change_selections( - Some(Autoscroll::center()), + SelectionEffects::scroll(Autoscroll::center()), window, cx, |selections| { @@ -1868,7 +1863,7 @@ mod tests { // Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) }); }); @@ -2124,7 +2119,7 @@ mod tests { // Rejecting a hunk also moves the cursor to the next hunk, possibly cycling if it's at the end. editor1.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) }); }); diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs index 6e77e764a5ed172f0948d7d76f476377cafd04b7..c9c173a68be5191e77690e826378ca52d3db9684 100644 --- a/crates/agent_ui/src/inline_assistant.rs +++ b/crates/agent_ui/src/inline_assistant.rs @@ -18,6 +18,7 @@ use agent_settings::AgentSettings; use anyhow::{Context as _, Result}; use client::telemetry::Telemetry; use collections::{HashMap, HashSet, VecDeque, hash_map}; +use editor::SelectionEffects; use editor::{ Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint, @@ -1159,7 +1160,7 @@ impl InlineAssistant { let position = assist.range.start; editor.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_anchor_ranges([position..position]) }); diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 645bc451fcb8fbb91d05eb0bfe72814ea630c988..dcb239a46ddec79d7aa52c4180cb511e8b74ac71 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -21,7 +21,6 @@ use editor::{ BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint, }, - scroll::Autoscroll, }; use editor::{FoldPlaceholder, display_map::CreaseId}; use fs::Fs; @@ -389,7 +388,7 @@ impl TextThreadEditor { cursor..cursor }; self.editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges([new_selection]) }); }); @@ -449,8 +448,7 @@ impl TextThreadEditor { if let Some(command) = self.slash_commands.command(name, cx) { self.editor.update(cx, |editor, cx| { editor.transact(window, cx, |editor, window, cx| { - editor - .change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel()); + editor.change_selections(Default::default(), window, cx, |s| s.try_cancel()); let snapshot = editor.buffer().read(cx).snapshot(cx); let newest_cursor = editor.selections.newest::(cx).head(); if newest_cursor.column > 0 @@ -1583,7 +1581,7 @@ impl TextThreadEditor { self.editor.update(cx, |editor, cx| { editor.transact(window, cx, |this, window, cx| { - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(selections); }); this.insert("", window, cx); @@ -3141,6 +3139,7 @@ pub fn make_lsp_adapter_delegate( #[cfg(test)] mod tests { use super::*; + use editor::SelectionEffects; use fs::FakeFs; use gpui::{App, TestAppContext, VisualTestContext}; use indoc::indoc; @@ -3366,7 +3365,9 @@ mod tests { ) { context_editor.update_in(cx, |context_editor, window, cx| { context_editor.editor.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([range])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([range]) + }); }); context_editor.copy(&Default::default(), window, cx); diff --git a/crates/assistant_tools/src/edit_file_tool.rs b/crates/assistant_tools/src/edit_file_tool.rs index fcf82856922c2e1c78345cc129aaea871a63ecfa..8c7728b4b72c9aa52c717e58fbdd63591dd88f0f 100644 --- a/crates/assistant_tools/src/edit_file_tool.rs +++ b/crates/assistant_tools/src/edit_file_tool.rs @@ -10,7 +10,7 @@ use assistant_tool::{ ToolUseStatus, }; use buffer_diff::{BufferDiff, BufferDiffSnapshot}; -use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey, scroll::Autoscroll}; +use editor::{Editor, EditorMode, MinimapVisibility, MultiBuffer, PathKey}; use futures::StreamExt; use gpui::{ Animation, AnimationExt, AnyWindowHandle, App, AppContext, AsyncApp, Entity, Task, @@ -823,7 +823,7 @@ impl ToolCard for EditFileToolCard { let first_hunk_start = first_hunk.multi_buffer_range().start; editor.change_selections( - Some(Autoscroll::fit()), + Default::default(), window, cx, |selections| { diff --git a/crates/collab/src/tests/channel_buffer_tests.rs b/crates/collab/src/tests/channel_buffer_tests.rs index 4069f61f90b48bfedfd4780f0865a061e4ab6971..0b331ff1e66279f5e2f5e52f9d83f0eaca6cfcdb 100644 --- a/crates/collab/src/tests/channel_buffer_tests.rs +++ b/crates/collab/src/tests/channel_buffer_tests.rs @@ -178,7 +178,7 @@ async fn test_channel_notes_participant_indices( channel_view_a.update_in(cx_a, |notes, window, cx| { notes.editor.update(cx, |editor, cx| { editor.insert("a", window, cx); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges(vec![0..1]); }); }); @@ -188,7 +188,7 @@ async fn test_channel_notes_participant_indices( notes.editor.update(cx, |editor, cx| { editor.move_down(&Default::default(), window, cx); editor.insert("b", window, cx); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges(vec![1..2]); }); }); @@ -198,7 +198,7 @@ async fn test_channel_notes_participant_indices( notes.editor.update(cx, |editor, cx| { editor.move_down(&Default::default(), window, cx); editor.insert("c", window, cx); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges(vec![2..3]); }); }); @@ -273,12 +273,12 @@ async fn test_channel_notes_participant_indices( .unwrap(); editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges(vec![0..1]); }); }); editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges(vec![2..3]); }); }); diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 7a51caefa1c2f7f6a3e7f702ae9594b790760d7d..2cc3ca76d1b639cc479cb44cde93a73570d5eb7f 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -4,7 +4,7 @@ use crate::{ }; use call::ActiveCall; use editor::{ - DocumentColorsRenderMode, Editor, EditorSettings, RowInfo, + DocumentColorsRenderMode, Editor, EditorSettings, RowInfo, SelectionEffects, actions::{ ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, ExpandMacroRecursively, MoveToEnd, Redo, Rename, SelectAll, ToggleCodeActions, Undo, @@ -348,7 +348,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu // Type a completion trigger character as the guest. editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input(".", window, cx); }); cx_b.focus(&editor_b); @@ -461,7 +463,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu // Now we do a second completion, this time to ensure that documentation/snippets are // resolved editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([46..46])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([46..46]) + }); editor.handle_input("; a", window, cx); editor.handle_input(".", window, cx); }); @@ -613,7 +617,7 @@ async fn test_collaborating_with_code_actions( // Move cursor to a location that contains code actions. editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 31)..Point::new(1, 31)]) }); }); @@ -817,7 +821,9 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T // Move cursor to a location that can be renamed. let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([7..7])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([7..7]) + }); editor.rename(&Rename, window, cx).unwrap() }); @@ -863,7 +869,9 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T editor.cancel(&editor::actions::Cancel, window, cx); }); let prepare_rename = editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([7..8])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([7..8]) + }); editor.rename(&Rename, window, cx).unwrap() }); @@ -1364,7 +1372,9 @@ async fn test_on_input_format_from_host_to_guest( // Type a on type formatting trigger character as the guest. cx_a.focus(&editor_a); editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input(">", window, cx); }); @@ -1460,7 +1470,9 @@ async fn test_on_input_format_from_guest_to_host( // Type a on type formatting trigger character as the guest. cx_b.focus(&editor_b); editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input(":", window, cx); }); @@ -1697,7 +1709,9 @@ async fn test_mutual_editor_inlay_hint_cache_update( let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1; editor_b.update_in(cx_b, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone())); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13].clone()) + }); editor.handle_input(":", window, cx); }); cx_b.focus(&editor_b); @@ -1718,7 +1732,9 @@ async fn test_mutual_editor_inlay_hint_cache_update( let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1; editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input("a change to increment both buffers' versions", window, cx); }); cx_a.focus(&editor_a); @@ -2121,7 +2137,9 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo }); editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13].clone())); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13].clone()) + }); editor.handle_input(":", window, cx); }); color_request_handle.next().await.unwrap(); diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 99f9b3350512f8d7eb126cb7a427979ab360d509..a77112213f195190e613c2382300bfbbeca70066 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -6,7 +6,7 @@ use collab_ui::{ channel_view::ChannelView, notifications::project_shared_notification::ProjectSharedNotification, }; -use editor::{Editor, MultiBuffer, PathKey}; +use editor::{Editor, MultiBuffer, PathKey, SelectionEffects}; use gpui::{ AppContext as _, BackgroundExecutor, BorrowAppContext, Entity, SharedString, TestAppContext, VisualContext, VisualTestContext, point, @@ -376,7 +376,9 @@ async fn test_basic_following( // Changes to client A's editor are reflected on client B. editor_a1.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([1..1, 2..2]) + }); }); executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); executor.run_until_parked(); @@ -393,7 +395,9 @@ async fn test_basic_following( editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO")); editor_a1.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([3..3])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([3..3]) + }); editor.set_scroll_position(point(0., 100.), window, cx); }); executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); @@ -1647,7 +1651,9 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T // b should follow a to position 1 editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([1..1])) + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([1..1]) + }) }); cx_a.executor() .advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); @@ -1667,7 +1673,9 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T // b should not follow a to position 2 editor_a.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([2..2])) + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([2..2]) + }) }); cx_a.executor() .advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); @@ -1968,7 +1976,7 @@ async fn test_following_to_channel_notes_without_a_shared_project( assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); notes.editor.update(cx, |editor, cx| { editor.insert("Hello from A.", window, cx); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges(vec![3..4]); }); }); @@ -2109,7 +2117,7 @@ async fn test_following_after_replacement(cx_a: &mut TestAppContext, cx_b: &mut workspace.add_item_to_center(Box::new(editor.clone()) as _, window, cx) }); editor.update_in(cx_a, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::row_range(4..4)]); }) }); diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 80cc504308b30579d80e42e35e3267117a8bc456..c872f99aa10ee160ed499621d9aceb2aa7c06a05 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -7,8 +7,8 @@ use client::{ }; use collections::HashMap; use editor::{ - CollaborationHub, DisplayPoint, Editor, EditorEvent, display_map::ToDisplayPoint, - scroll::Autoscroll, + CollaborationHub, DisplayPoint, Editor, EditorEvent, SelectionEffects, + display_map::ToDisplayPoint, scroll::Autoscroll, }; use gpui::{ AnyView, App, ClipboardItem, Context, Entity, EventEmitter, Focusable, Pixels, Point, Render, @@ -260,9 +260,16 @@ impl ChannelView { .find(|item| &Channel::slug(&item.text).to_lowercase() == &position) { self.editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| { - s.replace_cursors_with(|map| vec![item.range.start.to_display_point(map)]) - }) + editor.change_selections( + SelectionEffects::scroll(Autoscroll::focused()), + window, + cx, + |s| { + s.replace_cursors_with(|map| { + vec![item.range.start.to_display_point(map)] + }) + }, + ) }); return; } diff --git a/crates/copilot/src/copilot_completion_provider.rs b/crates/copilot/src/copilot_completion_provider.rs index ff636178753b11bbe3be920a27a27a5c467cef5e..8dc04622f9020c2fe175304764157b409c7936c1 100644 --- a/crates/copilot/src/copilot_completion_provider.rs +++ b/crates/copilot/src/copilot_completion_provider.rs @@ -264,7 +264,8 @@ fn common_prefix, T2: Iterator>(a: T1, b: mod tests { use super::*; use editor::{ - Editor, ExcerptRange, MultiBuffer, test::editor_lsp_test_context::EditorLspTestContext, + Editor, ExcerptRange, MultiBuffer, SelectionEffects, + test::editor_lsp_test_context::EditorLspTestContext, }; use fs::FakeFs; use futures::StreamExt; @@ -478,7 +479,7 @@ mod tests { // Reset the editor to verify how suggestions behave when tabbing on leading indentation. cx.update_editor(|editor, window, cx| { editor.set_text("fn foo() {\n \n}", window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 2)..Point::new(1, 2)]) }); }); @@ -767,7 +768,7 @@ mod tests { ); _ = editor.update(cx, |editor, window, cx| { // Ensure copilot suggestions are shown for the first excerpt. - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 5)..Point::new(1, 5)]) }); editor.next_edit_prediction(&Default::default(), window, cx); @@ -793,7 +794,7 @@ mod tests { ); _ = editor.update(cx, |editor, window, cx| { // Move to another excerpt, ensuring the suggestion gets cleared. - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(4, 5)..Point::new(4, 5)]) }); assert!(!editor.has_active_inline_completion()); @@ -1019,7 +1020,7 @@ mod tests { ); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)]) }); editor.refresh_inline_completion(true, false, window, cx); @@ -1029,7 +1030,7 @@ mod tests { assert!(copilot_requests.try_next().is_err()); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(5, 0)..Point::new(5, 0)]) }); editor.refresh_inline_completion(true, false, window, cx); diff --git a/crates/debugger_ui/src/stack_trace_view.rs b/crates/debugger_ui/src/stack_trace_view.rs index 675522e99996b276b5f62eeb88297dfe7d592579..aef053df4a1ea930fb09a779e08afecfa08ddde9 100644 --- a/crates/debugger_ui/src/stack_trace_view.rs +++ b/crates/debugger_ui/src/stack_trace_view.rs @@ -4,7 +4,7 @@ use collections::HashMap; use dap::StackFrameId; use editor::{ Anchor, Bias, DebugStackFrameLine, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, - RowHighlightOptions, ToPoint, scroll::Autoscroll, + RowHighlightOptions, SelectionEffects, ToPoint, scroll::Autoscroll, }; use gpui::{ AnyView, App, AppContext, Entity, EventEmitter, Focusable, IntoElement, Render, SharedString, @@ -99,10 +99,11 @@ impl StackTraceView { if frame_anchor.excerpt_id != editor.selections.newest_anchor().head().excerpt_id { - let auto_scroll = - Some(Autoscroll::center().for_anchor(frame_anchor)); + let effects = SelectionEffects::scroll( + Autoscroll::center().for_anchor(frame_anchor), + ); - editor.change_selections(auto_scroll, window, cx, |selections| { + editor.change_selections(effects, window, cx, |selections| { let selection_id = selections.new_selection_id(); let selection = Selection { diff --git a/crates/diagnostics/src/diagnostic_renderer.rs b/crates/diagnostics/src/diagnostic_renderer.rs index 9524f97ff1e14599576df549844ee7c164d6d017..77bb249733f612ede3017e1cff592927b40e8d43 100644 --- a/crates/diagnostics/src/diagnostic_renderer.rs +++ b/crates/diagnostics/src/diagnostic_renderer.rs @@ -4,7 +4,6 @@ use editor::{ Anchor, Editor, EditorSnapshot, ToOffset, display_map::{BlockContext, BlockPlacement, BlockProperties, BlockStyle}, hover_popover::diagnostics_markdown_style, - scroll::Autoscroll, }; use gpui::{AppContext, Entity, Focusable, WeakEntity}; use language::{BufferId, Diagnostic, DiagnosticEntry}; @@ -311,7 +310,7 @@ impl DiagnosticBlock { let range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot); editor.unfold_ranges(&[range.start..range.end], true, false, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges([range.start..range.start]); }); window.focus(&editor.focus_handle(cx)); diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 4f66a5a8839ddd8a3a2405a2b57114b73a1cf9f8..8b49c536245a2509cb73254eca8de6d1be1cfd75 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -12,7 +12,6 @@ use diagnostic_renderer::DiagnosticBlock; use editor::{ DEFAULT_MULTIBUFFER_CONTEXT, Editor, EditorEvent, ExcerptRange, MultiBuffer, PathKey, display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId}, - scroll::Autoscroll, }; use futures::future::join_all; use gpui::{ @@ -626,7 +625,7 @@ impl ProjectDiagnosticsEditor { if let Some(anchor_range) = anchor_ranges.first() { let range_to_select = anchor_range.start..anchor_range.start; this.editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges([range_to_select]); }) }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 376aa60ba42f275acbdb8fe5e1f59fdf1d7be711..48ceaec18b40b5453901d804c8a06efae5b122b5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1262,6 +1262,19 @@ impl Default for SelectionHistoryMode { } #[derive(Debug)] +/// SelectionEffects controls the side-effects of updating the selection. +/// +/// The default behaviour does "what you mostly want": +/// - it pushes to the nav history if the cursor moved by >10 lines +/// - it re-triggers completion requests +/// - it scrolls to fit +/// +/// You might want to modify these behaviours. For example when doing a "jump" +/// like go to definition, we always want to add to nav history; but when scrolling +/// in vim mode we never do. +/// +/// Similarly, you might want to disable scrolling if you don't want the viewport to +/// move. pub struct SelectionEffects { nav_history: Option, completions: bool, @@ -3164,12 +3177,11 @@ impl Editor { /// effects of selection change occur at the end of the transaction. pub fn change_selections( &mut self, - effects: impl Into, + effects: SelectionEffects, window: &mut Window, cx: &mut Context, change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R, ) -> R { - let effects = effects.into(); if let Some(state) = &mut self.deferred_selection_effects_state { state.effects.scroll = effects.scroll.or(state.effects.scroll); state.effects.completions = effects.completions; @@ -3449,8 +3461,13 @@ impl Editor { }; let selections_count = self.selections.count(); + let effects = if auto_scroll { + SelectionEffects::default() + } else { + SelectionEffects::no_scroll() + }; - self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| { + self.change_selections(effects, window, cx, |s| { if let Some(point_to_delete) = point_to_delete { s.delete(point_to_delete); @@ -3488,13 +3505,18 @@ impl Editor { .buffer_snapshot .anchor_before(position.to_point(&display_map)); - self.change_selections(Some(Autoscroll::newest()), window, cx, |s| { - s.clear_disjoint(); - s.set_pending_anchor_range( - pointer_position..pointer_position, - SelectMode::Character, - ); - }); + self.change_selections( + SelectionEffects::scroll(Autoscroll::newest()), + window, + cx, + |s| { + s.clear_disjoint(); + s.set_pending_anchor_range( + pointer_position..pointer_position, + SelectMode::Character, + ); + }, + ); }; let tail = self.selections.newest::(cx).tail(); @@ -3609,7 +3631,7 @@ impl Editor { pending.reversed = false; } - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.set_pending(pending, mode); }); } else { @@ -3625,7 +3647,7 @@ impl Editor { self.columnar_selection_state.take(); if self.selections.pending_anchor().is_some() { let selections = self.selections.all::(cx); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select(selections); s.clear_pending(); }); @@ -3699,7 +3721,7 @@ impl Editor { _ => selection_ranges, }; - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(ranges); }); cx.notify(); @@ -3739,7 +3761,7 @@ impl Editor { } if self.mode.is_full() - && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel()) + && self.change_selections(Default::default(), window, cx, |s| s.try_cancel()) { return; } @@ -4542,9 +4564,7 @@ impl Editor { }) .collect(); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(new_selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(new_selections)); this.refresh_inline_completion(true, false, window, cx); }); } @@ -4573,7 +4593,7 @@ impl Editor { self.transact(window, cx, |editor, window, cx| { editor.edit(edits, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let mut index = 0; s.move_cursors_with(|map, _, _| { let row = rows[index]; @@ -4635,7 +4655,7 @@ impl Editor { self.transact(window, cx, |editor, window, cx| { editor.edit(edits, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let mut index = 0; s.move_cursors_with(|map, _, _| { let row = rows[index]; @@ -4712,7 +4732,7 @@ impl Editor { anchors }); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select_anchors(selection_anchors); }); @@ -4856,7 +4876,7 @@ impl Editor { .collect(); drop(buffer); - self.change_selections(None, window, cx, |selections| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select(new_selections) }); } @@ -7160,7 +7180,7 @@ impl Editor { self.unfold_ranges(&[target..target], true, false, cx); // Note that this is also done in vim's handler of the Tab action. self.change_selections( - Some(Autoscroll::newest()), + SelectionEffects::scroll(Autoscroll::newest()), window, cx, |selections| { @@ -7205,7 +7225,7 @@ impl Editor { buffer.edit(edits.iter().cloned(), None, cx) }); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_anchor_ranges([last_edit_end..last_edit_end]); }); @@ -7252,9 +7272,14 @@ impl Editor { match &active_inline_completion.completion { InlineCompletion::Move { target, .. } => { let target = *target; - self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| { - selections.select_anchor_ranges([target..target]); - }); + self.change_selections( + SelectionEffects::scroll(Autoscroll::newest()), + window, + cx, + |selections| { + selections.select_anchor_ranges([target..target]); + }, + ); } InlineCompletion::Edit { edits, .. } => { // Find an insertion that starts at the cursor position. @@ -7855,9 +7880,12 @@ impl Editor { this.entry("Run to cursor", None, move |window, cx| { weak_editor .update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |s| { - s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]) - }); + editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]), + ); }) .ok(); @@ -9398,7 +9426,7 @@ impl Editor { .collect::>() }); if let Some(tabstop) = tabstops.first() { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { // Reverse order so that the first range is the newest created selection. // Completions will use it and autoscroll will prioritize it. s.select_ranges(tabstop.ranges.iter().rev().cloned()); @@ -9516,7 +9544,7 @@ impl Editor { } } if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { // Reverse order so that the first range is the newest created selection. // Completions will use it and autoscroll will prioritize it. s.select_ranges(current_ranges.iter().rev().cloned()) @@ -9606,9 +9634,7 @@ impl Editor { } } - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); this.insert("", window, cx); let empty_str: Arc = Arc::from(""); for (buffer, edits) in linked_ranges { @@ -9644,7 +9670,7 @@ impl Editor { pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = movement::right(map, selection.head()); @@ -9787,9 +9813,7 @@ impl Editor { self.transact(window, cx, |this, window, cx| { this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); this.refresh_inline_completion(true, false, window, cx); }); } @@ -9822,9 +9846,7 @@ impl Editor { self.transact(window, cx, |this, window, cx| { this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); }); } @@ -9977,9 +9999,7 @@ impl Editor { ); }); let selections = this.selections.all::(cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); }); } @@ -10004,9 +10024,7 @@ impl Editor { buffer.autoindent_ranges(selections, cx); }); let selections = this.selections.all::(cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); }); } @@ -10087,7 +10105,7 @@ impl Editor { }) .collect(); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(new_selections); }); }); @@ -10153,7 +10171,7 @@ impl Editor { } } - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges(cursor_positions) }); }); @@ -10740,7 +10758,7 @@ impl Editor { }) .collect(); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(new_selections); }); @@ -11091,7 +11109,7 @@ impl Editor { buffer.edit(edits, None, cx); }); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(new_selections); }); @@ -11127,7 +11145,7 @@ impl Editor { this.buffer.update(cx, |buffer, cx| { buffer.edit(edits, None, cx); }); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges([last_edit_start..last_edit_end]); }); }); @@ -11329,7 +11347,7 @@ impl Editor { } }); this.fold_creases(refold_creases, true, window, cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(new_selections); }) }); @@ -11430,9 +11448,7 @@ impl Editor { } }); this.fold_creases(refold_creases, true, window, cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(new_selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(new_selections)); }); } @@ -11440,7 +11456,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); let text_layout_details = &self.text_layout_details(window); self.transact(window, cx, |this, window, cx| { - let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + let edits = this.change_selections(Default::default(), window, cx, |s| { let mut edits: Vec<(Range, String)> = Default::default(); s.move_with(|display_map, selection| { if !selection.is_empty() { @@ -11488,7 +11504,7 @@ impl Editor { this.buffer .update(cx, |buffer, cx| buffer.edit(edits, None, cx)); let selections = this.selections.all::(cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(selections); }); }); @@ -11744,7 +11760,7 @@ impl Editor { } self.transact(window, cx, |this, window, cx| { - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.select(selections); }); this.insert("", window, cx); @@ -11760,7 +11776,7 @@ impl Editor { pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|snapshot, sel| { if sel.is_empty() { sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row())) @@ -11964,9 +11980,7 @@ impl Editor { }); let selections = this.selections.all::(cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); } else { this.insert(&clipboard_text, window, cx); } @@ -12005,7 +12019,7 @@ impl Editor { if let Some((selections, _)) = self.selection_history.transaction(transaction_id).cloned() { - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_anchors(selections.to_vec()); }); } else { @@ -12035,7 +12049,7 @@ impl Editor { if let Some((_, Some(selections))) = self.selection_history.transaction(transaction_id).cloned() { - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_anchors(selections.to_vec()); }); } else { @@ -12065,7 +12079,7 @@ impl Editor { pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let cursor = if selection.is_empty() { movement::left(map, selection.start) @@ -12079,14 +12093,14 @@ impl Editor { pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None)); }) } pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let cursor = if selection.is_empty() { movement::right(map, selection.end) @@ -12100,7 +12114,7 @@ impl Editor { pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None)); }) } @@ -12121,7 +12135,7 @@ impl Editor { let selection_count = self.selections.count(); let first_selection = self.selections.first_anchor(); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12162,7 +12176,7 @@ impl Editor { let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12199,7 +12213,7 @@ impl Editor { let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12225,7 +12239,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details) }) @@ -12240,7 +12254,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details) }) @@ -12261,7 +12275,7 @@ impl Editor { let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::up_by_rows(map, head, row_count, goal, false, text_layout_details) }) @@ -12299,15 +12313,15 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let autoscroll = if action.center_cursor { - Autoscroll::center() + let effects = if action.center_cursor { + SelectionEffects::scroll(Autoscroll::center()) } else { - Autoscroll::fit() + SelectionEffects::default() }; let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(autoscroll), window, cx, |s| { + self.change_selections(effects, window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12328,7 +12342,7 @@ impl Editor { pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::up(map, head, goal, false, text_layout_details) }) @@ -12349,7 +12363,7 @@ impl Editor { let selection_count = self.selections.count(); let first_selection = self.selections.first_anchor(); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12385,7 +12399,7 @@ impl Editor { let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::down_by_rows(map, head, row_count, goal, false, text_layout_details) }) @@ -12423,14 +12437,14 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - let autoscroll = if action.center_cursor { - Autoscroll::center() + let effects = if action.center_cursor { + SelectionEffects::scroll(Autoscroll::center()) } else { - Autoscroll::fit() + SelectionEffects::default() }; let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(autoscroll), window, cx, |s| { + self.change_selections(effects, window, cx, |s| { s.move_with(|map, selection| { if !selection.is_empty() { selection.goal = SelectionGoal::None; @@ -12451,7 +12465,7 @@ impl Editor { pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let text_layout_details = &self.text_layout_details(window); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, goal| { movement::down(map, head, goal, false, text_layout_details) }) @@ -12509,7 +12523,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, head, _| { ( movement::previous_word_start(map, head), @@ -12526,7 +12540,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, head, _| { ( movement::previous_subword_start(map, head), @@ -12543,7 +12557,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::previous_word_start(map, head), @@ -12560,7 +12574,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::previous_subword_start(map, head), @@ -12579,7 +12593,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { this.select_autoclose_pair(window, cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = if action.ignore_newlines { @@ -12604,7 +12618,7 @@ impl Editor { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { this.select_autoclose_pair(window, cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = movement::previous_subword_start(map, selection.head()); @@ -12623,7 +12637,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, head, _| { (movement::next_word_end(map, head), SelectionGoal::None) }); @@ -12637,7 +12651,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, head, _| { (movement::next_subword_end(map, head), SelectionGoal::None) }); @@ -12651,7 +12665,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { (movement::next_word_end(map, head), SelectionGoal::None) }); @@ -12665,7 +12679,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { (movement::next_subword_end(map, head), SelectionGoal::None) }); @@ -12680,7 +12694,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = if action.ignore_newlines { @@ -12704,7 +12718,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { let cursor = movement::next_subword_end(map, selection.head()); @@ -12723,7 +12737,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, head, _| { ( movement::indented_line_beginning( @@ -12745,7 +12759,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::indented_line_beginning( @@ -12768,7 +12782,7 @@ impl Editor { ) { self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx); self.transact(window, cx, |this, window, cx| { - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_with(|_, selection| { selection.reversed = true; }); @@ -12793,7 +12807,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, head, _| { ( movement::line_end(map, head, action.stop_at_soft_wraps), @@ -12810,7 +12824,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::line_end(map, head, action.stop_at_soft_wraps), @@ -12869,7 +12883,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::start_of_paragraph(map, selection.head(), 1), @@ -12890,7 +12904,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::end_of_paragraph(map, selection.head(), 1), @@ -12911,7 +12925,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::start_of_paragraph(map, head, 1), @@ -12932,7 +12946,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::end_of_paragraph(map, head, 1), @@ -12953,7 +12967,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::start_of_excerpt( @@ -12978,7 +12992,7 @@ impl Editor { return; } - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::start_of_excerpt( @@ -13003,7 +13017,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::end_of_excerpt( @@ -13028,7 +13042,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { selection.collapse_to( movement::end_of_excerpt( @@ -13053,7 +13067,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev), @@ -13074,7 +13088,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next), @@ -13095,7 +13109,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next), @@ -13116,7 +13130,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_heads_with(|map, head, _| { ( movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev), @@ -13137,7 +13151,7 @@ impl Editor { return; } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges(vec![0..0]); }); } @@ -13151,7 +13165,7 @@ impl Editor { let mut selection = self.selections.last::(cx); selection.set_head(Point::zero(), SelectionGoal::None); self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select(vec![selection]); }); } @@ -13163,7 +13177,7 @@ impl Editor { } self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let cursor = self.buffer.read(cx).read(cx).len(); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges(vec![cursor..cursor]) }); } @@ -13229,7 +13243,7 @@ impl Editor { let buffer = self.buffer.read(cx).snapshot(cx); let mut selection = self.selections.first::(cx); selection.set_head(buffer.len(), SelectionGoal::None); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select(vec![selection]); }); } @@ -13237,7 +13251,7 @@ impl Editor { pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); let end = self.buffer.read(cx).read(cx).len(); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(vec![0..end]); }); } @@ -13253,7 +13267,7 @@ impl Editor { selection.end = cmp::min(max_point, Point::new(rows.end.0, 0)); selection.reversed = false; } - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select(selections); }); } @@ -13290,7 +13304,7 @@ impl Editor { } } } - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges(new_selection_ranges); }); } @@ -13438,7 +13452,7 @@ impl Editor { } } - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select(final_selections); }); @@ -13476,7 +13490,12 @@ impl Editor { auto_scroll.is_some(), cx, ); - self.change_selections(auto_scroll, window, cx, |s| { + let effects = if let Some(scroll) = auto_scroll { + SelectionEffects::scroll(scroll) + } else { + SelectionEffects::no_scroll() + }; + self.change_selections(effects, window, cx, |s| { if replace_newest { s.delete(s.newest_anchor().id); } @@ -13688,7 +13707,7 @@ impl Editor { } self.unfold_ranges(&new_selections.clone(), false, false, cx); - self.change_selections(None, window, cx, |selections| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges(new_selections) }); @@ -13859,7 +13878,7 @@ impl Editor { let selections = self.selections.disjoint_anchors(); match selections.first() { Some(first) if selections.len() >= 2 => { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges([first.range()]); }); } @@ -13883,7 +13902,7 @@ impl Editor { let selections = self.selections.disjoint_anchors(); match selections.last() { Some(last) if selections.len() >= 2 => { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges([last.range()]); }); } @@ -14162,9 +14181,7 @@ impl Editor { } drop(snapshot); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { - s.select(selections) - }); + this.change_selections(Default::default(), window, cx, |s| s.select(selections)); let selections = this.selections.all::(cx); let selections_on_single_row = selections.windows(2).all(|selections| { @@ -14183,7 +14200,7 @@ impl Editor { if advance_downwards { let snapshot = this.buffer.read(cx).snapshot(cx); - this.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + this.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|display_snapshot, display_point, _| { let mut point = display_point.to_point(display_snapshot); point.row += 1; @@ -14250,7 +14267,7 @@ impl Editor { .collect::>(); if selected_larger_symbol { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select(new_selections); }); } @@ -14350,7 +14367,7 @@ impl Editor { if selected_larger_node { self.select_syntax_node_history.disable_clearing = true; - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select(new_selections.clone()); }); self.select_syntax_node_history.disable_clearing = false; @@ -14396,7 +14413,7 @@ impl Editor { } self.select_syntax_node_history.disable_clearing = true; - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select(selections.to_vec()); }); self.select_syntax_node_history.disable_clearing = false; @@ -14661,7 +14678,7 @@ impl Editor { cx: &mut Context, ) { self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.move_offsets_with(|snapshot, selection| { let Some(enclosing_bracket_ranges) = snapshot.enclosing_bracket_ranges(selection.start..selection.end) @@ -14722,9 +14739,12 @@ impl Editor { self.selection_history.mode = SelectionHistoryMode::Undoing; self.with_selection_effects_deferred(window, cx, |this, window, cx| { this.end_selection(window, cx); - this.change_selections(Some(Autoscroll::newest()), window, cx, |s| { - s.select_anchors(entry.selections.to_vec()) - }); + this.change_selections( + SelectionEffects::scroll(Autoscroll::newest()), + window, + cx, + |s| s.select_anchors(entry.selections.to_vec()), + ); }); self.selection_history.mode = SelectionHistoryMode::Normal; @@ -14745,9 +14765,12 @@ impl Editor { self.selection_history.mode = SelectionHistoryMode::Redoing; self.with_selection_effects_deferred(window, cx, |this, window, cx| { this.end_selection(window, cx); - this.change_selections(Some(Autoscroll::newest()), window, cx, |s| { - s.select_anchors(entry.selections.to_vec()) - }); + this.change_selections( + SelectionEffects::scroll(Autoscroll::newest()), + window, + cx, + |s| s.select_anchors(entry.selections.to_vec()), + ); }); self.selection_history.mode = SelectionHistoryMode::Normal; @@ -14980,7 +15003,7 @@ impl Editor { let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else { return; }; - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges(vec![ next_diagnostic.range.start..next_diagnostic.range.start, ]) @@ -15022,7 +15045,7 @@ impl Editor { let autoscroll = Autoscroll::center(); self.unfold_ranges(&[destination..destination], false, false, cx); - self.change_selections(Some(autoscroll), window, cx, |s| { + self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| { s.select_ranges([destination..destination]); }); } @@ -15085,7 +15108,7 @@ impl Editor { .next_change(1, Direction::Next) .map(|s| s.to_vec()) { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { let map = s.display_map(); s.select_display_ranges(selections.iter().map(|a| { let point = a.to_display_point(&map); @@ -15106,7 +15129,7 @@ impl Editor { .next_change(1, Direction::Prev) .map(|s| s.to_vec()) { - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { let map = s.display_map(); s.select_display_ranges(selections.iter().map(|a| { let point = a.to_display_point(&map); @@ -15726,10 +15749,16 @@ impl Editor { match multibuffer_selection_mode { MultibufferSelectionMode::First => { if let Some(first_range) = ranges.first() { - editor.change_selections(None, window, cx, |selections| { - selections.clear_disjoint(); - selections.select_anchor_ranges(std::iter::once(first_range.clone())); - }); + editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |selections| { + selections.clear_disjoint(); + selections + .select_anchor_ranges(std::iter::once(first_range.clone())); + }, + ); } editor.highlight_background::( &ranges, @@ -15738,10 +15767,15 @@ impl Editor { ); } MultibufferSelectionMode::All => { - editor.change_selections(None, window, cx, |selections| { - selections.clear_disjoint(); - selections.select_anchor_ranges(ranges); - }); + editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |selections| { + selections.clear_disjoint(); + selections.select_anchor_ranges(ranges); + }, + ); } } editor.register_buffers_with_language_servers(cx); @@ -15875,7 +15909,7 @@ impl Editor { if rename_selection_range.end > old_name.len() { editor.select_all(&SelectAll, window, cx); } else { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges([rename_selection_range]); }); } @@ -16048,7 +16082,7 @@ impl Editor { .min(rename_range.end); drop(snapshot); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(vec![cursor_in_editor..cursor_in_editor]) }); } else { @@ -16731,7 +16765,7 @@ impl Editor { pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context) { if self.selection_mark_mode { - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|_, sel| { sel.collapse_to(sel.head(), SelectionGoal::None); }); @@ -16747,7 +16781,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|_, sel| { if sel.start != sel.end { sel.reversed = !sel.reversed @@ -17486,7 +17520,7 @@ impl Editor { let autoscroll = Autoscroll::center(); self.unfold_ranges(&[destination..destination], false, false, cx); - self.change_selections(Some(autoscroll), window, cx, |s| { + self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| { s.select_ranges([destination..destination]); }); } @@ -20021,9 +20055,14 @@ impl Editor { None => Autoscroll::newest(), }; let nav_history = editor.nav_history.take(); - editor.change_selections(Some(autoscroll), window, cx, |s| { - s.select_ranges(ranges); - }); + editor.change_selections( + SelectionEffects::scroll(autoscroll), + window, + cx, + |s| { + s.select_ranges(ranges); + }, + ); editor.nav_history = nav_history; }); } @@ -20224,7 +20263,7 @@ impl Editor { } if let Some(relative_utf16_range) = relative_utf16_range { let selections = self.selections.all::(cx); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { let new_ranges = selections.into_iter().map(|range| { let start = OffsetUtf16( range @@ -20367,7 +20406,7 @@ impl Editor { .iter() .map(|selection| (selection.end..selection.end, pending.clone())); this.edit(edits, cx); - this.change_selections(None, window, cx, |s| { + this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| { sel.start + ix * pending.len()..sel.end + ix * pending.len() })); @@ -20523,7 +20562,9 @@ impl Editor { } }) .detach(); - self.change_selections(None, window, cx, |selections| selections.refresh()); + self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + selections.refresh() + }); } pub fn to_pixel_point( @@ -20648,7 +20689,7 @@ impl Editor { buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx)); // skip adding the initial selection to selection history self.selection_history.mode = SelectionHistoryMode::Skipping; - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(selections.into_iter().map(|(start, end)| { snapshot.clip_offset(start, Bias::Left) ..snapshot.clip_offset(end, Bias::Right) @@ -22462,7 +22503,7 @@ impl EntityInputHandler for Editor { }); if let Some(new_selected_ranges) = new_selected_ranges { - this.change_selections(None, window, cx, |selections| { + this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges(new_selected_ranges) }); this.backspace(&Default::default(), window, cx); @@ -22537,7 +22578,9 @@ impl EntityInputHandler for Editor { }); if let Some(ranges) = ranges_to_replace { - this.change_selections(None, window, cx, |s| s.select_ranges(ranges)); + this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(ranges) + }); } let marked_ranges = { @@ -22591,7 +22634,7 @@ impl EntityInputHandler for Editor { .collect::>(); drop(snapshot); - this.change_selections(None, window, cx, |selections| { + this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges(new_selected_ranges) }); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 1ef2294d41d2815b2bfadb21257a0cc3132ebf3a..376effa91dce14f4703eec657d9fb6e04ae3d8d0 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -179,7 +179,9 @@ fn test_edit_events(cx: &mut TestAppContext) { // No event is emitted when the mutation is a no-op. _ = editor2.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([0..0])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([0..0]) + }); editor.backspace(&Backspace, window, cx); }); @@ -202,7 +204,9 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { _ = editor.update(cx, |editor, window, cx| { editor.start_transaction_at(now, window, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges([2..4])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([2..4]) + }); editor.insert("cd", window, cx); editor.end_transaction_at(now, cx); @@ -210,14 +214,18 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { assert_eq!(editor.selections.ranges(cx), vec![4..4]); editor.start_transaction_at(now, window, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges([4..5])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([4..5]) + }); editor.insert("e", window, cx); editor.end_transaction_at(now, cx); assert_eq!(editor.text(cx), "12cde6"); assert_eq!(editor.selections.ranges(cx), vec![5..5]); now += group_interval + Duration::from_millis(1); - editor.change_selections(None, window, cx, |s| s.select_ranges([2..2])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([2..2]) + }); // Simulate an edit in another editor buffer.update(cx, |buffer, cx| { @@ -325,7 +333,7 @@ fn test_ime_composition(cx: &mut TestAppContext) { assert_eq!(editor.marked_text_ranges(cx), None); // Start a new IME composition with multiple cursors. - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ OffsetUtf16(1)..OffsetUtf16(1), OffsetUtf16(3)..OffsetUtf16(3), @@ -623,7 +631,7 @@ fn test_clone(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(selection_ranges.clone()) }); editor.fold_creases( @@ -709,12 +717,12 @@ async fn test_navigation_history(cx: &mut TestAppContext) { // Move the cursor a small distance. // Nothing is added to the navigation history. - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0) ]) }); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0) ]) @@ -723,7 +731,7 @@ async fn test_navigation_history(cx: &mut TestAppContext) { // Move the cursor a large distance. // The history can jump back to the previous position. - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3) ]) @@ -893,7 +901,7 @@ fn test_fold_action(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0) ]); @@ -984,7 +992,7 @@ fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0) ]); @@ -1069,7 +1077,7 @@ fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0) ]); @@ -1301,7 +1309,7 @@ fn test_move_cursor(cx: &mut TestAppContext) { &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)] ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2) ]); @@ -1446,7 +1454,7 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { build_editor(buffer.clone(), window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]); }); @@ -1536,7 +1544,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4), @@ -1731,7 +1739,7 @@ fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) { // First, let's assert behavior on the first line, that was not soft-wrapped. // Start the cursor at the `k` on the first line - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7) ]); @@ -1753,7 +1761,7 @@ fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) { // Now, let's assert behavior on the second line, that ended up being soft-wrapped. // Start the cursor at the last line (`y` that was wrapped to a new line) - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0) ]); @@ -1819,7 +1827,7 @@ fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4), @@ -1901,7 +1909,7 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11), DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4), @@ -1971,7 +1979,7 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) { "use one::{\n two::three::\n four::five\n};" ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7) ]); @@ -2234,7 +2242,7 @@ async fn test_autoscroll(cx: &mut TestAppContext) { // on screen, the editor autoscrolls to reveal the newest cursor, and // allows the vertical scroll margin below that cursor. cx.update_editor(|editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges([ Point::new(0, 0)..Point::new(0, 0), Point::new(6, 0)..Point::new(6, 0), @@ -2262,7 +2270,7 @@ async fn test_autoscroll(cx: &mut TestAppContext) { // Add a cursor above the visible area. Since both cursors fit on screen, // the editor scrolls to show both. cx.update_editor(|editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| { + editor.change_selections(Default::default(), window, cx, |selections| { selections.select_ranges([ Point::new(1, 0)..Point::new(1, 0), Point::new(6, 0)..Point::new(6, 0), @@ -2429,7 +2437,7 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ // an empty selection - the preceding word fragment is deleted DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -2448,7 +2456,7 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ // an empty selection - the following word fragment is deleted DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3), @@ -2483,7 +2491,7 @@ fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) { }; _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1) ]) @@ -2519,7 +2527,7 @@ fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) { }; _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0) ]) @@ -2558,7 +2566,7 @@ fn test_newline(cx: &mut TestAppContext) { }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2), @@ -2591,7 +2599,7 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) { cx, ); let mut editor = build_editor(buffer.clone(), window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ Point::new(2, 4)..Point::new(2, 5), Point::new(5, 4)..Point::new(5, 5), @@ -3078,7 +3086,7 @@ fn test_insert_with_old_selections(cx: &mut TestAppContext) { let editor = cx.add_window(|window, cx| { let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx); let mut editor = build_editor(buffer.clone(), window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([3..4, 11..12, 19..20]) }); editor @@ -3727,7 +3735,7 @@ fn test_delete_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1), @@ -3750,7 +3758,7 @@ fn test_delete_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1) ]) @@ -3787,7 +3795,7 @@ fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { ); // When multiple lines are selected, remove newlines that are spanned by the selection - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(0, 5)..Point::new(2, 2)]) }); editor.join_lines(&JoinLines, window, cx); @@ -3806,7 +3814,7 @@ fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { ); // When joining an empty line don't insert a space - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(2, 1)..Point::new(2, 2)]) }); editor.join_lines(&JoinLines, window, cx); @@ -3846,7 +3854,7 @@ fn test_join_lines_with_single_selection(cx: &mut TestAppContext) { // We remove any leading spaces assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td"); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(0, 1)..Point::new(0, 1)]) }); editor.join_lines(&JoinLines, window, cx); @@ -3873,7 +3881,7 @@ fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) { let mut editor = build_editor(buffer.clone(), window, cx); let buffer = buffer.read(cx).as_singleton().unwrap(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ Point::new(0, 2)..Point::new(1, 1), Point::new(1, 2)..Point::new(1, 2), @@ -4713,7 +4721,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -4739,7 +4747,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1), DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1), @@ -4763,7 +4771,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -4789,7 +4797,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1), DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1), @@ -4811,7 +4819,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1), DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1), @@ -4848,7 +4856,7 @@ fn test_move_line_up_down(cx: &mut TestAppContext) { window, cx, ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1), @@ -4951,7 +4959,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { Some(Autoscroll::fit()), cx, ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(2, 0)..Point::new(2, 0)]) }); editor.move_line_down(&MoveLineDown, window, cx); @@ -5036,7 +5044,9 @@ fn test_transpose(cx: &mut TestAppContext) { _ = cx.add_window(|window, cx| { let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx); editor.set_style(EditorStyle::default(), window, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges([1..1])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([1..1]) + }); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "bac"); assert_eq!(editor.selections.ranges(cx), [2..2]); @@ -5055,12 +5065,16 @@ fn test_transpose(cx: &mut TestAppContext) { _ = cx.add_window(|window, cx| { let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx); editor.set_style(EditorStyle::default(), window, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges([3..3])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([3..3]) + }); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "acb\nde"); assert_eq!(editor.selections.ranges(cx), [3..3]); - editor.change_selections(None, window, cx, |s| s.select_ranges([4..4])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([4..4]) + }); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "acbd\ne"); assert_eq!(editor.selections.ranges(cx), [5..5]); @@ -5079,7 +5093,9 @@ fn test_transpose(cx: &mut TestAppContext) { _ = cx.add_window(|window, cx| { let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx); editor.set_style(EditorStyle::default(), window, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([1..1, 2..2, 4..4]) + }); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "bacd\ne"); assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]); @@ -5106,7 +5122,9 @@ fn test_transpose(cx: &mut TestAppContext) { _ = cx.add_window(|window, cx| { let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx); editor.set_style(EditorStyle::default(), window, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges([4..4])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([4..4]) + }); editor.transpose(&Default::default(), window, cx); assert_eq!(editor.text(cx), "🏀🍐✋"); assert_eq!(editor.selections.ranges(cx), [8..8]); @@ -6085,7 +6103,7 @@ fn test_select_line(cx: &mut TestAppContext) { build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -6212,7 +6230,7 @@ async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestA }); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2), @@ -6231,7 +6249,7 @@ async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestA .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ"); _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1) ]) @@ -6977,7 +6995,7 @@ async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) { // Move cursor to a different position cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]); }); }); @@ -7082,7 +7100,7 @@ async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext "}); cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]); }); }); @@ -7342,7 +7360,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25), DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12), @@ -7524,7 +7542,7 @@ async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContex // Test case 1: Cursor at end of word editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5) ]); @@ -7548,7 +7566,7 @@ async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContex // Test case 2: Cursor at end of statement editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11) ]); @@ -7593,7 +7611,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte // Test 1: Cursor on a letter of a string word editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17) ]); @@ -7627,7 +7645,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte // Test 2: Partial selection within a word editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19) ]); @@ -7661,7 +7679,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte // Test 3: Complete word already selected editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21) ]); @@ -7695,7 +7713,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte // Test 4: Selection spanning across words editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24) ]); @@ -7897,7 +7915,9 @@ async fn test_autoindent(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([5..5, 8..8, 9..9]) + }); editor.newline(&Newline, window, cx); assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n"); assert_eq!( @@ -8679,7 +8699,7 @@ async fn test_surround_with_pair(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1), DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1), @@ -8829,7 +8849,7 @@ async fn test_delete_autoclose_pair(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ Point::new(0, 1)..Point::new(0, 1), Point::new(1, 1)..Point::new(1, 1), @@ -9511,16 +9531,22 @@ async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) { }); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(1..2)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(1..2)), + ); editor.insert("|one|two|three|", window, cx); }); assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx))); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(60..70)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(60..70)), + ); editor.insert("|four|five|six|", window, cx); }); assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx))); @@ -9683,9 +9709,12 @@ async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) { // Edit only the first buffer editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(10..10)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(10..10)), + ); editor.insert("// edited", window, cx); }); @@ -11097,7 +11126,9 @@ async fn test_signature_help(cx: &mut TestAppContext) { "}); cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([0..0])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([0..0]) + }); }); let mocked_response = lsp::SignatureHelp { @@ -11184,7 +11215,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { // When selecting a range, the popover is gone. // Avoid using `cx.set_state` to not actually edit the document, just change its selections. cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19))); }) }); @@ -11201,7 +11232,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { // When unselecting again, the popover is back if within the brackets. cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19))); }) }); @@ -11221,7 +11252,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape. cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0))); s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19))); }) @@ -11262,7 +11293,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { cx.condition(|editor, _| !editor.signature_help_state.is_shown()) .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19))); }) }); @@ -11274,7 +11305,7 @@ async fn test_signature_help(cx: &mut TestAppContext) { fn sample(param1: u8, param2: u8) {} "}); cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19))); }) }); @@ -11930,7 +11961,7 @@ async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppConte let fake_server = fake_servers.next().await.unwrap(); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ Point::new(1, 11)..Point::new(1, 11), Point::new(7, 11)..Point::new(7, 11), @@ -13571,7 +13602,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx)); editor.update_in(cx, |editor, window, cx| { assert_eq!(editor.text(cx), "aaaa\nbbbb"); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ Point::new(0, 0)..Point::new(0, 0), Point::new(1, 0)..Point::new(1, 0), @@ -13589,7 +13620,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { ); // Ensure the cursor's head is respected when deleting across an excerpt boundary. - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(0, 2)..Point::new(1, 2)]) }); editor.backspace(&Default::default(), window, cx); @@ -13599,7 +13630,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) { [Point::new(1, 0)..Point::new(1, 0)] ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 1)..Point::new(0, 1)]) }); editor.backspace(&Default::default(), window, cx); @@ -13647,7 +13678,9 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) { true, ); assert_eq!(editor.text(cx), expected_text); - editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges)); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(selection_ranges) + }); editor.handle_input("X", window, cx); @@ -13708,7 +13741,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) { let editor = cx.add_window(|window, cx| { let mut editor = build_editor(multibuffer.clone(), window, cx); let snapshot = editor.snapshot(window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 3)..Point::new(1, 3)]) }); editor.begin_selection( @@ -13730,7 +13763,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) { // Refreshing selections is a no-op when excerpts haven't changed. _ = editor.update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.refresh()); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh()); assert_eq!( editor.selections.ranges(cx), [ @@ -13755,7 +13788,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) { // Refreshing selections will relocate the first selection to the original buffer // location. - editor.change_selections(None, window, cx, |s| s.refresh()); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh()); assert_eq!( editor.selections.ranges(cx), [ @@ -13817,7 +13850,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) { ); // Ensure we don't panic when selections are refreshed and that the pending selection is finalized. - editor.change_selections(None, window, cx, |s| s.refresh()); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh()); assert_eq!( editor.selections.ranges(cx), [Point::new(0, 3)..Point::new(0, 3)] @@ -13876,7 +13909,7 @@ async fn test_extra_newline_insertion(cx: &mut TestAppContext) { .await; editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3), DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5), @@ -14055,7 +14088,9 @@ async fn test_following(cx: &mut TestAppContext) { // Update the selections only _ = leader.update(cx, |leader, window, cx| { - leader.change_selections(None, window, cx, |s| s.select_ranges([1..1])); + leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([1..1]) + }); }); follower .update(cx, |follower, window, cx| { @@ -14103,7 +14138,9 @@ async fn test_following(cx: &mut TestAppContext) { // Update the selections and scroll position. The follower's scroll position is updated // via autoscroll, not via the leader's exact scroll position. _ = leader.update(cx, |leader, window, cx| { - leader.change_selections(None, window, cx, |s| s.select_ranges([0..0])); + leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([0..0]) + }); leader.request_autoscroll(Autoscroll::newest(), cx); leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx); }); @@ -14127,7 +14164,9 @@ async fn test_following(cx: &mut TestAppContext) { // Creating a pending selection that precedes another selection _ = leader.update(cx, |leader, window, cx| { - leader.change_selections(None, window, cx, |s| s.select_ranges([1..1])); + leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([1..1]) + }); leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx); }); follower @@ -14783,7 +14822,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) { editor_handle.update_in(cx, |editor, window, cx| { window.focus(&editor.focus_handle(cx)); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(0, 21)..Point::new(0, 20)]) }); editor.handle_input("{", window, cx); @@ -16398,7 +16437,7 @@ async fn test_multibuffer_reverts(cx: &mut TestAppContext) { }); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0))); }); editor.git_restore(&Default::default(), window, cx); @@ -16542,9 +16581,12 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) { cx.executor().run_until_parked(); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(1..2)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(1..2)), + ); editor.open_excerpts(&OpenExcerpts, window, cx); }); cx.executor().run_until_parked(); @@ -16594,9 +16636,12 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) { .unwrap(); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(39..40)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(39..40)), + ); editor.open_excerpts(&OpenExcerpts, window, cx); }); cx.executor().run_until_parked(); @@ -16650,9 +16695,12 @@ async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) { .unwrap(); multi_buffer_editor.update_in(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(70..70)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(70..70)), + ); editor.open_excerpts(&OpenExcerpts, window, cx); }); cx.executor().run_until_parked(); @@ -18254,7 +18302,7 @@ async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) { .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 0)..Point::new(1, 0)]) }); }); @@ -18282,7 +18330,7 @@ async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 0)..Point::new(1, 0)]) }); }); @@ -18298,7 +18346,7 @@ async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext ); cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(2, 0)..Point::new(2, 0)]) }); }); @@ -18314,7 +18362,7 @@ async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext ); cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]) }); }); @@ -18345,7 +18393,7 @@ async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) { .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(2, 0)..Point::new(2, 0)]) }); }); @@ -18371,7 +18419,7 @@ async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) { .await; cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(1, 0)..Point::new(1, 0)]) }); }); @@ -19309,14 +19357,14 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) { ); // Test finding task when cursor is inside function body - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(4, 5)..Point::new(4, 5)]) }); let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap(); assert_eq!(row, 3, "Should find task for cursor inside runnable_1"); // Test finding task when cursor is on function name - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(8, 4)..Point::new(8, 4)]) }); let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap(); @@ -19470,7 +19518,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) { .collect::(), "bbbb" ); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]); }); editor.handle_input("B", window, cx); @@ -19697,7 +19745,9 @@ async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut Test HighlightStyle::color(Hsla::green()), cx, ); - editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range))); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(Some(highlight_range)) + }); }); let full_text = format!("\n\n{sample_text}"); @@ -21067,7 +21117,7 @@ println!("5"); }) }); editor_1.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(expected_ranges.clone()); }); }); @@ -21513,7 +21563,7 @@ async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) { let fake_server = fake_servers.next().await.unwrap(); editor.update_in(cx, |editor, window, cx| { editor.set_text("", window, cx); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]); }); let Some((buffer, _)) = editor @@ -22519,7 +22569,7 @@ async fn test_pulling_diagnostics(cx: &mut TestAppContext) { // Moving cursor should not trigger diagnostic request editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(0, 0)..Point::new(0, 0)]) }); }); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6fee347c17ea6b80a9767d5fcbf9094f6160ac5a..426053707649c01aa655902f1a94c302125ef103 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -5238,8 +5238,8 @@ impl EditorElement { paint_highlight(range.start, range.end, color, edges); } - let scroll_left = layout.position_map.snapshot.scroll_position().x - * layout.position_map.em_advance; + let scroll_left = + layout.position_map.snapshot.scroll_position().x * layout.position_map.em_width; for (wrap_position, active) in layout.wrap_guides.iter() { let x = (layout.position_map.text_hitbox.origin.x @@ -6676,7 +6676,7 @@ impl EditorElement { let position_map: &PositionMap = &position_map; let line_height = position_map.line_height; - let max_glyph_advance = position_map.em_advance; + let max_glyph_width = position_map.em_width; let (delta, axis) = match delta { gpui::ScrollDelta::Pixels(mut pixels) => { //Trackpad @@ -6687,15 +6687,15 @@ impl EditorElement { gpui::ScrollDelta::Lines(lines) => { //Not trackpad let pixels = - point(lines.x * max_glyph_advance, lines.y * line_height); + point(lines.x * max_glyph_width, lines.y * line_height); (pixels, None) } }; let current_scroll_position = position_map.snapshot.scroll_position(); - let x = (current_scroll_position.x * max_glyph_advance + let x = (current_scroll_position.x * max_glyph_width - (delta.x * scroll_sensitivity)) - / max_glyph_advance; + / max_glyph_width; let y = (current_scroll_position.y * line_height - (delta.y * scroll_sensitivity)) / line_height; @@ -8591,7 +8591,7 @@ impl Element for EditorElement { start_row, editor_content_width, scroll_width, - em_advance, + em_width, &line_layouts, cx, ) @@ -10051,7 +10051,7 @@ fn compute_auto_height_layout( mod tests { use super::*; use crate::{ - Editor, MultiBuffer, + Editor, MultiBuffer, SelectionEffects, display_map::{BlockPlacement, BlockProperties}, editor_tests::{init_test, update_test_language_settings}, }; @@ -10176,7 +10176,7 @@ mod tests { window .update(cx, |editor, window, cx| { editor.cursor_shape = CursorShape::Block; - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ Point::new(0, 0)..Point::new(1, 0), Point::new(3, 2)..Point::new(3, 3), diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index a716b2e0314223aa81338942da063d87919a71fe..02f93e6829a3f7ac08ec7dfa390cd846560bb7d5 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -1257,7 +1257,7 @@ mod tests { let snapshot = editor.buffer().read(cx).snapshot(cx); let anchor_range = snapshot.anchor_before(selection_range.start) ..snapshot.anchor_after(selection_range.end); - editor.change_selections(Some(crate::Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.set_pending_anchor_range(anchor_range, crate::SelectMode::Character) }); }); diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 9e6fc356ea6ee840824b174fd216d0ea10828d59..cae47895356c4fbd6ffc94779952475ce6f18dd6 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -3,7 +3,7 @@ use crate::{ EditorSnapshot, GlobalDiagnosticRenderer, Hover, display_map::{InlayOffset, ToDisplayPoint, invisibles::is_invisible}, hover_links::{InlayHighlight, RangeInEditor}, - scroll::{Autoscroll, ScrollAmount}, + scroll::ScrollAmount, }; use anyhow::Context as _; use gpui::{ @@ -746,7 +746,7 @@ pub fn open_markdown_url(link: SharedString, window: &mut Window, cx: &mut App) }; editor.update_in(cx, |editor, window, cx| { editor.change_selections( - Some(Autoscroll::fit()), + Default::default(), window, cx, |selections| { diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index dcfa8429a0da818679965dac4cdbc6875a16118f..647f34487ffc3cd8e688dffa9051737b3e44321e 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -1302,6 +1302,7 @@ fn apply_hint_update( #[cfg(test)] pub mod tests { + use crate::SelectionEffects; use crate::editor_tests::update_test_language_settings; use crate::scroll::ScrollAmount; use crate::{ExcerptRange, scroll::Autoscroll, test::editor_lsp_test_context::rust_lang}; @@ -1384,7 +1385,9 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input("some change", window, cx); }) .unwrap(); @@ -1698,7 +1701,9 @@ pub mod tests { rs_editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input("some rs change", window, cx); }) .unwrap(); @@ -1733,7 +1738,9 @@ pub mod tests { md_editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input("some md change", window, cx); }) .unwrap(); @@ -2155,7 +2162,9 @@ pub mod tests { ] { editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input(change_after_opening, window, cx); }) .unwrap(); @@ -2199,7 +2208,9 @@ pub mod tests { edits.push(cx.spawn(|mut cx| async move { task_editor .update(&mut cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| s.select_ranges([13..13])); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([13..13]) + }); editor.handle_input(async_later_change, window, cx); }) .unwrap(); @@ -2447,9 +2458,12 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_ranges([selection_in_cached_range..selection_in_cached_range]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_ranges([selection_in_cached_range..selection_in_cached_range]), + ); }) .unwrap(); cx.executor().advance_clock(Duration::from_millis( @@ -2712,15 +2726,24 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) - }); - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]) - }); - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges([Point::new(50, 0)..Point::new(50, 0)]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]), + ); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]), + ); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges([Point::new(50, 0)..Point::new(50, 0)]), + ); }) .unwrap(); cx.executor().run_until_parked(); @@ -2745,9 +2768,12 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]), + ); }) .unwrap(); cx.executor().advance_clock(Duration::from_millis( @@ -2778,9 +2804,12 @@ pub mod tests { editor .update(cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]), + ); }) .unwrap(); cx.executor().advance_clock(Duration::from_millis( @@ -2812,7 +2841,7 @@ pub mod tests { editor_edited.store(true, Ordering::Release); editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(57, 0)..Point::new(57, 0)]) }); editor.handle_input("++++more text++++", window, cx); @@ -3130,7 +3159,7 @@ pub mod tests { cx.executor().run_until_parked(); editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) }) }) @@ -3412,7 +3441,7 @@ pub mod tests { cx.executor().run_until_parked(); editor .update(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(10, 0)..Point::new(10, 0)]) }) }) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index ec3590dba217677bbaf2c8aa36bfd3147b9d6cbf..fa6bd93ab8558628670cb315e672ddf4fb3ebcab 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1352,7 +1352,7 @@ impl ProjectItem for Editor { cx, ); if !restoration_data.selections.is_empty() { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(clip_ranges(&restoration_data.selections, &snapshot)); }); } @@ -1558,7 +1558,7 @@ impl SearchableItem for Editor { ) { self.unfold_ranges(&[matches[index].clone()], false, true, cx); let range = self.range_for_match(&matches[index]); - self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + self.change_selections(Default::default(), window, cx, |s| { s.select_ranges([range]); }) } @@ -1570,7 +1570,7 @@ impl SearchableItem for Editor { cx: &mut Context, ) { self.unfold_ranges(matches, false, false, cx); - self.change_selections(None, window, cx, |s| { + self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(matches.iter().cloned()) }); } diff --git a/crates/editor/src/jsx_tag_auto_close.rs b/crates/editor/src/jsx_tag_auto_close.rs index f24fe46100879ce885d7bf863e797458c8bac52d..95a792583953e02a77e592ea957b752f0f8042bb 100644 --- a/crates/editor/src/jsx_tag_auto_close.rs +++ b/crates/editor/src/jsx_tag_auto_close.rs @@ -843,7 +843,7 @@ mod jsx_tag_autoclose_tests { let mut cx = EditorTestContext::for_editor(editor, cx).await; cx.update_editor(|editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select(vec![ Selection::from_offset(4), Selection::from_offset(9), diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index b9b8cbe997b2c6bbdd4f45e50e25621c037badf1..4780f1f56582bf675d7cd7deb7b8f8effb98bfae 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -1,8 +1,8 @@ use crate::{ Copy, CopyAndTrim, CopyPermalinkToLine, Cut, DisplayPoint, DisplaySnapshot, Editor, EvaluateSelectedText, FindAllReferences, GoToDeclaration, GoToDefinition, GoToImplementation, - GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, SelectionExt, - ToDisplayPoint, ToggleCodeActions, + GoToTypeDefinition, Paste, Rename, RevealInFileManager, SelectMode, SelectionEffects, + SelectionExt, ToDisplayPoint, ToggleCodeActions, actions::{Format, FormatSelections}, selections_collection::SelectionsCollection, }; @@ -177,7 +177,7 @@ pub fn deploy_context_menu( let anchor = buffer.anchor_before(point.to_point(&display_map)); if !display_ranges(&display_map, &editor.selections).any(|r| r.contains(&point)) { // Move the cursor to the clicked location so that dispatched actions make sense - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.clear_disjoint(); s.set_pending_anchor_range(anchor..anchor, SelectMode::Character); }); diff --git a/crates/editor/src/proposed_changes_editor.rs b/crates/editor/src/proposed_changes_editor.rs index c5f937f20c3c56b16f42b8e5b501b4a21e0e987f..1ead45b3de89c0705510f8afc55ecf6176a4d7a2 100644 --- a/crates/editor/src/proposed_changes_editor.rs +++ b/crates/editor/src/proposed_changes_editor.rs @@ -1,4 +1,4 @@ -use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SemanticsProvider}; +use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SelectionEffects, SemanticsProvider}; use buffer_diff::BufferDiff; use collections::HashSet; use futures::{channel::mpsc, future::join_all}; @@ -213,7 +213,9 @@ impl ProposedChangesEditor { self.buffer_entries = buffer_entries; self.editor.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |selections| selections.refresh()); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { + selections.refresh() + }); editor.buffer.update(cx, |buffer, cx| { for diff in new_diffs { buffer.add_diff(diff, cx) diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 9e20d14b61c6413fda35bdc7c3e0f2d0521f7aa4..0a9d5e9535d2b2d29e33ee49a8afa46a387d773e 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -5,7 +5,7 @@ use std::{rc::Rc, sync::LazyLock}; pub use crate::rust_analyzer_ext::expand_macro_recursively; use crate::{ - DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, + DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, SelectionEffects, display_map::{ Block, BlockPlacement, CustomBlockId, DisplayMap, DisplayRow, DisplaySnapshot, ToDisplayPoint, @@ -93,7 +93,9 @@ pub fn select_ranges( ) { let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true); assert_eq!(editor.text(cx), unmarked_text); - editor.change_selections(None, window, cx, |s| s.select_ranges(text_ranges)); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(text_ranges) + }); } #[track_caller] diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 195abbe6d98acafb0fa5a874362dd41a2e0fc630..bdf73da5fbfd5d4c29826859790493fbb8494239 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -1,5 +1,5 @@ use crate::{ - AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, RowExt, + AnchorRangeExt, DisplayPoint, Editor, MultiBuffer, RowExt, display_map::{HighlightKey, ToDisplayPoint}, }; use buffer_diff::DiffHunkStatusKind; @@ -362,7 +362,7 @@ impl EditorTestContext { let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true); self.editor.update_in(&mut self.cx, |editor, window, cx| { editor.set_text(unmarked_text, window, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(selection_ranges) }) }); @@ -379,7 +379,7 @@ impl EditorTestContext { let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true); self.editor.update_in(&mut self.cx, |editor, window, cx| { assert_eq!(editor.text(cx), unmarked_text); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(selection_ranges) }) }); diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index e07f84ba0272cb05572e404106af637788510a6e..c8c237fe90f12f2ac4ead04e0f2f0b4955f8bc1c 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -1,6 +1,6 @@ use anyhow::{Context as _, Result}; use buffer_diff::{BufferDiff, BufferDiffSnapshot}; -use editor::{Editor, EditorEvent, MultiBuffer}; +use editor::{Editor, EditorEvent, MultiBuffer, SelectionEffects}; use git::repository::{CommitDetails, CommitDiff, CommitSummary, RepoPath}; use gpui::{ AnyElement, AnyView, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, @@ -154,7 +154,7 @@ impl CommitView { }); editor.update(cx, |editor, cx| { editor.disable_header_for_buffer(metadata_buffer_id.unwrap(), cx); - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges(vec![0..0]); }); }); diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index 371759bd24eb21ae53995648cf86a794b114e156..f858bea94c288efc5dd24c3c17c63bc4b3c63aa2 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -8,7 +8,7 @@ use anyhow::Result; use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus}; use collections::HashSet; use editor::{ - Editor, EditorEvent, + Editor, EditorEvent, SelectionEffects, actions::{GoToHunk, GoToPreviousHunk}, scroll::Autoscroll, }; @@ -255,9 +255,14 @@ impl ProjectDiff { fn move_to_path(&mut self, path_key: PathKey, window: &mut Window, cx: &mut Context) { if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) { self.editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| { - s.select_ranges([position..position]); - }) + editor.change_selections( + SelectionEffects::scroll(Autoscroll::focused()), + window, + cx, + |s| { + s.select_ranges([position..position]); + }, + ) }); } else { self.pending_scroll = Some(path_key); @@ -463,7 +468,7 @@ impl ProjectDiff { self.editor.update(cx, |editor, cx| { if was_empty { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { // TODO select the very beginning (possibly inside a deletion) selections.select_ranges([0..0]) }); diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index bba9617975774883ba869e4a6e607cd66cebee5a..1ac933e316bcde24384139c851a8bedb63388611 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -2,8 +2,8 @@ pub mod cursor_position; use cursor_position::{LineIndicatorFormat, UserCaretPosition}; use editor::{ - Anchor, Editor, MultiBufferSnapshot, RowHighlightOptions, ToOffset, ToPoint, actions::Tab, - scroll::Autoscroll, + Anchor, Editor, MultiBufferSnapshot, RowHighlightOptions, SelectionEffects, ToOffset, ToPoint, + actions::Tab, scroll::Autoscroll, }; use gpui::{ App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, SharedString, Styled, @@ -249,9 +249,12 @@ impl GoToLine { let Some(start) = self.anchor_from_query(&snapshot, cx) else { return; }; - editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_anchor_ranges([start..start]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_anchor_ranges([start..start]), + ); editor.focus_handle(cx).focus(window); cx.notify() }); diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index 4ff793cbaf47a80bff266d21aebd273849c97875..4e9c887124d4583c0123db94508c3f2026fddc97 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/crates/inline_completion_button/src/inline_completion_button.rs @@ -2,7 +2,7 @@ use anyhow::Result; use client::{UserStore, zed_urls}; use copilot::{Copilot, Status}; use editor::{ - Editor, + Editor, SelectionEffects, actions::{ShowEditPrediction, ToggleEditPrediction}, scroll::Autoscroll, }; @@ -929,9 +929,14 @@ async fn open_disabled_globs_setting_in_editor( .map(|inner_match| inner_match.start()..inner_match.end()) }); if let Some(range) = range { - item.change_selections(Some(Autoscroll::newest()), window, cx, |selections| { - selections.select_ranges(vec![range]); - }); + item.change_selections( + SelectionEffects::scroll(Autoscroll::newest()), + window, + cx, + |selections| { + selections.select_ranges(vec![range]); + }, + ); } })?; diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 0aed317a0b80f0d0bb52095a9d6d5f95489bce2f..08bdb8e04f620518ef7955361979f28d83353718 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -1,7 +1,7 @@ use anyhow::Result; use chrono::{Datelike, Local, NaiveTime, Timelike}; -use editor::Editor; use editor::scroll::Autoscroll; +use editor::{Editor, SelectionEffects}; use gpui::{App, AppContext as _, Context, Window, actions}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -168,9 +168,12 @@ pub fn new_journal_entry(workspace: &Workspace, window: &mut Window, cx: &mut Ap if let Some(editor) = item.downcast::().map(|editor| editor.downgrade()) { editor.update_in(cx, |editor, window, cx| { let len = editor.buffer().read(cx).len(cx); - editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_ranges([len..len]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_ranges([len..len]), + ); if len > 0 { editor.insert("\n\n", window, cx); } diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index 99132ce452e4680c8a7302f4c1afbc9d62b613a9..6f74e76e261b7b5f33463fe7932c7eaf0fa2a9fe 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -1,4 +1,4 @@ -use editor::{Anchor, Editor, ExcerptId, scroll::Autoscroll}; +use editor::{Anchor, Editor, ExcerptId, SelectionEffects, scroll::Autoscroll}; use gpui::{ App, AppContext as _, Context, Div, Entity, EventEmitter, FocusHandle, Focusable, Hsla, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, @@ -340,7 +340,7 @@ impl Render for SyntaxTreeView { mem::swap(&mut range.start, &mut range.end); editor.change_selections( - Some(Autoscroll::newest()), + SelectionEffects::scroll(Autoscroll::newest()), window, cx, |selections| { selections.select_ranges(vec![range]); diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index bf1a1da5727a9143e844921dabd770728dc8bcf0..f22671d5dfaf2badafb9a7be5b372c91bd0b1ef6 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -4,7 +4,7 @@ use std::{ops::Range, path::PathBuf}; use anyhow::Result; use editor::scroll::Autoscroll; -use editor::{Editor, EditorEvent}; +use editor::{Editor, EditorEvent, SelectionEffects}; use gpui::{ App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ListState, ParentElement, Render, RetainAllImageCache, Styled, Subscription, Task, @@ -468,9 +468,12 @@ impl MarkdownPreviewView { ) { if let Some(state) = &self.active_editor { state.editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::center()), window, cx, |selections| { - selections.select_ranges(vec![selection]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |selections| selections.select_ranges(vec![selection]), + ); window.focus(&editor.focus_handle(cx)); }); } diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 3fec1d616ab5cbe577d4f3fec7fff1449c62fec6..8c5e78d77bce76e62ef94d2501dbef588cd76f00 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -4,8 +4,8 @@ use std::{ sync::Arc, }; -use editor::RowHighlightOptions; use editor::{Anchor, AnchorRangeExt, Editor, scroll::Autoscroll}; +use editor::{RowHighlightOptions, SelectionEffects}; use fuzzy::StringMatch; use gpui::{ App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, HighlightStyle, @@ -288,9 +288,12 @@ impl PickerDelegate for OutlineViewDelegate { .highlighted_rows::() .next(); if let Some((rows, _)) = highlight { - active_editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_ranges([rows.start..rows.start]) - }); + active_editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_ranges([rows.start..rows.start]), + ); active_editor.clear_row_highlights::(); window.focus(&active_editor.focus_handle(cx)); } diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 5bb771c1e9fc8e1e7d605e1583b52137f0181bd4..0be05d458908e3d7b1317ea205664a349eb6ef5f 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -19,10 +19,10 @@ use collections::{BTreeSet, HashMap, HashSet, hash_map}; use db::kvp::KEY_VALUE_STORE; use editor::{ AnchorRangeExt, Bias, DisplayPoint, Editor, EditorEvent, EditorSettings, ExcerptId, - ExcerptRange, MultiBufferSnapshot, RangeToAnchorExt, ShowScrollbar, + ExcerptRange, MultiBufferSnapshot, RangeToAnchorExt, SelectionEffects, ShowScrollbar, display_map::ToDisplayPoint, items::{entry_git_aware_label_color, entry_label_color}, - scroll::{Autoscroll, AutoscrollStrategy, ScrollAnchor, ScrollbarAutoHide}, + scroll::{Autoscroll, ScrollAnchor, ScrollbarAutoHide}, }; use file_icons::FileIcons; use fuzzy::{StringMatch, StringMatchCandidate, match_strings}; @@ -1099,7 +1099,7 @@ impl OutlinePanel { if change_selection { active_editor.update(cx, |editor, cx| { editor.change_selections( - Some(Autoscroll::Strategy(AutoscrollStrategy::Center, None)), + SelectionEffects::scroll(Autoscroll::center()), window, cx, |s| s.select_ranges(Some(anchor..anchor)), diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index c1ebe25538c4db1f02539f5138c065661be47085..4a122ac7316ed1a7552eda41ef223c62bc3ba910 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -4,7 +4,7 @@ pub mod popover_menu; use anyhow::Result; use editor::{ - Editor, + Editor, SelectionEffects, actions::{MoveDown, MoveUp}, scroll::Autoscroll, }; @@ -695,9 +695,12 @@ impl Picker { editor.update(cx, |editor, cx| { editor.set_text(query, window, cx); let editor_offset = editor.buffer().read(cx).len(cx); - editor.change_selections(Some(Autoscroll::Next), window, cx, |s| { - s.select_ranges(Some(editor_offset..editor_offset)) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::Next), + window, + cx, + |s| s.select_ranges(Some(editor_offset..editor_offset)), + ); }); } } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 3bcc881f9d8a39ddbf1285e0deffe6b2907a4aa5..4db83bcf4c897d3a9bddf304ee96b3de600899bb 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -12,7 +12,7 @@ use editor::{ entry_diagnostic_aware_icon_decoration_and_color, entry_diagnostic_aware_icon_name_and_color, entry_git_aware_label_color, }, - scroll::{Autoscroll, ScrollbarAutoHide}, + scroll::ScrollbarAutoHide, }; use file_icons::FileIcons; use git::status::GitSummary; @@ -1589,7 +1589,7 @@ impl ProjectPanel { }); self.filename_editor.update(cx, |editor, cx| { editor.set_text(file_name, window, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges([selection]) }); window.focus(&editor.focus_handle(cx)); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index a9ba14264ff4a1c30536f6b400f0336bc49a1631..47aed8f470f3538f34bff0a0accdd55d9f1ac70e 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -1,4 +1,4 @@ -use editor::{Bias, Editor, scroll::Autoscroll, styled_runs_for_code_label}; +use editor::{Bias, Editor, SelectionEffects, scroll::Autoscroll, styled_runs_for_code_label}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ App, Context, DismissEvent, Entity, FontWeight, ParentElement, StyledText, Task, WeakEntity, @@ -136,9 +136,12 @@ impl PickerDelegate for ProjectSymbolsDelegate { workspace.open_project_item::(pane, buffer, true, true, window, cx); editor.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::center()), window, cx, |s| { - s.select_ranges([position..position]) - }); + editor.change_selections( + SelectionEffects::scroll(Autoscroll::center()), + window, + cx, + |s| s.select_ranges([position..position]), + ); }); })?; anyhow::Ok(()) diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index 20518fb12cc39c54993a077decd0ee1ff5f81c8b..18d41f3eae97ce4288d95e1e0eabb57d4b47adec 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -8,6 +8,7 @@ use crate::{ }; use anyhow::Context as _; use collections::{HashMap, HashSet}; +use editor::SelectionEffects; use editor::{ Anchor, AnchorRangeExt as _, Editor, MultiBuffer, ToPoint, display_map::{ @@ -477,7 +478,7 @@ impl Session { if move_down { editor.update(cx, move |editor, cx| { editor.change_selections( - Some(Autoscroll::top_relative(8)), + SelectionEffects::scroll(Autoscroll::top_relative(8)), window, cx, |selections| { diff --git a/crates/rules_library/src/rules_library.rs b/crates/rules_library/src/rules_library.rs index 231647ef5a930da03a50b21eb571d0f19e039e7a..5e249162d3286e777ba28f8c645f8e2918bc9acf 100644 --- a/crates/rules_library/src/rules_library.rs +++ b/crates/rules_library/src/rules_library.rs @@ -1,6 +1,6 @@ use anyhow::Result; use collections::{HashMap, HashSet}; -use editor::CompletionProvider; +use editor::{CompletionProvider, SelectionEffects}; use editor::{CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle, actions::Tab}; use gpui::{ Action, App, Bounds, Entity, EventEmitter, Focusable, PromptLevel, Subscription, Task, @@ -895,10 +895,15 @@ impl RulesLibrary { } EditorEvent::Blurred => { title_editor.update(cx, |title_editor, cx| { - title_editor.change_selections(None, window, cx, |selections| { - let cursor = selections.oldest_anchor().head(); - selections.select_anchor_ranges([cursor..cursor]); - }); + title_editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |selections| { + let cursor = selections.oldest_anchor().head(); + selections.select_anchor_ranges([cursor..cursor]); + }, + ); }); } _ => {} @@ -920,10 +925,15 @@ impl RulesLibrary { } EditorEvent::Blurred => { body_editor.update(cx, |body_editor, cx| { - body_editor.change_selections(None, window, cx, |selections| { - let cursor = selections.oldest_anchor().head(); - selections.select_anchor_ranges([cursor..cursor]); - }); + body_editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |selections| { + let cursor = selections.oldest_anchor().head(); + selections.select_anchor_ranges([cursor..cursor]); + }, + ); }); } _ => {} diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index fa7a3ba915896d52f1d2f60f55d5ab13746edda8..715cb451ddc6b0ea662234bd99dfeb4ba876f767 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1540,7 +1540,10 @@ mod tests { use std::ops::Range; use super::*; - use editor::{DisplayPoint, Editor, MultiBuffer, SearchSettings, display_map::DisplayRow}; + use editor::{ + DisplayPoint, Editor, MultiBuffer, SearchSettings, SelectionEffects, + display_map::DisplayRow, + }; use gpui::{Hsla, TestAppContext, UpdateGlobal, VisualTestContext}; use language::{Buffer, Point}; use project::Project; @@ -1677,7 +1680,7 @@ mod tests { }); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0) ]) @@ -1764,7 +1767,7 @@ mod tests { // Park the cursor in between matches and ensure that going to the previous match selects // the closest match to the left. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0) ]) @@ -1785,7 +1788,7 @@ mod tests { // Park the cursor in between matches and ensure that going to the next match selects the // closest match to the right. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0) ]) @@ -1806,7 +1809,7 @@ mod tests { // Park the cursor after the last match and ensure that going to the previous match selects // the last match. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 60)..DisplayPoint::new(DisplayRow(3), 60) ]) @@ -1827,7 +1830,7 @@ mod tests { // Park the cursor after the last match and ensure that going to the next match selects the // first match. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(3), 60)..DisplayPoint::new(DisplayRow(3), 60) ]) @@ -1848,7 +1851,7 @@ mod tests { // Park the cursor before the first match and ensure that going to the previous match // selects the last match. editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([ DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0) ]) @@ -2625,7 +2628,7 @@ mod tests { }); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(vec![Point::new(1, 0)..Point::new(2, 4)]) }) }); @@ -2708,7 +2711,7 @@ mod tests { }); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(vec![ Point::new(1, 0)..Point::new(1, 4), Point::new(5, 3)..Point::new(6, 4), diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 8e1ea3d7733cd18412b1330551301864df981ec8..fd2cc3a1ced907921698081c8c124c8132ba3692 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -7,7 +7,7 @@ use anyhow::Context as _; use collections::{HashMap, HashSet}; use editor::{ Anchor, Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MAX_TAB_TITLE_LEN, - MultiBuffer, actions::SelectAll, items::active_match_index, scroll::Autoscroll, + MultiBuffer, SelectionEffects, actions::SelectAll, items::active_match_index, }; use futures::{StreamExt, stream::FuturesOrdered}; use gpui::{ @@ -1303,7 +1303,7 @@ impl ProjectSearchView { self.results_editor.update(cx, |editor, cx| { let range_to_select = editor.range_for_match(&range_to_select); editor.unfold_ranges(std::slice::from_ref(&range_to_select), false, true, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges([range_to_select]) }); }); @@ -1350,7 +1350,9 @@ impl ProjectSearchView { fn focus_results_editor(&mut self, window: &mut Window, cx: &mut Context) { self.query_editor.update(cx, |query_editor, cx| { let cursor = query_editor.selections.newest_anchor().head(); - query_editor.change_selections(None, window, cx, |s| s.select_ranges([cursor..cursor])); + query_editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges([cursor..cursor]) + }); }); let results_handle = self.results_editor.focus_handle(cx); window.focus(&results_handle); @@ -1370,7 +1372,7 @@ impl ProjectSearchView { let range_to_select = match_ranges .first() .map(|range| editor.range_for_match(range)); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(range_to_select) }); editor.scroll(Point::default(), Some(Axis::Vertical), window, cx); diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index d3b8d927b3cf9114bc341b795f31e1ee4ad8e6b7..1510f613e34ef7bfc78bbfad23b7843787432491 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -751,7 +751,7 @@ fn string_match_candidates<'a>( mod tests { use std::{path::PathBuf, sync::Arc}; - use editor::Editor; + use editor::{Editor, SelectionEffects}; use gpui::{TestAppContext, VisualTestContext}; use language::{Language, LanguageConfig, LanguageMatcher, Point}; use project::{ContextProviderWithTasks, FakeFs, Project}; @@ -1028,7 +1028,7 @@ mod tests { .update(|_window, cx| second_item.act_as::(cx)) .unwrap(); editor.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(Some(Point::new(1, 2)..Point::new(1, 5))) }) }); diff --git a/crates/tasks_ui/src/tasks_ui.rs b/crates/tasks_ui/src/tasks_ui.rs index acdc7d0298490b2765b828c5bc468796deb6b3c3..0b3f70e6bcc5402bae3af09effb5bebc1a574977 100644 --- a/crates/tasks_ui/src/tasks_ui.rs +++ b/crates/tasks_ui/src/tasks_ui.rs @@ -393,7 +393,7 @@ fn worktree_context(worktree_abs_path: &Path) -> TaskContext { mod tests { use std::{collections::HashMap, sync::Arc}; - use editor::Editor; + use editor::{Editor, SelectionEffects}; use gpui::TestAppContext; use language::{Language, LanguageConfig}; use project::{BasicContextProvider, FakeFs, Project, task_store::TaskStore}; @@ -538,7 +538,7 @@ mod tests { // And now, let's select an identifier. editor2.update_in(cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |selections| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges([14..18]) }) }); diff --git a/crates/vim/src/change_list.rs b/crates/vim/src/change_list.rs index 3332239631ae836111fe34431e807a21381b970f..25da3e09b8f6115273176cdb74e10e52aaeb951c 100644 --- a/crates/vim/src/change_list.rs +++ b/crates/vim/src/change_list.rs @@ -1,4 +1,4 @@ -use editor::{Bias, Direction, Editor, display_map::ToDisplayPoint, movement, scroll::Autoscroll}; +use editor::{Bias, Direction, Editor, display_map::ToDisplayPoint, movement}; use gpui::{Context, Window, actions}; use crate::{Vim, state::Mode}; @@ -29,7 +29,7 @@ impl Vim { .next_change(count, direction) .map(|s| s.to_vec()) { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let map = s.display_map(); s.select_display_ranges(selections.iter().map(|a| { let point = a.to_display_point(&map); diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 40e8fcffa3c90be95f1421548a19c3a1a444035c..839a0392d4d3b18edb6449b15c9a310c387c5ad7 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -2,10 +2,9 @@ use anyhow::Result; use collections::{HashMap, HashSet}; use command_palette_hooks::CommandInterceptResult; use editor::{ - Bias, Editor, ToPoint, + Bias, Editor, SelectionEffects, ToPoint, actions::{SortLinesCaseInsensitive, SortLinesCaseSensitive}, display_map::ToDisplayPoint, - scroll::Autoscroll, }; use gpui::{Action, App, AppContext as _, Context, Global, Window, actions}; use itertools::Itertools; @@ -422,7 +421,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { let target = snapshot .buffer_snapshot .clip_point(Point::new(buffer_row.0, current.head().column), Bias::Left); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges([target..target]); }); @@ -493,7 +492,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { .disjoint_anchor_ranges() .collect::>() }); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { let end = Point::new(range.end.0, s.buffer().line_len(range.end)); s.select_ranges([end..Point::new(range.start.0, 0)]); }); @@ -503,7 +502,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { window.dispatch_action(action.action.boxed_clone(), cx); cx.defer_in(window, move |vim, window, cx| { vim.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { if let Some(previous_selections) = previous_selections { s.select_ranges(previous_selections); } else { @@ -1455,15 +1454,20 @@ impl OnMatchingLines { editor .update_in(cx, |editor, window, cx| { editor.start_transaction_at(Instant::now(), window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.replace_cursors_with(|_| new_selections); }); window.dispatch_action(action, cx); cx.defer_in(window, move |editor, window, cx| { let newest = editor.selections.newest::(cx).clone(); - editor.change_selections(None, window, cx, |s| { - s.select(vec![newest]); - }); + editor.change_selections( + SelectionEffects::no_scroll(), + window, + cx, + |s| { + s.select(vec![newest]); + }, + ); editor.end_transaction_at(Instant::now(), cx); }) }) @@ -1566,7 +1570,7 @@ impl Vim { ) .unwrap_or((start.range(), MotionKind::Exclusive)); if range.start != start.start { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ range.start.to_point(&snapshot)..range.start.to_point(&snapshot) ]); @@ -1606,7 +1610,7 @@ impl Vim { .range(&snapshot, start.clone(), around) .unwrap_or(start.range()); if range.start != start.start { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([ range.start.to_point(&snapshot)..range.start.to_point(&snapshot) ]); @@ -1799,7 +1803,7 @@ impl ShellExec { editor.transact(window, cx, |editor, window, cx| { editor.edit([(range.clone(), text)], cx); let snapshot = editor.buffer().read(cx).snapshot(cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let point = if is_read { let point = range.end.to_point(&snapshot); Point::new(point.row.saturating_sub(1), 0) diff --git a/crates/vim/src/helix.rs b/crates/vim/src/helix.rs index d5312934e477d2d5ddea089695a5055858cd391b..d0bbf5f17f3bf39dd1a7d02d0b54d2512a32e913 100644 --- a/crates/vim/src/helix.rs +++ b/crates/vim/src/helix.rs @@ -1,4 +1,4 @@ -use editor::{DisplayPoint, Editor, movement, scroll::Autoscroll}; +use editor::{DisplayPoint, Editor, movement}; use gpui::{Action, actions}; use gpui::{Context, Window}; use language::{CharClassifier, CharKind}; @@ -47,7 +47,7 @@ impl Vim { mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool, ) { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let times = times.unwrap_or(1); let new_goal = SelectionGoal::None; @@ -100,7 +100,7 @@ impl Vim { mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool, ) { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let times = times.unwrap_or(1); let new_goal = SelectionGoal::None; @@ -161,7 +161,7 @@ impl Vim { ) { self.update_editor(window, cx, |_, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let goal = selection.goal; let cursor = if selection.is_empty() || selection.reversed { @@ -239,7 +239,7 @@ impl Vim { Motion::FindForward { .. } => { self.update_editor(window, cx, |_, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let goal = selection.goal; let cursor = if selection.is_empty() || selection.reversed { @@ -266,7 +266,7 @@ impl Vim { Motion::FindBackward { .. } => { self.update_editor(window, cx, |_, editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let goal = selection.goal; let cursor = if selection.is_empty() || selection.reversed { diff --git a/crates/vim/src/indent.rs b/crates/vim/src/indent.rs index ac708a7e8932f98502a2b969fa9ca68153765e8b..c8762c563a63479b6f187d3d7d0648ee2d2a92be 100644 --- a/crates/vim/src/indent.rs +++ b/crates/vim/src/indent.rs @@ -1,5 +1,6 @@ use crate::{Vim, motion::Motion, object::Object, state::Mode}; use collections::HashMap; +use editor::SelectionEffects; use editor::{Bias, Editor, display_map::ToDisplayPoint}; use gpui::actions; use gpui::{Context, Window}; @@ -88,7 +89,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); selection_starts.insert(selection.id, anchor); @@ -106,7 +107,7 @@ impl Vim { IndentDirection::Out => editor.outdent(&Default::default(), window, cx), IndentDirection::Auto => editor.autoindent(&Default::default(), window, cx), } - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = selection_starts.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); @@ -128,7 +129,7 @@ impl Vim { self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { let mut original_positions: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); original_positions.insert(selection.id, anchor); @@ -140,7 +141,7 @@ impl Vim { IndentDirection::Out => editor.outdent(&Default::default(), window, cx), IndentDirection::Auto => editor.autoindent(&Default::default(), window, cx), } - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = original_positions.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); diff --git a/crates/vim/src/insert.rs b/crates/vim/src/insert.rs index a30af8769fac99ac1d1b8c131b32e8c440e0b180..7b38bed2be087085bf66e632c027af7aa858e6f3 100644 --- a/crates/vim/src/insert.rs +++ b/crates/vim/src/insert.rs @@ -1,5 +1,5 @@ use crate::{Vim, state::Mode}; -use editor::{Bias, Editor, scroll::Autoscroll}; +use editor::{Bias, Editor}; use gpui::{Action, Context, Window, actions}; use language::SelectionGoal; use settings::Settings; @@ -34,7 +34,7 @@ impl Vim { editor.dismiss_menus_and_popups(false, window, cx); if !HelixModeSetting::get_global(cx).0 { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, mut cursor, _| { *cursor.column_mut() = cursor.column().saturating_sub(1); (map.clip_point(cursor, Bias::Left), SelectionGoal::None) diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index e9b01f5a674f8736b0379ca20d8907e1ac3782c6..2a6e5196bc01da9f8e6f3b6e12a9e0690757580f 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -4,7 +4,6 @@ use editor::{ movement::{ self, FindRange, TextLayoutDetails, find_boundary, find_preceding_boundary_display_point, }, - scroll::Autoscroll, }; use gpui::{Action, Context, Window, actions, px}; use language::{CharKind, Point, Selection, SelectionGoal}; @@ -626,7 +625,7 @@ impl Vim { Mode::Visual | Mode::VisualLine | Mode::VisualBlock => { if !prior_selections.is_empty() { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(prior_selections.iter().cloned()) }) }); diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 1d70227e0ba8791ebe6ebecd6e1202eae44d91db..2003c8b754613ffd288fac6166d20c700f3d1884 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -26,7 +26,6 @@ use collections::BTreeSet; use convert::ConvertTarget; use editor::Bias; use editor::Editor; -use editor::scroll::Autoscroll; use editor::{Anchor, SelectionEffects}; use editor::{display_map::ToDisplayPoint, movement}; use gpui::{Context, Window, actions}; @@ -103,7 +102,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, |vim, _: &HelixDelete, window, cx| { vim.record_current_action(cx); vim.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { if selection.is_empty() { selection.end = movement::right(map, selection.end) @@ -377,7 +376,7 @@ impl Vim { self.start_recording(cx); self.switch_mode(Mode::Insert, false, window, cx); self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, cursor, _| (right(map, cursor, 1), SelectionGoal::None)); }); }); @@ -388,7 +387,7 @@ impl Vim { if self.mode.is_visual() { let current_mode = self.mode; self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if current_mode == Mode::VisualLine { let start_of_line = motion::start_of_line(map, false, selection.start); @@ -412,7 +411,7 @@ impl Vim { self.start_recording(cx); self.switch_mode(Mode::Insert, false, window, cx); self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { ( first_non_whitespace(map, false, cursor), @@ -432,7 +431,7 @@ impl Vim { self.start_recording(cx); self.switch_mode(Mode::Insert, false, window, cx); self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { (next_line_end(map, cursor, 1), SelectionGoal::None) }); @@ -453,7 +452,7 @@ impl Vim { return; }; - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges(marks.iter().map(|mark| *mark..*mark)) }); }); @@ -489,7 +488,7 @@ impl Vim { }) .collect::>(); editor.edit_with_autoindent(edits, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { let previous_line = motion::start_of_relative_buffer_row(map, cursor, -1); let insert_point = motion::end_of_line(map, false, previous_line, 1); @@ -530,7 +529,7 @@ impl Vim { (end_of_line..end_of_line, "\n".to_string() + &indent) }) .collect::>(); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.maybe_move_cursors_with(|map, cursor, goal| { Motion::CurrentLine.move_point( map, @@ -607,7 +606,7 @@ impl Vim { .collect::>(); editor.edit(edits, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|_, selection| { if let Some(position) = original_positions.get(&selection.id) { selection.collapse_to(*position, SelectionGoal::None); @@ -755,7 +754,7 @@ impl Vim { editor.newline(&editor::actions::Newline, window, cx); } editor.set_clip_at_line_ends(true, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let point = movement::saturating_left(map, selection.head()); selection.collapse_to(point, SelectionGoal::None) @@ -791,7 +790,7 @@ impl Vim { cx: &mut Context, mut positions: HashMap, ) { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if let Some(anchor) = positions.remove(&selection.id) { selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index e6ecf309f198891ba05370a9270d52978c73ea52..da8d38ea13518945b4ba7ca5c416477b99a05b6e 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -8,7 +8,6 @@ use editor::{ Bias, DisplayPoint, display_map::{DisplaySnapshot, ToDisplayPoint}, movement::TextLayoutDetails, - scroll::Autoscroll, }; use gpui::{Context, Window}; use language::Selection; @@ -40,7 +39,7 @@ impl Vim { editor.transact(window, cx, |editor, window, cx| { // We are swapping to insert mode anyway. Just set the line end clipping behavior now editor.set_clip_at_line_ends(false, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let kind = match motion { Motion::NextWordStart { ignore_punctuation } @@ -114,7 +113,7 @@ impl Vim { // We are swapping to insert mode anyway. Just set the line end clipping behavior now editor.set_clip_at_line_ends(false, cx); editor.transact(window, cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { objects_found |= object.expand_selection(map, selection, around); }); diff --git a/crates/vim/src/normal/convert.rs b/crates/vim/src/normal/convert.rs index 5295e79edb4c08c1b7ee869d0014168df2f40787..4621e3ab896c0e487d9e05323e362642d684573a 100644 --- a/crates/vim/src/normal/convert.rs +++ b/crates/vim/src/normal/convert.rs @@ -1,5 +1,5 @@ use collections::HashMap; -use editor::{display_map::ToDisplayPoint, scroll::Autoscroll}; +use editor::{SelectionEffects, display_map::ToDisplayPoint}; use gpui::{Context, Window}; use language::{Bias, Point, SelectionGoal}; use multi_buffer::MultiBufferRow; @@ -36,7 +36,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Left); selection_starts.insert(selection.id, anchor); @@ -66,7 +66,7 @@ impl Vim { editor.convert_to_rot47(&Default::default(), window, cx) } } - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = selection_starts.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); @@ -90,7 +90,7 @@ impl Vim { editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); let mut original_positions: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around); original_positions.insert( @@ -116,7 +116,7 @@ impl Vim { editor.convert_to_rot47(&Default::default(), window, cx) } } - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = original_positions.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); @@ -239,7 +239,7 @@ impl Vim { .collect::(); editor.edit([(range, text)], cx) } - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(cursor_positions) }) }); diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index f52d9bebe05d517a5dda8d8080d47a9588c9ed9d..141346c99fcdc1f155e8628596c3e6805f5086aa 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -7,7 +7,6 @@ use collections::{HashMap, HashSet}; use editor::{ Bias, DisplayPoint, display_map::{DisplaySnapshot, ToDisplayPoint}, - scroll::Autoscroll, }; use gpui::{Context, Window}; use language::{Point, Selection}; @@ -30,7 +29,7 @@ impl Vim { let mut original_columns: HashMap<_, _> = Default::default(); let mut motion_kind = None; let mut ranges_to_copy = Vec::new(); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let original_head = selection.head(); original_columns.insert(selection.id, original_head.column()); @@ -71,7 +70,7 @@ impl Vim { // Fixup cursor position after the deletion editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let mut cursor = selection.head(); if kind.linewise() { @@ -102,7 +101,7 @@ impl Vim { // Emulates behavior in vim where if we expanded backwards to include a newline // the cursor gets set back to the start of the line let mut should_move_to_start: HashSet<_> = Default::default(); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around); let offset_range = selection.map(|p| p.to_offset(map, Bias::Left)).range(); @@ -159,7 +158,7 @@ impl Vim { // Fixup cursor position after the deletion editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let mut cursor = selection.head(); if should_move_to_start.contains(&selection.id) { diff --git a/crates/vim/src/normal/increment.rs b/crates/vim/src/normal/increment.rs index e2a0d282673a6f1ccb96d7c0a2d63f55d3dd78c1..09e6e85a5ccd057111dddca9e1bc76ebfacc1b63 100644 --- a/crates/vim/src/normal/increment.rs +++ b/crates/vim/src/normal/increment.rs @@ -1,4 +1,4 @@ -use editor::{Editor, MultiBufferSnapshot, ToOffset, ToPoint, scroll::Autoscroll}; +use editor::{Editor, MultiBufferSnapshot, ToOffset, ToPoint}; use gpui::{Action, Context, Window}; use language::{Bias, Point}; use schemars::JsonSchema; @@ -97,7 +97,7 @@ impl Vim { editor.edit(edits, cx); let snapshot = editor.buffer().read(cx).snapshot(cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let mut new_ranges = Vec::new(); for (visual, anchor) in new_anchors.iter() { let mut point = anchor.to_point(&snapshot); diff --git a/crates/vim/src/normal/mark.rs b/crates/vim/src/normal/mark.rs index af4b71f4278a35a1e6462d833d46a247f025fda4..57a6108841e49d0461ff343e000969839287d6c7 100644 --- a/crates/vim/src/normal/mark.rs +++ b/crates/vim/src/normal/mark.rs @@ -4,7 +4,6 @@ use editor::{ Anchor, Bias, DisplayPoint, Editor, MultiBuffer, display_map::{DisplaySnapshot, ToDisplayPoint}, movement, - scroll::Autoscroll, }; use gpui::{Context, Entity, EntityId, UpdateGlobal, Window}; use language::SelectionGoal; @@ -116,7 +115,7 @@ impl Vim { } } - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges(ranges) }); }) @@ -169,7 +168,7 @@ impl Vim { } }) .collect(); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(points.into_iter().map(|p| p..p)) }) }) @@ -251,7 +250,7 @@ impl Vim { } if !should_jump && !ranges.is_empty() { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges(ranges) }); } diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 41337f07074e56e17b35bc72addf3c0ce3ae0f39..0dade838f5d5edbdca89dcea945da16a9fc89c63 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -1,4 +1,4 @@ -use editor::{DisplayPoint, RowExt, display_map::ToDisplayPoint, movement, scroll::Autoscroll}; +use editor::{DisplayPoint, RowExt, SelectionEffects, display_map::ToDisplayPoint, movement}; use gpui::{Action, Context, Window}; use language::{Bias, SelectionGoal}; use schemars::JsonSchema; @@ -187,7 +187,7 @@ impl Vim { // and put the cursor on the first non-blank character of the first inserted line (or at the end if the first line is blank). // otherwise vim will insert the next text at (or before) the current cursor position, // the cursor will go to the last (or first, if is_multiline) inserted character. - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.replace_cursors_with(|map| { let mut cursors = Vec::new(); for (anchor, line_mode, is_multiline) in &new_selections { @@ -238,7 +238,7 @@ impl Vim { self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around); }); @@ -252,7 +252,7 @@ impl Vim { }; editor.insert(&text, window, cx); editor.set_clip_at_line_ends(true, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { selection.start = map.clip_point(selection.start, Bias::Left); selection.end = selection.start @@ -276,7 +276,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { motion.expand_selection( map, @@ -296,7 +296,7 @@ impl Vim { }; editor.insert(&text, window, cx); editor.set_clip_at_line_ends(true, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { selection.start = map.clip_point(selection.start, Bias::Left); selection.end = selection.start diff --git a/crates/vim/src/normal/substitute.rs b/crates/vim/src/normal/substitute.rs index 1199356995df9be3e8425d7c7d3ad0f1ae4c76b7..96df61e528d3df3a480b978c78154d8c0c3a0150 100644 --- a/crates/vim/src/normal/substitute.rs +++ b/crates/vim/src/normal/substitute.rs @@ -1,4 +1,4 @@ -use editor::{Editor, movement}; +use editor::{Editor, SelectionEffects, movement}; use gpui::{Context, Window, actions}; use language::Point; @@ -41,7 +41,7 @@ impl Vim { editor.set_clip_at_line_ends(false, cx); editor.transact(window, cx, |editor, window, cx| { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { if selection.start == selection.end { Motion::Right.expand_selection( diff --git a/crates/vim/src/normal/toggle_comments.rs b/crates/vim/src/normal/toggle_comments.rs index 1df381acbeea2fdc9cc691ebadcc4a429f7745ec..3b578c44cbed080758e5598bc910ed5431ade956 100644 --- a/crates/vim/src/normal/toggle_comments.rs +++ b/crates/vim/src/normal/toggle_comments.rs @@ -1,6 +1,6 @@ use crate::{Vim, motion::Motion, object::Object}; use collections::HashMap; -use editor::{Bias, display_map::ToDisplayPoint}; +use editor::{Bias, SelectionEffects, display_map::ToDisplayPoint}; use gpui::{Context, Window}; use language::SelectionGoal; @@ -18,7 +18,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); selection_starts.insert(selection.id, anchor); @@ -32,7 +32,7 @@ impl Vim { }); }); editor.toggle_comments(&Default::default(), window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = selection_starts.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); @@ -53,7 +53,7 @@ impl Vim { self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { let mut original_positions: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); original_positions.insert(selection.id, anchor); @@ -61,7 +61,7 @@ impl Vim { }); }); editor.toggle_comments(&Default::default(), window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = original_positions.remove(&selection.id).unwrap(); selection.collapse_to(anchor.to_display_point(map), SelectionGoal::None); diff --git a/crates/vim/src/normal/yank.rs b/crates/vim/src/normal/yank.rs index 3525b0d43fbc215fe0d469e1536398177c925653..6beb81b2b6d09f2dcd696929b6858af50cb16f90 100644 --- a/crates/vim/src/normal/yank.rs +++ b/crates/vim/src/normal/yank.rs @@ -7,7 +7,7 @@ use crate::{ state::{Mode, Register}, }; use collections::HashMap; -use editor::{ClipboardSelection, Editor}; +use editor::{ClipboardSelection, Editor, SelectionEffects}; use gpui::Context; use gpui::Window; use language::Point; @@ -31,7 +31,7 @@ impl Vim { editor.set_clip_at_line_ends(false, cx); let mut original_positions: HashMap<_, _> = Default::default(); let mut kind = None; - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let original_position = (selection.head(), selection.goal); kind = motion.expand_selection( @@ -51,7 +51,7 @@ impl Vim { }); let Some(kind) = kind else { return }; vim.yank_selections_content(editor, kind, window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|_, selection| { let (head, goal) = original_positions.remove(&selection.id).unwrap(); selection.collapse_to(head, goal); @@ -73,7 +73,7 @@ impl Vim { editor.transact(window, cx, |editor, window, cx| { editor.set_clip_at_line_ends(false, cx); let mut start_positions: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { object.expand_selection(map, selection, around); let start_position = (selection.start, selection.goal); @@ -81,7 +81,7 @@ impl Vim { }); }); vim.yank_selections_content(editor, MotionKind::Exclusive, window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|_, selection| { let (head, goal) = start_positions.remove(&selection.id).unwrap(); selection.collapse_to(head, goal); diff --git a/crates/vim/src/replace.rs b/crates/vim/src/replace.rs index 5f407db5cb816a30aa83875d19e48bf4bb856473..bf0d977531e55565173d3164c15d11f18d31c360 100644 --- a/crates/vim/src/replace.rs +++ b/crates/vim/src/replace.rs @@ -5,8 +5,8 @@ use crate::{ state::Mode, }; use editor::{ - Anchor, Bias, Editor, EditorSnapshot, ToOffset, ToPoint, display_map::ToDisplayPoint, - scroll::Autoscroll, + Anchor, Bias, Editor, EditorSnapshot, SelectionEffects, ToOffset, ToPoint, + display_map::ToDisplayPoint, }; use gpui::{Context, Window, actions}; use language::{Point, SelectionGoal}; @@ -72,7 +72,7 @@ impl Vim { editor.edit_with_block_indent(edits.clone(), Vec::new(), cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_anchor_ranges(edits.iter().map(|(range, _)| range.end..range.end)); }); editor.set_clip_at_line_ends(true, cx); @@ -124,7 +124,7 @@ impl Vim { editor.edit(edits, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges(new_selections); }); editor.set_clip_at_line_ends(true, cx); @@ -251,7 +251,7 @@ impl Vim { } if let Some(position) = final_cursor_position { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|_map, selection| { selection.collapse_to(position, SelectionGoal::None); }); diff --git a/crates/vim/src/rewrap.rs b/crates/vim/src/rewrap.rs index b5d69ef0ae73d87deb49dde9d852457d910be075..e03a3308fca52c6d11766ccb7731cbd6ec7883c4 100644 --- a/crates/vim/src/rewrap.rs +++ b/crates/vim/src/rewrap.rs @@ -1,6 +1,6 @@ use crate::{Vim, motion::Motion, object::Object, state::Mode}; use collections::HashMap; -use editor::{Bias, Editor, RewrapOptions, display_map::ToDisplayPoint, scroll::Autoscroll}; +use editor::{Bias, Editor, RewrapOptions, SelectionEffects, display_map::ToDisplayPoint}; use gpui::{Context, Window, actions}; use language::SelectionGoal; @@ -22,7 +22,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context) { }, cx, ); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if let Some(anchor) = positions.remove(&selection.id) { let mut point = anchor.to_display_point(map); @@ -53,7 +53,7 @@ impl Vim { let text_layout_details = editor.text_layout_details(window); editor.transact(window, cx, |editor, window, cx| { let mut selection_starts: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); selection_starts.insert(selection.id, anchor); @@ -73,7 +73,7 @@ impl Vim { }, cx, ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = selection_starts.remove(&selection.id).unwrap(); let mut point = anchor.to_display_point(map); @@ -96,7 +96,7 @@ impl Vim { self.update_editor(window, cx, |_, editor, window, cx| { editor.transact(window, cx, |editor, window, cx| { let mut original_positions: HashMap<_, _> = Default::default(); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); original_positions.insert(selection.id, anchor); @@ -110,7 +110,7 @@ impl Vim { }, cx, ); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let anchor = original_positions.remove(&selection.id).unwrap(); let mut point = anchor.to_display_point(map); diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index 6697742e4d318bb3a790e59e3404cf1f19a8c4ff..852433bc8e42ebe97d3b0f140139e20d9f8b4d6f 100644 --- a/crates/vim/src/surrounds.rs +++ b/crates/vim/src/surrounds.rs @@ -4,7 +4,7 @@ use crate::{ object::Object, state::Mode, }; -use editor::{Bias, movement, scroll::Autoscroll}; +use editor::{Bias, movement}; use gpui::{Context, Window}; use language::BracketPair; @@ -109,7 +109,7 @@ impl Vim { editor.edit(edits, cx); editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { if mode == Mode::VisualBlock { s.select_anchor_ranges(anchors.into_iter().take(1)) } else { @@ -207,7 +207,7 @@ impl Vim { } } - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(anchors); }); edits.sort_by_key(|(range, _)| range.start); @@ -317,7 +317,7 @@ impl Vim { edits.sort_by_key(|(range, _)| range.start); editor.edit(edits, cx); editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_anchor_ranges(stable_anchors); }); }); @@ -375,7 +375,7 @@ impl Vim { anchors.push(start..start) } } - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(anchors); }); editor.set_clip_at_line_ends(true, cx); diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 6b5d41f12ebf732781f6cb3234924c6ea48e92b5..2c2d60004e7aae6771906ff718c73b1dc0539723 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -22,7 +22,8 @@ mod visual; use anyhow::Result; use collections::HashMap; use editor::{ - Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, ToPoint, + Anchor, Bias, Editor, EditorEvent, EditorSettings, HideMouseCursorOrigin, SelectionEffects, + ToPoint, movement::{self, FindRange}, }; use gpui::{ @@ -963,7 +964,7 @@ impl Vim { } } - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { // we cheat with visual block mode and use multiple cursors. // the cost of this cheat is we need to convert back to a single // cursor whenever vim would. @@ -1163,7 +1164,7 @@ impl Vim { } else { self.update_editor(window, cx, |_, editor, window, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|_, selection| { selection.collapse_to(selection.start, selection.goal) }) @@ -1438,27 +1439,29 @@ impl Vim { Mode::VisualLine | Mode::VisualBlock | Mode::Visual => { self.update_editor(window, cx, |vim, editor, window, cx| { let original_mode = vim.undo_modes.get(transaction_id); - editor.change_selections(None, window, cx, |s| match original_mode { - Some(Mode::VisualLine) => { - s.move_with(|map, selection| { - selection.collapse_to( - map.prev_line_boundary(selection.start.to_point(map)).1, - SelectionGoal::None, - ) - }); - } - Some(Mode::VisualBlock) => { - let mut first = s.first_anchor(); - first.collapse_to(first.start, first.goal); - s.select_anchors(vec![first]); - } - _ => { - s.move_with(|map, selection| { - selection.collapse_to( - map.clip_at_line_end(selection.start), - selection.goal, - ); - }); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + match original_mode { + Some(Mode::VisualLine) => { + s.move_with(|map, selection| { + selection.collapse_to( + map.prev_line_boundary(selection.start.to_point(map)).1, + SelectionGoal::None, + ) + }); + } + Some(Mode::VisualBlock) => { + let mut first = s.first_anchor(); + first.collapse_to(first.start, first.goal); + s.select_anchors(vec![first]); + } + _ => { + s.move_with(|map, selection| { + selection.collapse_to( + map.clip_at_line_end(selection.start), + selection.goal, + ); + }); + } } }); }); @@ -1466,7 +1469,7 @@ impl Vim { } Mode::Normal => { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { selection .collapse_to(map.clip_at_line_end(selection.end), selection.goal) diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 29ef3943b57086021844d8f65644fbe24e80392d..2d72881b7aed3894b48771fa7396ea8597f620e1 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -2,10 +2,9 @@ use std::sync::Arc; use collections::HashMap; use editor::{ - Bias, DisplayPoint, Editor, + Bias, DisplayPoint, Editor, SelectionEffects, display_map::{DisplaySnapshot, ToDisplayPoint}, movement, - scroll::Autoscroll, }; use gpui::{Context, Window, actions}; use language::{Point, Selection, SelectionGoal}; @@ -133,7 +132,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { vim.update_editor(window, cx, |_, editor, window, cx| { editor.set_clip_at_line_ends(false, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let map = s.display_map(); let ranges = ranges .into_iter() @@ -187,7 +186,7 @@ impl Vim { motion.move_point(map, point, goal, times, &text_layout_details) }) } else { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let was_reversed = selection.reversed; let mut current_head = selection.head(); @@ -259,7 +258,7 @@ impl Vim { ) -> Option<(DisplayPoint, SelectionGoal)>, ) { let text_layout_details = editor.text_layout_details(window); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { let map = &s.display_map(); let mut head = s.newest_anchor().head().to_display_point(map); let mut tail = s.oldest_anchor().tail().to_display_point(map); @@ -375,7 +374,7 @@ impl Vim { } self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let mut mut_selection = selection.clone(); @@ -454,7 +453,7 @@ impl Vim { ) { self.update_editor(window, cx, |_, editor, window, cx| { editor.split_selection_into_lines(&Default::default(), window, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { (next_line_end(map, cursor, 1), SelectionGoal::None) }); @@ -472,7 +471,7 @@ impl Vim { ) { self.update_editor(window, cx, |_, editor, window, cx| { editor.split_selection_into_lines(&Default::default(), window, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_cursors_with(|map, cursor, _| { ( first_non_whitespace(map, false, cursor), @@ -495,7 +494,7 @@ impl Vim { pub fn other_end(&mut self, _: &OtherEnd, window: &mut Window, cx: &mut Context) { self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|_, selection| { selection.reversed = !selection.reversed; }); @@ -511,7 +510,7 @@ impl Vim { ) { let mode = self.mode; self.update_editor(window, cx, |_, editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|_, selection| { selection.reversed = !selection.reversed; }); @@ -530,7 +529,7 @@ impl Vim { editor.selections.line_mode = false; editor.transact(window, cx, |editor, window, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { if line_mode { let mut position = selection.head(); @@ -567,7 +566,7 @@ impl Vim { vim.copy_selections_content(editor, kind, window, cx); if line_mode && vim.mode != Mode::VisualBlock { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let end = selection.end.to_point(map); let start = selection.start.to_point(map); @@ -587,7 +586,7 @@ impl Vim { // Fixup cursor position after the deletion editor.set_clip_at_line_ends(true, cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { let mut cursor = selection.head().to_point(map); @@ -613,7 +612,7 @@ impl Vim { // For visual line mode, adjust selections to avoid yanking the next line when on \n if line_mode && vim.mode != Mode::VisualBlock { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { let start = selection.start.to_point(map); let end = selection.end.to_point(map); @@ -634,7 +633,7 @@ impl Vim { MotionKind::Exclusive }; vim.yank_selections_content(editor, kind, window, cx); - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { if line_mode { selection.start = start_of_line(map, false, selection.start); @@ -687,7 +686,9 @@ impl Vim { } editor.edit(edits, cx); - editor.change_selections(None, window, cx, |s| s.select_ranges(stable_anchors)); + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_ranges(stable_anchors) + }); }); }); self.switch_mode(Mode::Normal, false, window, cx); @@ -799,7 +800,7 @@ impl Vim { if direction == Direction::Prev { std::mem::swap(&mut start_selection, &mut end_selection); } - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges([start_selection..end_selection]); }); editor.set_collapse_matches(true); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2bbe3d0bcb6d119033b4fcc6ed6794faec914ca7..ea3f327ff07c54d0d2816947613859ed8bff2b1c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -18,7 +18,7 @@ use client::zed_urls; use collections::VecDeque; use debugger_ui::debugger_panel::DebugPanel; use editor::ProposedChangesEditorToolbar; -use editor::{Editor, MultiBuffer, scroll::Autoscroll}; +use editor::{Editor, MultiBuffer}; use futures::future::Either; use futures::{StreamExt, channel::mpsc, select_biased}; use git_ui::git_panel::GitPanel; @@ -1125,7 +1125,7 @@ fn open_log_file(workspace: &mut Workspace, window: &mut Window, cx: &mut Contex editor.update(cx, |editor, cx| { let last_multi_buffer_offset = editor.buffer().read(cx).len(cx); - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_ranges(Some( last_multi_buffer_offset..last_multi_buffer_offset, )); @@ -1774,7 +1774,7 @@ mod tests { use super::*; use assets::Assets; use collections::HashSet; - use editor::{DisplayPoint, Editor, display_map::DisplayRow, scroll::Autoscroll}; + use editor::{DisplayPoint, Editor, SelectionEffects, display_map::DisplayRow}; use gpui::{ Action, AnyWindowHandle, App, AssetSource, BorrowAppContext, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowHandle, actions, @@ -3348,7 +3348,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(10), 0) ..DisplayPoint::new(DisplayRow(10), 0)]) }); @@ -3378,7 +3378,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor3.update(cx, |editor, cx| { - editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + editor.change_selections(Default::default(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(12), 0) ..DisplayPoint::new(DisplayRow(12), 0)]) }); @@ -3593,7 +3593,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(15), 0) ..DisplayPoint::new(DisplayRow(15), 0)]) }) @@ -3604,7 +3604,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(3), 0) ..DisplayPoint::new(DisplayRow(3), 0)]) }); @@ -3615,7 +3615,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(13), 0) ..DisplayPoint::new(DisplayRow(13), 0)]) }) @@ -3627,7 +3627,7 @@ mod tests { .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { editor.transact(window, cx, |editor, window, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(2), 0) ..DisplayPoint::new(DisplayRow(14), 0)]) }); @@ -3640,7 +3640,7 @@ mod tests { workspace .update(cx, |_, window, cx| { editor1.update(cx, |editor, cx| { - editor.change_selections(None, window, cx, |s| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_display_ranges([DisplayPoint::new(DisplayRow(1), 0) ..DisplayPoint::new(DisplayRow(1), 0)]) }) From 695118d1107bebb48a87b13b4122a772207d9b57 Mon Sep 17 00:00:00 2001 From: Artem Loenko Date: Fri, 27 Jun 2025 23:33:58 +0100 Subject: [PATCH 035/107] agent: Show provider icon in model selectors (#30595) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I often switch between models, and I believe many people do. Currently, it is difficult to determine which provider offers the selected model because the same models are available from different providers. I propose a simple change to the selector so that users can distinguish between providers from the model they have chosen. As a side note, I would actually prefer to have a text label with the provider’s name next to the model name in the selector. However, I understand that this is too opinionated and takes up too much space. | Before | After | | ------ | ------ | | ![before_inline_assist](https://github.com/user-attachments/assets/35617ba5-e8d4-4dab-a997-f7286f73f2bf) | ![after_inline_assist](https://github.com/user-attachments/assets/c37c81b4-73e4-49e2-955d-b8543b2855ad) | | ![before_text_thread](https://github.com/user-attachments/assets/af90303b-12d6-402c-90a5-8b36cd97396e) | ![after_text_thread](https://github.com/user-attachments/assets/bca5b423-f12b-4eaf-a82e-424d09b7f447) | | ![before_thread](https://github.com/user-attachments/assets/0946775f-1d52-437b-a217-9708ee2e789a) | ![after_thread](https://github.com/user-attachments/assets/f5e53968-9020-446f-9d5e-653ae9fdea3e) | Release Notes: - The model selector has been improved with a provider icon, making it easier to distinguish between providers. --------- Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Co-authored-by: Danilo Leal --- crates/agent_ui/src/agent_model_selector.rs | 31 +++++++++++++++------ crates/agent_ui/src/text_thread_editor.rs | 19 +++++++++++-- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/crates/agent_ui/src/agent_model_selector.rs b/crates/agent_ui/src/agent_model_selector.rs index c8b628c938e5be37ee3e10ca2a46dd2e59c78e84..f7b9157bbb9c07abac6a80dddfc014443165a712 100644 --- a/crates/agent_ui/src/agent_model_selector.rs +++ b/crates/agent_ui/src/agent_model_selector.rs @@ -11,7 +11,7 @@ use language_model::{ConfiguredModel, LanguageModelRegistry}; use picker::popover_menu::PickerPopoverMenu; use settings::update_settings_file; use std::sync::Arc; -use ui::{PopoverMenuHandle, Tooltip, prelude::*}; +use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*}; pub struct AgentModelSelector { selector: Entity, @@ -94,20 +94,35 @@ impl Render for AgentModelSelector { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let model = self.selector.read(cx).delegate.active_model(cx); let model_name = model + .as_ref() .map(|model| model.model.name().0) .unwrap_or_else(|| SharedString::from("No model selected")); + let provider_icon = model + .as_ref() + .map(|model| model.provider.icon()) + .unwrap_or_else(|| IconName::Ai); let focus_handle = self.focus_handle.clone(); PickerPopoverMenu::new( self.selector.clone(), - Button::new("active-model", model_name) - .label_size(LabelSize::Small) - .color(Color::Muted) - .icon(IconName::ChevronDown) - .icon_size(IconSize::XSmall) - .icon_position(IconPosition::End) - .icon_color(Color::Muted), + ButtonLike::new("active-model") + .child( + Icon::new(provider_icon) + .color(Color::Muted) + .size(IconSize::XSmall), + ) + .child( + Label::new(model_name) + .color(Color::Muted) + .size(LabelSize::Small) + .ml_0p5(), + ) + .child( + Icon::new(IconName::ChevronDown) + .color(Color::Muted) + .size(IconSize::XSmall), + ), move |window, cx| { Tooltip::for_action_in( "Change Model", diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index dcb239a46ddec79d7aa52c4180cb511e8b74ac71..d11deb790820ba18a7437ac50ed3d5b2e8d4c9c0 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -2120,12 +2120,21 @@ impl TextThreadEditor { let active_model = LanguageModelRegistry::read_global(cx) .default_model() .map(|default| default.model); - let focus_handle = self.editor().focus_handle(cx).clone(); let model_name = match active_model { Some(model) => model.name().0, None => SharedString::from("No model selected"), }; + let active_provider = LanguageModelRegistry::read_global(cx) + .default_model() + .map(|default| default.provider); + let provider_icon = match active_provider { + Some(provider) => provider.icon(), + None => IconName::Ai, + }; + + let focus_handle = self.editor().focus_handle(cx).clone(); + PickerPopoverMenu::new( self.language_model_selector.clone(), ButtonLike::new("active-model") @@ -2133,10 +2142,16 @@ impl TextThreadEditor { .child( h_flex() .gap_0p5() + .child( + Icon::new(provider_icon) + .color(Color::Muted) + .size(IconSize::XSmall), + ) .child( Label::new(model_name) + .color(Color::Muted) .size(LabelSize::Small) - .color(Color::Muted), + .ml_0p5(), ) .child( Icon::new(IconName::ChevronDown) From c56b8904ccf5c44d608898ccd84dad7934db1531 Mon Sep 17 00:00:00 2001 From: ddoemonn <109994179+ddoemonn@users.noreply.github.com> Date: Sat, 28 Jun 2025 02:50:53 +0300 Subject: [PATCH 036/107] Prevent branch name overflow in git panel selection (#33529) Closes #33527 Release Notes: - Fixed long branch names overflowing to multiple lines in git panel branch selector --------- Co-authored-by: Danilo Leal --- crates/git_ui/src/branch_picker.rs | 55 +++++++++++++++--------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs index 635876dace889bde4f461a9feee9c8df4d1c24cc..9eac3ce5aff6dd440fd18fde3ea70042e71a4ce7 100644 --- a/crates/git_ui/src/branch_picker.rs +++ b/crates/git_ui/src/branch_picker.rs @@ -245,7 +245,7 @@ impl PickerDelegate for BranchListDelegate { type ListItem = ListItem; fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc { - "Select branch...".into() + "Select branch…".into() } fn editor_position(&self) -> PickerEditorPosition { @@ -439,44 +439,43 @@ impl PickerDelegate for BranchListDelegate { }) .unwrap_or_else(|| (None, None)); + let branch_name = if entry.is_new { + h_flex() + .gap_1() + .child( + Icon::new(IconName::Plus) + .size(IconSize::Small) + .color(Color::Muted), + ) + .child( + Label::new(format!("Create branch \"{}\"…", entry.branch.name())) + .single_line() + .truncate(), + ) + .into_any_element() + } else { + HighlightedLabel::new(entry.branch.name().to_owned(), entry.positions.clone()) + .truncate() + .into_any_element() + }; + Some( ListItem::new(SharedString::from(format!("vcs-menu-{ix}"))) .inset(true) - .spacing(match self.style { - BranchListStyle::Modal => ListItemSpacing::default(), - BranchListStyle::Popover => ListItemSpacing::ExtraDense, - }) .spacing(ListItemSpacing::Sparse) .toggle_state(selected) .child( v_flex() .w_full() + .overflow_hidden() .child( h_flex() - .w_full() - .flex_shrink() - .overflow_x_hidden() - .gap_2() + .gap_6() .justify_between() - .child(div().flex_shrink().overflow_x_hidden().child( - if entry.is_new { - Label::new(format!( - "Create branch \"{}\"…", - entry.branch.name() - )) - .single_line() - .into_any_element() - } else { - HighlightedLabel::new( - entry.branch.name().to_owned(), - entry.positions.clone(), - ) - .truncate() - .into_any_element() - }, - )) - .when_some(commit_time, |el, commit_time| { - el.child( + .overflow_x_hidden() + .child(branch_name) + .when_some(commit_time, |label, commit_time| { + label.child( Label::new(commit_time) .size(LabelSize::Small) .color(Color::Muted) From bbf16bda75587626cc1e2bb959e714d817aeffec Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Sat, 28 Jun 2025 05:38:18 +0530 Subject: [PATCH 037/107] editor: Improve rewrap to respect indent and prefix boundaries (#33566) 1. Fixes bug where this would not rewrap: ```rs // This is the first long comment block to be wrapped. fn my_func(a: u32); // This is the second long comment block to be wrapped. ``` 2. Comment prefix boundaries (Notice now they don't merge between different comment prefix): Initial text: ```rs // A regular long long comment to be wrapped. // A second regular long comment to be wrapped. /// A documentation long comment to be wrapped. ``` Upon rewrap: ```rs // A regular long long comment to be // wrapped. A second regular long // comment to be wrapped. /// A documentation long comment to be /// wrapped. ``` 3. Indent boundaries (Notice now they don't merge between different indentation): Initial text: ```rs fn foo() { // This is a long comment at the base indent. // This is a long comment at the base indent. // This is a long comment at the next indent. // This is a long comment at the next indent. // This is a long comment at the base indent. } ``` Upon rewrap: ```rs fn foo() { // This is a long comment at the base // indent. This is a long comment at the // base indent. // This is a long comment at the // next indent. This is a long // comment at the next indent. // This is a long comment at the base // indent. } ``` Release Notes: - Fixed an issue where rewrap would not work with selection when two comment blocks are separated with line of code. - Improved rewrap to respect changes in indentation or comment prefix (e.g. `//` vs `///`) as boundaries so that it doesn't merge them into one mangled text. --- crates/editor/src/editor.rs | 145 ++++++------ crates/editor/src/editor_tests.rs | 361 +++++++++++++----------------- 2 files changed, 234 insertions(+), 272 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 48ceaec18b40b5453901d804c8a06efae5b122b5..f3d97e19d0f1c85fdf51d2bba5a6d7d446ee52fc 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -11524,42 +11524,82 @@ impl Editor { let buffer = self.buffer.read(cx).snapshot(cx); let selections = self.selections.all::(cx); - // Shrink and split selections to respect paragraph boundaries. - let ranges = selections.into_iter().flat_map(|selection| { + // Split selections to respect paragraph, indent, and comment prefix boundaries. + let wrap_ranges = selections.into_iter().flat_map(|selection| { + let mut non_blank_rows_iter = (selection.start.row..=selection.end.row) + .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row))) + .peekable(); + + let first_row = if let Some(&row) = non_blank_rows_iter.peek() { + row + } else { + return Vec::new(); + }; + let language_settings = buffer.language_settings_at(selection.head(), cx); let language_scope = buffer.language_scope_at(selection.head()); - let Some(start_row) = (selection.start.row..=selection.end.row) - .find(|row| !buffer.is_line_blank(MultiBufferRow(*row))) - else { - return vec![]; - }; - let Some(end_row) = (selection.start.row..=selection.end.row) - .rev() - .find(|row| !buffer.is_line_blank(MultiBufferRow(*row))) - else { - return vec![]; + let mut ranges = Vec::new(); + let mut current_range_start = first_row; + let from_empty_selection = selection.is_empty(); + + let mut prev_row = first_row; + let mut prev_indent = buffer.indent_size_for_line(MultiBufferRow(first_row)); + let mut prev_comment_prefix = if let Some(language_scope) = &language_scope { + let indent = buffer.indent_size_for_line(MultiBufferRow(first_row)); + let indent_end = Point::new(first_row, indent.len); + language_scope + .line_comment_prefixes() + .iter() + .find(|prefix| buffer.contains_str_at(indent_end, prefix)) + .cloned() + } else { + None }; - let mut row = start_row; - let mut ranges = Vec::new(); - while let Some(blank_row) = - (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row))) - { - let next_paragraph_start = (blank_row + 1..=end_row) - .find(|row| !buffer.is_line_blank(MultiBufferRow(*row))) - .unwrap(); - ranges.push(( - language_settings.clone(), - language_scope.clone(), - Point::new(row, 0)..Point::new(blank_row - 1, 0), - )); - row = next_paragraph_start; + for row in non_blank_rows_iter.skip(1) { + let has_paragraph_break = row > prev_row + 1; + + let row_indent = buffer.indent_size_for_line(MultiBufferRow(row)); + let row_comment_prefix = if let Some(language_scope) = &language_scope { + let indent = buffer.indent_size_for_line(MultiBufferRow(row)); + let indent_end = Point::new(row, indent.len); + language_scope + .line_comment_prefixes() + .iter() + .find(|prefix| buffer.contains_str_at(indent_end, prefix)) + .cloned() + } else { + None + }; + + let has_boundary_change = + row_indent != prev_indent || row_comment_prefix != prev_comment_prefix; + + if has_paragraph_break || has_boundary_change { + ranges.push(( + language_settings.clone(), + Point::new(current_range_start, 0) + ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))), + prev_indent, + prev_comment_prefix.clone(), + from_empty_selection, + )); + current_range_start = row; + } + + prev_row = row; + prev_indent = row_indent; + prev_comment_prefix = row_comment_prefix; } + ranges.push(( language_settings.clone(), - language_scope.clone(), - Point::new(row, 0)..Point::new(end_row, 0), + Point::new(current_range_start, 0) + ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))), + prev_indent, + prev_comment_prefix, + from_empty_selection, )); ranges @@ -11568,9 +11608,11 @@ impl Editor { let mut edits = Vec::new(); let mut rewrapped_row_ranges = Vec::>::new(); - for (language_settings, language_scope, range) in ranges { - let mut start_row = range.start.row; - let mut end_row = range.end.row; + for (language_settings, wrap_range, indent_size, comment_prefix, from_empty_selection) in + wrap_ranges + { + let mut start_row = wrap_range.start.row; + let mut end_row = wrap_range.end.row; // Skip selections that overlap with a range that has already been rewrapped. let selection_range = start_row..end_row; @@ -11583,49 +11625,16 @@ impl Editor { let tab_size = language_settings.tab_size; - // Since not all lines in the selection may be at the same indent - // level, choose the indent size that is the most common between all - // of the lines. - // - // If there is a tie, we use the deepest indent. - let (indent_size, indent_end) = { - let mut indent_size_occurrences = HashMap::default(); - let mut rows_by_indent_size = HashMap::>::default(); - - for row in start_row..=end_row { - let indent = buffer.indent_size_for_line(MultiBufferRow(row)); - rows_by_indent_size.entry(indent).or_default().push(row); - *indent_size_occurrences.entry(indent).or_insert(0) += 1; - } - - let indent_size = indent_size_occurrences - .into_iter() - .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size))) - .map(|(indent, _)| indent) - .unwrap_or_default(); - let row = rows_by_indent_size[&indent_size][0]; - let indent_end = Point::new(row, indent_size.len); - - (indent_size, indent_end) - }; - let mut line_prefix = indent_size.chars().collect::(); - let mut inside_comment = false; - if let Some(comment_prefix) = language_scope.and_then(|language| { - language - .line_comment_prefixes() - .iter() - .find(|prefix| buffer.contains_str_at(indent_end, prefix)) - .cloned() - }) { - line_prefix.push_str(&comment_prefix); + if let Some(prefix) = &comment_prefix { + line_prefix.push_str(prefix); inside_comment = true; } let allow_rewrap_based_on_language = match language_settings.allow_rewrap { RewrapBehavior::InComments => inside_comment, - RewrapBehavior::InSelections => !range.is_empty(), + RewrapBehavior::InSelections => !wrap_range.is_empty(), RewrapBehavior::Anywhere => true, }; @@ -11636,7 +11645,7 @@ impl Editor { continue; } - if range.is_empty() { + if from_empty_selection { 'expand_upwards: while start_row > 0 { let prev_row = start_row - 1; if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 376effa91dce14f4703eec657d9fb6e04ae3d8d0..020fd068fd25174d22cc39aac12fead8ec9c7ef6 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -5149,6 +5149,7 @@ async fn test_rewrap(cx: &mut TestAppContext) { "Markdown".into(), LanguageSettingsContent { allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere), + preferred_line_length: Some(40), ..Default::default() }, ), @@ -5156,6 +5157,31 @@ async fn test_rewrap(cx: &mut TestAppContext) { "Plain Text".into(), LanguageSettingsContent { allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere), + preferred_line_length: Some(40), + ..Default::default() + }, + ), + ( + "C++".into(), + LanguageSettingsContent { + allow_rewrap: Some(language_settings::RewrapBehavior::InComments), + preferred_line_length: Some(40), + ..Default::default() + }, + ), + ( + "Python".into(), + LanguageSettingsContent { + allow_rewrap: Some(language_settings::RewrapBehavior::InComments), + preferred_line_length: Some(40), + ..Default::default() + }, + ), + ( + "Rust".into(), + LanguageSettingsContent { + allow_rewrap: Some(language_settings::RewrapBehavior::InComments), + preferred_line_length: Some(40), ..Default::default() }, ), @@ -5164,15 +5190,17 @@ async fn test_rewrap(cx: &mut TestAppContext) { let mut cx = EditorTestContext::new(cx).await; - let language_with_c_comments = Arc::new(Language::new( + let cpp_language = Arc::new(Language::new( LanguageConfig { + name: "C++".into(), line_comments: vec!["// ".into()], ..LanguageConfig::default() }, None, )); - let language_with_pound_comments = Arc::new(Language::new( + let python_language = Arc::new(Language::new( LanguageConfig { + name: "Python".into(), line_comments: vec!["# ".into()], ..LanguageConfig::default() }, @@ -5185,8 +5213,9 @@ async fn test_rewrap(cx: &mut TestAppContext) { }, None, )); - let language_with_doc_comments = Arc::new(Language::new( + let rust_language = Arc::new(Language::new( LanguageConfig { + name: "Rust".into(), line_comments: vec!["// ".into(), "/// ".into()], ..LanguageConfig::default() }, @@ -5201,296 +5230,220 @@ async fn test_rewrap(cx: &mut TestAppContext) { None, )); + // Test basic rewrapping of a long line with a cursor assert_rewrap( indoc! {" - // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros. + // ˇThis is a long comment that needs to be wrapped. "}, indoc! {" - // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit - // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus - // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam - // tincidunt hendrerit. Praesent semper egestas tellus id dignissim. - // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed - // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, - // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum - // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu - // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis - // porttitor id. Aliquam id accumsan eros. + // ˇThis is a long comment that needs to + // be wrapped. "}, - language_with_c_comments.clone(), + cpp_language.clone(), &mut cx, ); - // Test that rewrapping works inside of a selection + // Test rewrapping a full selection assert_rewrap( indoc! {" - «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ» - "}, + «// This selected long comment needs to be wrapped.ˇ»" + }, indoc! {" - «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit - // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus - // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam - // tincidunt hendrerit. Praesent semper egestas tellus id dignissim. - // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed - // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, - // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum - // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu - // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis - // porttitor id. Aliquam id accumsan eros.ˇ» - "}, - language_with_c_comments.clone(), + «// This selected long comment needs to + // be wrapped.ˇ»" + }, + cpp_language.clone(), &mut cx, ); - // Test that cursors that expand to the same region are collapsed. + // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping assert_rewrap( indoc! {" - // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. - // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. - // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, - // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros. - "}, + // ˇThis is the first line. + // Thisˇ is the second line. + // This is the thirdˇ line, all part of one paragraph. + "}, indoc! {" - // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit - // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus - // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam - // tincidunt hendrerit. Praesent semper egestas tellus id dignissim. - // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed - // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, - // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum - // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu - // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis - // porttitor id. Aliquam id accumsan eros. - "}, - language_with_c_comments.clone(), + // ˇThis is the first line. Thisˇ is the + // second line. This is the thirdˇ line, + // all part of one paragraph. + "}, + cpp_language.clone(), &mut cx, ); - // Test that non-contiguous selections are treated separately. + // Test multiple cursors in different paragraphs trigger separate rewraps assert_rewrap( indoc! {" - // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. - // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. - // - // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, - // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros. + // ˇThis is the first paragraph, first line. + // ˇThis is the first paragraph, second line. + + // ˇThis is the second paragraph, first line. + // ˇThis is the second paragraph, second line. "}, indoc! {" - // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit - // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus - // auctor, eu lacinia sapien scelerisque. - // - // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas - // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, - // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec - // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque - // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas - // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id - // vulputate turpis porttitor id. Aliquam id accumsan eros. + // ˇThis is the first paragraph, first + // line. ˇThis is the first paragraph, + // second line. + + // ˇThis is the second paragraph, first + // line. ˇThis is the second paragraph, + // second line. "}, - language_with_c_comments.clone(), + cpp_language.clone(), &mut cx, ); - // Test that different comment prefixes are supported. + // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps assert_rewrap( indoc! {" - # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros. - "}, + «// A regular long long comment to be wrapped. + /// A documentation long comment to be wrapped.ˇ» + "}, indoc! {" - # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit - # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, - # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt - # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio - # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit - # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet - # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur - # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. - # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id - # accumsan eros. - "}, - language_with_pound_comments.clone(), + «// A regular long long comment to be + // wrapped. + /// A documentation long comment to be + /// wrapped.ˇ» + "}, + rust_language.clone(), &mut cx, ); - // Test that rewrapping is ignored outside of comments in most languages. + // Test that change in indentation level trigger seperate rewraps assert_rewrap( indoc! {" - /// Adds two numbers. - /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ - fn add(a: u32, b: u32) -> u32 { - a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ + fn foo() { + «// This is a long comment at the base indent. + // This is a long comment at the next indent.ˇ» } "}, indoc! {" - /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit. - /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ - fn add(a: u32, b: u32) -> u32 { - a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ + fn foo() { + «// This is a long comment at the + // base indent. + // This is a long comment at the + // next indent.ˇ» } "}, - language_with_doc_comments.clone(), + rust_language.clone(), &mut cx, ); - // Test that rewrapping works in Markdown and Plain Text languages. + // Test that different comment prefix characters (e.g., '#') are handled correctly assert_rewrap( indoc! {" - # Hello - - Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. + # ˇThis is a long comment using a pound sign. "}, indoc! {" - # Hello - - Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit - purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, - eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt - hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio - lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet - nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. - Integer sit amet scelerisque nisi. + # ˇThis is a long comment using a pound + # sign. "}, - markdown_language, + python_language.clone(), &mut cx, ); + // Test rewrapping only affects comments, not code even when selected assert_rewrap( indoc! {" - Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. + «/// This doc comment is long and should be wrapped. + fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ» "}, indoc! {" - Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit - purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, - eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt - hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio - lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet - nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. - Integer sit amet scelerisque nisi. + «/// This doc comment is long and should + /// be wrapped. + fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ» "}, - plaintext_language.clone(), + rust_language.clone(), &mut cx, ); - // Test rewrapping unaligned comments in a selection. + // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere` assert_rewrap( indoc! {" - fn foo() { - if true { - « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. - // Praesent semper egestas tellus id dignissim.ˇ» - do_something(); - } else { - // - } - } - "}, + # Header + + A long long long line of markdown text to wrap.ˇ + "}, indoc! {" - fn foo() { - if true { - « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus - // mollis elit purus, a ornare lacus gravida vitae. Praesent semper - // egestas tellus id dignissim.ˇ» - do_something(); - } else { - // - } - } - "}, - language_with_doc_comments.clone(), + # Header + + A long long long line of markdown text + to wrap.ˇ + "}, + markdown_language, &mut cx, ); + // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere` assert_rewrap( indoc! {" - fn foo() { - if true { - «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. - // Praesent semper egestas tellus id dignissim.» - do_something(); - } else { - // - } - - } + ˇThis is a very long line of plain text that will be wrapped. "}, indoc! {" - fn foo() { - if true { - «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus - // mollis elit purus, a ornare lacus gravida vitae. Praesent semper - // egestas tellus id dignissim.» - do_something(); - } else { - // - } - - } + ˇThis is a very long line of plain text + that will be wrapped. "}, - language_with_doc_comments.clone(), + plaintext_language.clone(), &mut cx, ); + // Test that non-commented code acts as a paragraph boundary within a selection assert_rewrap( indoc! {" - «ˇone one one one one one one one one one one one one one one one one one one one one one one one one - - two» - - three - - «ˇ\t - - four four four four four four four four four four four four four four four four four four four four» + «// This is the first long comment block to be wrapped. + fn my_func(a: u32); + // This is the second long comment block to be wrapped.ˇ» + "}, + indoc! {" + «// This is the first long comment block + // to be wrapped. + fn my_func(a: u32); + // This is the second long comment block + // to be wrapped.ˇ» + "}, + rust_language.clone(), + &mut cx, + ); - «ˇfive five five five five five five five five five five five five five five five five five five five - \t» - six six six six six six six six six six six six six six six six six six six six six six six six six - "}, + // Test rewrapping multiple selections, including ones with blank lines or tabs + assert_rewrap( indoc! {" - «ˇone one one one one one one one one one one one one one one one one one one one - one one one one one + «ˇThis is a very long line that will be wrapped. - two» + This is another paragraph in the same selection.» - three - - «ˇ\t + «\tThis is a very long indented line that will be wrapped.ˇ» + "}, + indoc! {" + «ˇThis is a very long line that will be + wrapped. - four four four four four four four four four four four four four four four four - four four four four» + This is another paragraph in the same + selection.» - «ˇfive five five five five five five five five five five five five five five five - five five five five - \t» - six six six six six six six six six six six six six six six six six six six six six six six six six - "}, + «\tThis is a very long indented line + \tthat will be wrapped.ˇ» + "}, plaintext_language.clone(), &mut cx, ); + // Test that an empty comment line acts as a paragraph boundary assert_rewrap( indoc! {" - //ˇ long long long long long long long long long long long long long long long long long long long long long long long long long long long long - //ˇ - //ˇ long long long long long long long long long long long long long long long long long long long long long long long long long long long long - //ˇ short short short - int main(void) { - return 17; - } - "}, + // ˇThis is a long comment that will be wrapped. + // + // And this is another long comment that will also be wrapped.ˇ + "}, indoc! {" - //ˇ long long long long long long long long long long long long long long long - // long long long long long long long long long long long long long - //ˇ - //ˇ long long long long long long long long long long long long long long long - //ˇ long long long long long long long long long long long long long short short - // short - int main(void) { - return 17; - } - "}, - language_with_c_comments, + // ˇThis is a long comment that will be + // wrapped. + // + // And this is another long comment that + // will also be wrapped.ˇ + "}, + cpp_language, &mut cx, ); From ba4fc1bcfc1748c0b0e9edda8998d798a02feba0 Mon Sep 17 00:00:00 2001 From: 5brian Date: Fri, 27 Jun 2025 23:32:40 -0400 Subject: [PATCH 038/107] vim: Add debug panel ex command (#33560) Added :Debug to open debug panel, also added [:display](https://neovim.io/doc/user/change.html#%3Adisplay), alias to :reg Release Notes: - N/A --- crates/vim/src/command.rs | 2 ++ docs/src/vim.md | 1 + 2 files changed, 3 insertions(+) diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 839a0392d4d3b18edb6449b15c9a310c387c5ad7..4c4d6b5175f02d3c793c932d188ce5723a08cf68 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -1067,6 +1067,7 @@ fn generate_commands(_: &App) -> Vec { ) }), VimCommand::new(("reg", "isters"), ToggleRegistersView).bang(ToggleRegistersView), + VimCommand::new(("di", "splay"), ToggleRegistersView).bang(ToggleRegistersView), VimCommand::new(("marks", ""), ToggleMarksView).bang(ToggleMarksView), VimCommand::new(("delm", "arks"), ArgumentRequired) .bang(DeleteMarks::AllLocal) @@ -1085,6 +1086,7 @@ fn generate_commands(_: &App) -> Vec { VimCommand::str(("No", "tifications"), "notification_panel::ToggleFocus"), VimCommand::str(("A", "I"), "agent::ToggleFocus"), VimCommand::str(("G", "it"), "git_panel::ToggleFocus"), + VimCommand::str(("D", "ebug"), "debug_panel::ToggleFocus"), VimCommand::new(("noh", "lsearch"), search::buffer_search::Dismiss), VimCommand::new(("$", ""), EndOfDocument), VimCommand::new(("%", ""), EndOfDocument), diff --git a/docs/src/vim.md b/docs/src/vim.md index 3d3a1bac013f6fb417d297bd9c6587af68699a60..a1c79b531da21533ba6467ae46f65d3eba4bbeb8 100644 --- a/docs/src/vim.md +++ b/docs/src/vim.md @@ -288,6 +288,7 @@ These ex commands open Zed's various panels and windows. | Open the chat panel | `:Ch[at]` | | Open the AI panel | `:A[I]` | | Open the git panel | `:G[it]` | +| Open the debug panel | `:D[ebug]` | | Open the notifications panel | `:No[tif]` | | Open the feedback window | `:fe[edback]` | | Open the diagnostics window | `:cl[ist]` | From 97c5c5a6e7600b21417dadf4ece6c8615e03b843 Mon Sep 17 00:00:00 2001 From: Rift <32559038+warp-records@users.noreply.github.com> Date: Sat, 28 Jun 2025 00:05:47 -0400 Subject: [PATCH 039/107] vim: Respect count for paragraphs (#33489) Closes #32462 Release Notes: - vim: Paragraph objects now support counts (`d2ap`, `v2ap`, etc.) --------- Co-authored-by: Rift Co-authored-by: Conrad Irwin --- crates/vim/src/command.rs | 2 +- crates/vim/src/indent.rs | 3 +- crates/vim/src/normal.rs | 43 +++++++++------ crates/vim/src/normal/change.rs | 3 +- crates/vim/src/normal/convert.rs | 3 +- crates/vim/src/normal/delete.rs | 3 +- crates/vim/src/normal/paste.rs | 2 +- crates/vim/src/normal/toggle_comments.rs | 3 +- crates/vim/src/normal/yank.rs | 3 +- crates/vim/src/object.rs | 54 +++++++++++-------- crates/vim/src/replace.rs | 2 +- crates/vim/src/rewrap.rs | 3 +- crates/vim/src/surrounds.rs | 14 +++-- crates/vim/src/test.rs | 40 ++++++++++++++ crates/vim/src/visual.rs | 34 ++++++++++-- .../test_paragraph_multi_delete.json | 18 +++++++ crates/vim/test_data/test_v2ap.json | 6 +++ 17 files changed, 182 insertions(+), 54 deletions(-) create mode 100644 crates/vim/test_data/test_paragraph_multi_delete.json create mode 100644 crates/vim/test_data/test_v2ap.json diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 4c4d6b5175f02d3c793c932d188ce5723a08cf68..83df86d0e887f9802e664db79cb8259d83495d1a 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -1609,7 +1609,7 @@ impl Vim { let snapshot = editor.snapshot(window, cx); let start = editor.selections.newest_display(cx); let range = object - .range(&snapshot, start.clone(), around) + .range(&snapshot, start.clone(), around, None) .unwrap_or(start.range()); if range.start != start.start { editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { diff --git a/crates/vim/src/indent.rs b/crates/vim/src/indent.rs index c8762c563a63479b6f187d3d7d0648ee2d2a92be..b10fff8b5d1b71a2c69edd3efe878dbb913fd17e 100644 --- a/crates/vim/src/indent.rs +++ b/crates/vim/src/indent.rs @@ -122,6 +122,7 @@ impl Vim { object: Object, around: bool, dir: IndentDirection, + times: Option, window: &mut Window, cx: &mut Context, ) { @@ -133,7 +134,7 @@ impl Vim { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); original_positions.insert(selection.id, anchor); - object.expand_selection(map, selection, around); + object.expand_selection(map, selection, around, times); }); }); match dir { diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 2003c8b754613ffd288fac6166d20c700f3d1884..f25467aec454e92dbc77dde2fccecd0ccbf46986 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -277,40 +277,51 @@ impl Vim { self.exit_temporary_normal(window, cx); } - pub fn normal_object(&mut self, object: Object, window: &mut Window, cx: &mut Context) { + pub fn normal_object( + &mut self, + object: Object, + times: Option, + window: &mut Window, + cx: &mut Context, + ) { let mut waiting_operator: Option = None; match self.maybe_pop_operator() { Some(Operator::Object { around }) => match self.maybe_pop_operator() { - Some(Operator::Change) => self.change_object(object, around, window, cx), - Some(Operator::Delete) => self.delete_object(object, around, window, cx), - Some(Operator::Yank) => self.yank_object(object, around, window, cx), + Some(Operator::Change) => self.change_object(object, around, times, window, cx), + Some(Operator::Delete) => self.delete_object(object, around, times, window, cx), + Some(Operator::Yank) => self.yank_object(object, around, times, window, cx), Some(Operator::Indent) => { - self.indent_object(object, around, IndentDirection::In, window, cx) + self.indent_object(object, around, IndentDirection::In, times, window, cx) } Some(Operator::Outdent) => { - self.indent_object(object, around, IndentDirection::Out, window, cx) + self.indent_object(object, around, IndentDirection::Out, times, window, cx) } Some(Operator::AutoIndent) => { - self.indent_object(object, around, IndentDirection::Auto, window, cx) + self.indent_object(object, around, IndentDirection::Auto, times, window, cx) } Some(Operator::ShellCommand) => { self.shell_command_object(object, around, window, cx); } - Some(Operator::Rewrap) => self.rewrap_object(object, around, window, cx), + Some(Operator::Rewrap) => self.rewrap_object(object, around, times, window, cx), Some(Operator::Lowercase) => { - self.convert_object(object, around, ConvertTarget::LowerCase, window, cx) + self.convert_object(object, around, ConvertTarget::LowerCase, times, window, cx) } Some(Operator::Uppercase) => { - self.convert_object(object, around, ConvertTarget::UpperCase, window, cx) - } - Some(Operator::OppositeCase) => { - self.convert_object(object, around, ConvertTarget::OppositeCase, window, cx) + self.convert_object(object, around, ConvertTarget::UpperCase, times, window, cx) } + Some(Operator::OppositeCase) => self.convert_object( + object, + around, + ConvertTarget::OppositeCase, + times, + window, + cx, + ), Some(Operator::Rot13) => { - self.convert_object(object, around, ConvertTarget::Rot13, window, cx) + self.convert_object(object, around, ConvertTarget::Rot13, times, window, cx) } Some(Operator::Rot47) => { - self.convert_object(object, around, ConvertTarget::Rot47, window, cx) + self.convert_object(object, around, ConvertTarget::Rot47, times, window, cx) } Some(Operator::AddSurrounds { target: None }) => { waiting_operator = Some(Operator::AddSurrounds { @@ -318,7 +329,7 @@ impl Vim { }); } Some(Operator::ToggleComments) => { - self.toggle_comments_object(object, around, window, cx) + self.toggle_comments_object(object, around, times, window, cx) } Some(Operator::ReplaceWithRegister) => { self.replace_with_register_object(object, around, window, cx) diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index da8d38ea13518945b4ba7ca5c416477b99a05b6e..9485f174771cd1f21f1513e9609008dce8479b14 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -105,6 +105,7 @@ impl Vim { &mut self, object: Object, around: bool, + times: Option, window: &mut Window, cx: &mut Context, ) { @@ -115,7 +116,7 @@ impl Vim { editor.transact(window, cx, |editor, window, cx| { editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { - objects_found |= object.expand_selection(map, selection, around); + objects_found |= object.expand_selection(map, selection, around, times); }); }); if objects_found { diff --git a/crates/vim/src/normal/convert.rs b/crates/vim/src/normal/convert.rs index 4621e3ab896c0e487d9e05323e362642d684573a..25b425e847d67eb5bc3d58b1d0a2201581a1e03f 100644 --- a/crates/vim/src/normal/convert.rs +++ b/crates/vim/src/normal/convert.rs @@ -82,6 +82,7 @@ impl Vim { object: Object, around: bool, mode: ConvertTarget, + times: Option, window: &mut Window, cx: &mut Context, ) { @@ -92,7 +93,7 @@ impl Vim { let mut original_positions: HashMap<_, _> = Default::default(); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { - object.expand_selection(map, selection, around); + object.expand_selection(map, selection, around, times); original_positions.insert( selection.id, map.display_point_to_anchor(selection.start, Bias::Left), diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index 141346c99fcdc1f155e8628596c3e6805f5086aa..ccbb3dd0fd901b515258a34bb9377063e2a84cbd 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -91,6 +91,7 @@ impl Vim { &mut self, object: Object, around: bool, + times: Option, window: &mut Window, cx: &mut Context, ) { @@ -103,7 +104,7 @@ impl Vim { let mut should_move_to_start: HashSet<_> = Default::default(); editor.change_selections(Default::default(), window, cx, |s| { s.move_with(|map, selection| { - object.expand_selection(map, selection, around); + object.expand_selection(map, selection, around, times); let offset_range = selection.map(|p| p.to_offset(map, Bias::Left)).range(); let mut move_selection_start_to_previous_line = |map: &DisplaySnapshot, selection: &mut Selection| { diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 0dade838f5d5edbdca89dcea945da16a9fc89c63..67ca6314af4cbe8068342e8eee8a79de37d8c4c9 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -240,7 +240,7 @@ impl Vim { editor.set_clip_at_line_ends(false, cx); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { - object.expand_selection(map, selection, around); + object.expand_selection(map, selection, around, None); }); }); diff --git a/crates/vim/src/normal/toggle_comments.rs b/crates/vim/src/normal/toggle_comments.rs index 3b578c44cbed080758e5598bc910ed5431ade956..636ea9eec2a04d46b4a9b288590bcf510e6b604f 100644 --- a/crates/vim/src/normal/toggle_comments.rs +++ b/crates/vim/src/normal/toggle_comments.rs @@ -46,6 +46,7 @@ impl Vim { &mut self, object: Object, around: bool, + times: Option, window: &mut Window, cx: &mut Context, ) { @@ -57,7 +58,7 @@ impl Vim { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); original_positions.insert(selection.id, anchor); - object.expand_selection(map, selection, around); + object.expand_selection(map, selection, around, times); }); }); editor.toggle_comments(&Default::default(), window, cx); diff --git a/crates/vim/src/normal/yank.rs b/crates/vim/src/normal/yank.rs index 6beb81b2b6d09f2dcd696929b6858af50cb16f90..f8cc3ca7dd7be954548209449b17e78c0b59a41a 100644 --- a/crates/vim/src/normal/yank.rs +++ b/crates/vim/src/normal/yank.rs @@ -66,6 +66,7 @@ impl Vim { &mut self, object: Object, around: bool, + times: Option, window: &mut Window, cx: &mut Context, ) { @@ -75,7 +76,7 @@ impl Vim { let mut start_positions: HashMap<_, _> = Default::default(); editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.move_with(|map, selection| { - object.expand_selection(map, selection, around); + object.expand_selection(map, selection, around, times); let start_position = (selection.start, selection.goal); start_positions.insert(selection.id, start_position); }); diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 2486619608fa8206d5bd7479ad93681b922081ec..2cec4e254ae3ac49a934be9a1b80842ae4cd3f1b 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -373,10 +373,12 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { impl Vim { fn object(&mut self, object: Object, window: &mut Window, cx: &mut Context) { + let count = Self::take_count(cx); + match self.mode { - Mode::Normal => self.normal_object(object, window, cx), + Mode::Normal => self.normal_object(object, count, window, cx), Mode::Visual | Mode::VisualLine | Mode::VisualBlock => { - self.visual_object(object, window, cx) + self.visual_object(object, count, window, cx) } Mode::Insert | Mode::Replace | Mode::HelixNormal => { // Shouldn't execute a text object in insert mode. Ignoring @@ -485,6 +487,7 @@ impl Object { map: &DisplaySnapshot, selection: Selection, around: bool, + times: Option, ) -> Option> { let relative_to = selection.head(); match self { @@ -503,7 +506,8 @@ impl Object { } } Object::Sentence => sentence(map, relative_to, around), - Object::Paragraph => paragraph(map, relative_to, around), + //change others later + Object::Paragraph => paragraph(map, relative_to, around, times.unwrap_or(1)), Object::Quotes => { surrounding_markers(map, relative_to, around, self.is_multiline(), '\'', '\'') } @@ -692,8 +696,9 @@ impl Object { map: &DisplaySnapshot, selection: &mut Selection, around: bool, + times: Option, ) -> bool { - if let Some(range) = self.range(map, selection.clone(), around) { + if let Some(range) = self.range(map, selection.clone(), around, times) { selection.start = range.start; selection.end = range.end; true @@ -1399,30 +1404,37 @@ fn paragraph( map: &DisplaySnapshot, relative_to: DisplayPoint, around: bool, + times: usize, ) -> Option> { let mut paragraph_start = start_of_paragraph(map, relative_to); let mut paragraph_end = end_of_paragraph(map, relative_to); - let paragraph_end_row = paragraph_end.row(); - let paragraph_ends_with_eof = paragraph_end_row == map.max_point().row(); - let point = relative_to.to_point(map); - let current_line_is_empty = map.buffer_snapshot.is_line_blank(MultiBufferRow(point.row)); + for i in 0..times { + let paragraph_end_row = paragraph_end.row(); + let paragraph_ends_with_eof = paragraph_end_row == map.max_point().row(); + let point = relative_to.to_point(map); + let current_line_is_empty = map.buffer_snapshot.is_line_blank(MultiBufferRow(point.row)); - if around { - if paragraph_ends_with_eof { - if current_line_is_empty { - return None; - } + if around { + if paragraph_ends_with_eof { + if current_line_is_empty { + return None; + } - let paragraph_start_row = paragraph_start.row(); - if paragraph_start_row.0 != 0 { - let previous_paragraph_last_line_start = - DisplayPoint::new(paragraph_start_row - 1, 0); - paragraph_start = start_of_paragraph(map, previous_paragraph_last_line_start); + let paragraph_start_row = paragraph_start.row(); + if paragraph_start_row.0 != 0 { + let previous_paragraph_last_line_start = + Point::new(paragraph_start_row.0 - 1, 0).to_display_point(map); + paragraph_start = start_of_paragraph(map, previous_paragraph_last_line_start); + } + } else { + let mut start_row = paragraph_end_row.0 + 1; + if i > 0 { + start_row += 1; + } + let next_paragraph_start = Point::new(start_row, 0).to_display_point(map); + paragraph_end = end_of_paragraph(map, next_paragraph_start); } - } else { - let next_paragraph_start = DisplayPoint::new(paragraph_end_row + 1, 0); - paragraph_end = end_of_paragraph(map, next_paragraph_start); } } diff --git a/crates/vim/src/replace.rs b/crates/vim/src/replace.rs index bf0d977531e55565173d3164c15d11f18d31c360..15753e829003f829cddb93faa85b84104c7d92c8 100644 --- a/crates/vim/src/replace.rs +++ b/crates/vim/src/replace.rs @@ -144,7 +144,7 @@ impl Vim { editor.set_clip_at_line_ends(false, cx); let mut selection = editor.selections.newest_display(cx); let snapshot = editor.snapshot(window, cx); - object.expand_selection(&snapshot, &mut selection, around); + object.expand_selection(&snapshot, &mut selection, around, None); let start = snapshot .buffer_snapshot .anchor_before(selection.start.to_point(&snapshot)); diff --git a/crates/vim/src/rewrap.rs b/crates/vim/src/rewrap.rs index e03a3308fca52c6d11766ccb7731cbd6ec7883c4..c1d157accbc0463a79a094a084a86748a122c552 100644 --- a/crates/vim/src/rewrap.rs +++ b/crates/vim/src/rewrap.rs @@ -89,6 +89,7 @@ impl Vim { &mut self, object: Object, around: bool, + times: Option, window: &mut Window, cx: &mut Context, ) { @@ -100,7 +101,7 @@ impl Vim { s.move_with(|map, selection| { let anchor = map.display_point_to_anchor(selection.head(), Bias::Right); original_positions.insert(selection.id, anchor); - object.expand_selection(map, selection, around); + object.expand_selection(map, selection, around, times); }); }); editor.rewrap_impl( diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index 852433bc8e42ebe97d3b0f140139e20d9f8b4d6f..1f77ebda4ab02c755aaa704b4d0c772f42b7f84b 100644 --- a/crates/vim/src/surrounds.rs +++ b/crates/vim/src/surrounds.rs @@ -52,7 +52,7 @@ impl Vim { for selection in &display_selections { let range = match &target { SurroundsType::Object(object, around) => { - object.range(&display_map, selection.clone(), *around) + object.range(&display_map, selection.clone(), *around, None) } SurroundsType::Motion(motion) => { motion @@ -150,7 +150,9 @@ impl Vim { for selection in &display_selections { let start = selection.start.to_offset(&display_map, Bias::Left); - if let Some(range) = pair_object.range(&display_map, selection.clone(), true) { + if let Some(range) = + pair_object.range(&display_map, selection.clone(), true, None) + { // If the current parenthesis object is single-line, // then we need to filter whether it is the current line or not if !pair_object.is_multiline() { @@ -247,7 +249,9 @@ impl Vim { for selection in &selections { let start = selection.start.to_offset(&display_map, Bias::Left); - if let Some(range) = target.range(&display_map, selection.clone(), true) { + if let Some(range) = + target.range(&display_map, selection.clone(), true, None) + { if !target.is_multiline() { let is_same_row = selection.start.row() == range.start.row() && selection.end.row() == range.end.row(); @@ -348,7 +352,9 @@ impl Vim { for selection in &selections { let start = selection.start.to_offset(&display_map, Bias::Left); - if let Some(range) = object.range(&display_map, selection.clone(), true) { + if let Some(range) = + object.range(&display_map, selection.clone(), true, None) + { // If the current parenthesis object is single-line, // then we need to filter whether it is the current line or not if object.is_multiline() diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 346f78c1cabe483ec6305704d0889d70d24f2e99..e62d8c58efbbedf553dbc71bfc61d9b1850f1bae 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -2031,3 +2031,43 @@ async fn test_delete_unmatched_brace(cx: &mut gpui::TestAppContext) { .await .assert_eq(" oth(wow)\n oth(wow)\n"); } + +#[gpui::test] +async fn test_paragraph_multi_delete(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + cx.set_shared_state(indoc! { + " + Emacs is + ˇa great + + operating system + + all it lacks + is a + + decent text editor + " + }) + .await; + + cx.simulate_shared_keystrokes("2 d a p").await; + cx.shared_state().await.assert_eq(indoc! { + " + ˇall it lacks + is a + + decent text editor + " + }); + + cx.simulate_shared_keystrokes("d a p").await; + cx.shared_clipboard() + .await + .assert_eq("all it lacks\nis a\n\n"); + + //reset to initial state + cx.simulate_shared_keystrokes("2 u").await; + + cx.simulate_shared_keystrokes("4 d a p").await; + cx.shared_state().await.assert_eq(indoc! {"ˇ"}); +} diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 2d72881b7aed3894b48771fa7396ea8597f620e1..c3da5d21438b0734b3e537411ddf3c8d37e53508 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -364,7 +364,13 @@ impl Vim { }) } - pub fn visual_object(&mut self, object: Object, window: &mut Window, cx: &mut Context) { + pub fn visual_object( + &mut self, + object: Object, + count: Option, + window: &mut Window, + cx: &mut Context, + ) { if let Some(Operator::Object { around }) = self.active_operator() { self.pop_operator(window, cx); let current_mode = self.mode; @@ -390,7 +396,7 @@ impl Vim { ); } - if let Some(range) = object.range(map, mut_selection, around) { + if let Some(range) = object.range(map, mut_selection, around, count) { if !range.is_empty() { let expand_both_ways = object.always_expands_both_ways() || selection.is_empty() @@ -402,7 +408,7 @@ impl Vim { && object.always_expands_both_ways() { if let Some(range) = - object.range(map, selection.clone(), around) + object.range(map, selection.clone(), around, count) { selection.start = range.start; selection.end = range.end; @@ -1761,4 +1767,26 @@ mod test { }); cx.shared_clipboard().await.assert_eq("quick\n"); } + + #[gpui::test] + async fn test_v2ap(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(indoc! { + "The + quicˇk + + brown + fox" + }) + .await; + cx.simulate_shared_keystrokes("v 2 a p").await; + cx.shared_state().await.assert_eq(indoc! { + "«The + quick + + brown + fˇ»ox" + }); + } } diff --git a/crates/vim/test_data/test_paragraph_multi_delete.json b/crates/vim/test_data/test_paragraph_multi_delete.json new file mode 100644 index 0000000000000000000000000000000000000000..f706827a24c7d5c903d87c422e1da5ab4ad89f6b --- /dev/null +++ b/crates/vim/test_data/test_paragraph_multi_delete.json @@ -0,0 +1,18 @@ +{"Put":{"state":"Emacs is\nˇa great\n\noperating system\n\nall it lacks\nis a\n\ndecent text editor\n"}} +{"Key":"2"} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇall it lacks\nis a\n\ndecent text editor\n","mode":"Normal"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇdecent text editor\n","mode":"Normal"}} +{"ReadRegister":{"name":"\"","value":"all it lacks\nis a\n\n"}} +{"Key":"2"} +{"Key":"u"} +{"Key":"4"} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} diff --git a/crates/vim/test_data/test_v2ap.json b/crates/vim/test_data/test_v2ap.json new file mode 100644 index 0000000000000000000000000000000000000000..7b4d31a5dc42e19eea40af74851aba981adb63eb --- /dev/null +++ b/crates/vim/test_data/test_v2ap.json @@ -0,0 +1,6 @@ +{"Put":{"state":"The\nquicˇk\n\nbrown\nfox"}} +{"Key":"v"} +{"Key":"2"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"«The\nquick\n\nbrown\nfˇ»ox","mode":"VisualLine"}} From 1d684c889083663cf0ad51f07e3f8e2ce1a89c5c Mon Sep 17 00:00:00 2001 From: alphaArgon <49616104+alphaArgon@users.noreply.github.com> Date: Sat, 28 Jun 2025 14:50:54 +0800 Subject: [PATCH 040/107] Add shadow back for blurred/transparent window on macOS (#27403) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #15383 Closes #10993 `NSVisualEffectView` is an official API for implementing blur effects and, by traversing the layers, we **can remove the background color** that comes with the view. This avoids using private APIs and aligns better with macOS’s native design. Currently, `GPUIView` serves as the content view of the window. To add the blurred view, `GPUIView` is downgraded to a subview of the content view, placed at the same level as the blurred view. Release Notes: - Fixed the missing shadow for blurred-background windows on macOS. --------- Co-authored-by: Peter Tripp --- crates/gpui/src/platform/mac/window.rs | 176 +++++++++++++++++++++---- 1 file changed, 151 insertions(+), 25 deletions(-) diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 82a43eb760bfb9bf1bd411aea455c2e2e864758a..aedf131909a6956e9a4501b107c81ce242b80a49 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -10,10 +10,12 @@ use crate::{ use block::ConcreteBlock; use cocoa::{ appkit::{ - NSApplication, NSBackingStoreBuffered, NSColor, NSEvent, NSEventModifierFlags, - NSFilenamesPboardType, NSPasteboard, NSScreen, NSView, NSViewHeightSizable, - NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior, - NSWindowOcclusionState, NSWindowStyleMask, NSWindowTitleVisibility, + NSAppKitVersionNumber, NSAppKitVersionNumber12_0, NSApplication, NSBackingStoreBuffered, + NSColor, NSEvent, NSEventModifierFlags, NSFilenamesPboardType, NSPasteboard, NSScreen, + NSView, NSViewHeightSizable, NSViewWidthSizable, NSVisualEffectMaterial, + NSVisualEffectState, NSVisualEffectView, NSWindow, NSWindowButton, + NSWindowCollectionBehavior, NSWindowOcclusionState, NSWindowOrderingMode, + NSWindowStyleMask, NSWindowTitleVisibility, }, base::{id, nil}, foundation::{ @@ -53,6 +55,7 @@ const WINDOW_STATE_IVAR: &str = "windowState"; static mut WINDOW_CLASS: *const Class = ptr::null(); static mut PANEL_CLASS: *const Class = ptr::null(); static mut VIEW_CLASS: *const Class = ptr::null(); +static mut BLURRED_VIEW_CLASS: *const Class = ptr::null(); #[allow(non_upper_case_globals)] const NSWindowStyleMaskNonactivatingPanel: NSWindowStyleMask = @@ -241,6 +244,20 @@ unsafe fn build_classes() { } decl.register() }; + BLURRED_VIEW_CLASS = { + let mut decl = ClassDecl::new("BlurredView", class!(NSVisualEffectView)).unwrap(); + unsafe { + decl.add_method( + sel!(initWithFrame:), + blurred_view_init_with_frame as extern "C" fn(&Object, Sel, NSRect) -> id, + ); + decl.add_method( + sel!(updateLayer), + blurred_view_update_layer as extern "C" fn(&Object, Sel), + ); + decl.register() + } + }; } } @@ -335,6 +352,7 @@ struct MacWindowState { executor: ForegroundExecutor, native_window: id, native_view: NonNull, + blurred_view: Option, display_link: Option, renderer: renderer::Renderer, request_frame_callback: Option>, @@ -600,8 +618,9 @@ impl MacWindow { setReleasedWhenClosed: NO ]; + let content_view = native_window.contentView(); let native_view: id = msg_send![VIEW_CLASS, alloc]; - let native_view = NSView::init(native_view); + let native_view = NSView::initWithFrame_(native_view, NSView::bounds(content_view)); assert!(!native_view.is_null()); let mut window = Self(Arc::new(Mutex::new(MacWindowState { @@ -609,6 +628,7 @@ impl MacWindow { executor, native_window, native_view: NonNull::new_unchecked(native_view), + blurred_view: None, display_link: None, renderer: renderer::new_renderer( renderer_context, @@ -683,11 +703,11 @@ impl MacWindow { // itself and break the association with its context. native_view.setWantsLayer(YES); let _: () = msg_send![ - native_view, - setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize + native_view, + setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize ]; - native_window.setContentView_(native_view.autorelease()); + content_view.addSubview_(native_view.autorelease()); native_window.makeFirstResponder_(native_view); match kind { @@ -1035,28 +1055,57 @@ impl PlatformWindow for MacWindow { fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) { let mut this = self.0.as_ref().lock(); - this.renderer - .update_transparency(background_appearance != WindowBackgroundAppearance::Opaque); - let blur_radius = if background_appearance == WindowBackgroundAppearance::Blurred { - 80 - } else { - 0 - }; - let opaque = (background_appearance == WindowBackgroundAppearance::Opaque).to_objc(); + let opaque = background_appearance == WindowBackgroundAppearance::Opaque; + this.renderer.update_transparency(!opaque); unsafe { - this.native_window.setOpaque_(opaque); - // Shadows for transparent windows cause artifacts and performance issues - this.native_window.setHasShadow_(opaque); - let clear_color = if opaque == YES { + this.native_window.setOpaque_(opaque as BOOL); + let background_color = if opaque { NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0f64, 0f64, 0f64, 1f64) } else { - NSColor::clearColor(nil) + // Not using `+[NSColor clearColor]` to avoid broken shadow. + NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0f64, 0f64, 0f64, 0.0001) }; - this.native_window.setBackgroundColor_(clear_color); - let window_number = this.native_window.windowNumber(); - CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, blur_radius); + this.native_window.setBackgroundColor_(background_color); + + if NSAppKitVersionNumber < NSAppKitVersionNumber12_0 { + // Whether `-[NSVisualEffectView respondsToSelector:@selector(_updateProxyLayer)]`. + // On macOS Catalina/Big Sur `NSVisualEffectView` doesn’t own concrete sublayers + // but uses a `CAProxyLayer`. Use the legacy WindowServer API. + let blur_radius = if background_appearance == WindowBackgroundAppearance::Blurred { + 80 + } else { + 0 + }; + + let window_number = this.native_window.windowNumber(); + CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, blur_radius); + } else { + // On newer macOS `NSVisualEffectView` manages the effect layer directly. Using it + // could have a better performance (it downsamples the backdrop) and more control + // over the effect layer. + if background_appearance != WindowBackgroundAppearance::Blurred { + if let Some(blur_view) = this.blurred_view { + NSView::removeFromSuperview(blur_view); + this.blurred_view = None; + } + } else if this.blurred_view == None { + let content_view = this.native_window.contentView(); + let frame = NSView::bounds(content_view); + let mut blur_view: id = msg_send![BLURRED_VIEW_CLASS, alloc]; + blur_view = NSView::initWithFrame_(blur_view, frame); + blur_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable); + + let _: () = msg_send![ + content_view, + addSubview: blur_view + positioned: NSWindowOrderingMode::NSWindowBelow + relativeTo: nil + ]; + this.blurred_view = Some(blur_view.autorelease()); + } + } } } @@ -1763,7 +1812,12 @@ extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) { let mut lock = window_state.as_ref().lock(); let new_size = Size::::from(size); - if lock.content_size() == new_size { + let old_size = unsafe { + let old_frame: NSRect = msg_send![this, frame]; + Size::::from(old_frame.size) + }; + + if old_size == new_size { return; } @@ -2148,3 +2202,75 @@ unsafe fn display_id_for_screen(screen: id) -> CGDirectDisplayID { screen_number as CGDirectDisplayID } } + +extern "C" fn blurred_view_init_with_frame(this: &Object, _: Sel, frame: NSRect) -> id { + unsafe { + let view = msg_send![super(this, class!(NSVisualEffectView)), initWithFrame: frame]; + // Use a colorless semantic material. The default value `AppearanceBased`, though not + // manually set, is deprecated. + NSVisualEffectView::setMaterial_(view, NSVisualEffectMaterial::Selection); + NSVisualEffectView::setState_(view, NSVisualEffectState::Active); + view + } +} + +extern "C" fn blurred_view_update_layer(this: &Object, _: Sel) { + unsafe { + let _: () = msg_send![super(this, class!(NSVisualEffectView)), updateLayer]; + let layer: id = msg_send![this, layer]; + if !layer.is_null() { + remove_layer_background(layer); + } + } +} + +unsafe fn remove_layer_background(layer: id) { + unsafe { + let _: () = msg_send![layer, setBackgroundColor:nil]; + + let class_name: id = msg_send![layer, className]; + if class_name.isEqualToString("CAChameleonLayer") { + // Remove the desktop tinting effect. + let _: () = msg_send![layer, setHidden: YES]; + return; + } + + let filters: id = msg_send![layer, filters]; + if !filters.is_null() { + // Remove the increased saturation. + // The effect of a `CAFilter` or `CIFilter` is determined by its name, and the + // `description` reflects its name and some parameters. Currently `NSVisualEffectView` + // uses a `CAFilter` named "colorSaturate". If one day they switch to `CIFilter`, the + // `description` will still contain "Saturat" ("... inputSaturation = ..."). + let test_string: id = NSString::alloc(nil).init_str("Saturat").autorelease(); + let count = NSArray::count(filters); + for i in 0..count { + let description: id = msg_send![filters.objectAtIndex(i), description]; + let hit: BOOL = msg_send![description, containsString: test_string]; + if hit == NO { + continue; + } + + let all_indices = NSRange { + location: 0, + length: count, + }; + let indices: id = msg_send![class!(NSMutableIndexSet), indexSet]; + let _: () = msg_send![indices, addIndexesInRange: all_indices]; + let _: () = msg_send![indices, removeIndex:i]; + let filtered: id = msg_send![filters, objectsAtIndexes: indices]; + let _: () = msg_send![layer, setFilters: filtered]; + break; + } + } + + let sublayers: id = msg_send![layer, sublayers]; + if !sublayers.is_null() { + let count = NSArray::count(sublayers); + for i in 0..count { + let sublayer = sublayers.objectAtIndex(i); + remove_layer_background(sublayer); + } + } + } +} From 3f4098e87b4130cf56c4087c4d8e1ab05b1c506a Mon Sep 17 00:00:00 2001 From: Umesh Yadav <23421535+imumesh18@users.noreply.github.com> Date: Sat, 28 Jun 2025 18:08:27 +0530 Subject: [PATCH 041/107] open_ai: Make OpenAI error message generic (#33383) Context: In this PR: https://github.com/zed-industries/zed/pull/33362, we started to use underlying open_ai crate for making api calls for vercel as well. Now whenever we get the error we get something like the below. Where on part of the error mentions OpenAI but the rest of the error returns the actual error from provider. This PR tries to make the error generic for now so that people don't get confused seeing OpenAI in their v0 integration. ``` Error interacting with language model Failed to connect to OpenAI API: 403 Forbidden {"success":false,"error":"Premium or Team plan required to access the v0 API: https://v0.dev/chat/settings/billing"} ``` Release Notes: - N/A --- crates/language_models/src/provider/lmstudio.rs | 2 +- crates/open_ai/src/open_ai.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/language_models/src/provider/lmstudio.rs b/crates/language_models/src/provider/lmstudio.rs index e0fcf38f38e1eb46f22eed8705344389dcb31848..519647b3bc89b303510b0439f7de13df18f9ac90 100644 --- a/crates/language_models/src/provider/lmstudio.rs +++ b/crates/language_models/src/provider/lmstudio.rs @@ -565,7 +565,7 @@ impl LmStudioEventMapper { events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::ToolUse))); } Some(stop_reason) => { - log::error!("Unexpected OpenAI stop_reason: {stop_reason:?}",); + log::error!("Unexpected LMStudio stop_reason: {stop_reason:?}",); events.push(Ok(LanguageModelCompletionEvent::Stop(StopReason::EndTurn))); } None => {} diff --git a/crates/open_ai/src/open_ai.rs b/crates/open_ai/src/open_ai.rs index 5b09aa5cbc17a0c48e4a1fadcbdd0b44cba98e1c..12a5cf52d2efe7bf1d94bfc45ed629e38bc94382 100644 --- a/crates/open_ai/src/open_ai.rs +++ b/crates/open_ai/src/open_ai.rs @@ -445,12 +445,14 @@ pub async fn stream_completion( match serde_json::from_str::(&body) { Ok(response) if !response.error.message.is_empty() => Err(anyhow!( - "Failed to connect to OpenAI API: {}", + "API request to {} failed: {}", + api_url, response.error.message, )), _ => anyhow::bail!( - "Failed to connect to OpenAI API: {} {}", + "API request to {} failed with status {}: {}", + api_url, response.status(), body, ), From c8c6468f9c283455a7197abde9890050ef07f56f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 28 Jun 2025 10:23:57 -0600 Subject: [PATCH 042/107] vim: Non-interactive shell (#33568) Closes #33144 Release Notes: - vim: Run r! in a non-interactive shell --- crates/project/src/terminals.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 00e12a312f860efde4dee562c6efd0f748650843..b4e1093293b6275b9da68075425dd3b75b5bb335 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -148,7 +148,7 @@ impl Project { let ssh_details = self.ssh_details(cx); let settings = self.terminal_settings(&path, cx).clone(); - let builder = ShellBuilder::new(ssh_details.is_none(), &settings.shell); + let builder = ShellBuilder::new(ssh_details.is_none(), &settings.shell).non_interactive(); let (command, args) = builder.build(command, &Vec::new()); let mut env = self From 521a22368113846f9eac1c73fb3282188d07f229 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Sat, 28 Jun 2025 15:35:59 -0400 Subject: [PATCH 043/107] Add `editor::Rewrap` binding to Emacs keymaps (#33588) `M-q` is `fill-paragraph` which is like `editor::Rewrap`. Release Notes: - emacs: Bound `alt-q` to `editor::Rewrap` (like `M-q` or `M-x fill-paragraph`) --- assets/keymaps/linux/emacs.json | 3 ++- assets/keymaps/macos/emacs.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/assets/keymaps/linux/emacs.json b/assets/keymaps/linux/emacs.json index d1453da4850226d9168410f55c0743b17a16ed1f..26482f66f5054235022db8ebd748bd9b94aac799 100755 --- a/assets/keymaps/linux/emacs.json +++ b/assets/keymaps/linux/emacs.json @@ -59,7 +59,8 @@ "alt->": "editor::MoveToEnd", // end-of-buffer "ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom "ctrl-s": "buffer_search::Deploy", // isearch-forward - "alt-^": "editor::JoinLines" // join-line + "alt-^": "editor::JoinLines", // join-line + "alt-q": "editor::Rewrap" // fill-paragraph } }, { diff --git a/assets/keymaps/macos/emacs.json b/assets/keymaps/macos/emacs.json index d1453da4850226d9168410f55c0743b17a16ed1f..26482f66f5054235022db8ebd748bd9b94aac799 100755 --- a/assets/keymaps/macos/emacs.json +++ b/assets/keymaps/macos/emacs.json @@ -59,7 +59,8 @@ "alt->": "editor::MoveToEnd", // end-of-buffer "ctrl-l": "editor::ScrollCursorCenterTopBottom", // recenter-top-bottom "ctrl-s": "buffer_search::Deploy", // isearch-forward - "alt-^": "editor::JoinLines" // join-line + "alt-^": "editor::JoinLines", // join-line + "alt-q": "editor::Rewrap" // fill-paragraph } }, { From 41583fb066629d1e54d600e930be068a68984c5c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 29 Jun 2025 00:10:49 +0300 Subject: [PATCH 044/107] Fix document colors issues with other inlays and multi buffers (#33598) Closes https://github.com/zed-industries/zed/issues/33575 * Fixes inlay colors spoiled after document color displayed * Optimizes the query pattern for large multi buffers Release Notes: - Fixed document colors issues with other inlays and multi buffers --- crates/editor/src/display_map/inlay_map.rs | 4 +- crates/editor/src/editor.rs | 22 +- crates/editor/src/editor_tests.rs | 106 +++++-- crates/editor/src/inlay_hint_cache.rs | 6 +- crates/editor/src/lsp_colors.rs | 53 ++-- crates/editor/src/scroll.rs | 6 +- crates/project/src/lsp_store.rs | 333 +++++++++------------ crates/project/src/project.rs | 2 +- 8 files changed, 285 insertions(+), 247 deletions(-) diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 33fc5540d63f20e5108e438f38c3cba4703ad927..e7d8868d42ced70485de4e718f0b57d82aa257c1 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -327,9 +327,9 @@ impl<'a> Iterator for InlayChunks<'a> { InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint, InlayId::Color(_) => match inlay.color { Some(color) => { - let style = self.highlight_styles.inlay_hint.get_or_insert_default(); + let mut style = self.highlight_styles.inlay_hint.unwrap_or_default(); style.color = Some(color); - Some(*style) + Some(style) } None => self.highlight_styles.inlay_hint, }, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f3d97e19d0f1c85fdf51d2bba5a6d7d446ee52fc..fedd9222ec00fedeeae58b0526504d3762100c08 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1845,13 +1845,13 @@ impl Editor { editor .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx); } - project::Event::LanguageServerAdded(server_id, ..) - | project::Event::LanguageServerRemoved(server_id) => { + project::Event::LanguageServerAdded(..) + | project::Event::LanguageServerRemoved(..) => { if editor.tasks_update_task.is_none() { editor.tasks_update_task = Some(editor.refresh_runnables(window, cx)); } - editor.update_lsp_data(Some(*server_id), None, window, cx); + editor.update_lsp_data(true, None, window, cx); } project::Event::SnippetEdit(id, snippet_edits) => { if let Some(buffer) = editor.buffer.read(cx).buffer(*id) { @@ -2291,7 +2291,7 @@ impl Editor { editor.minimap = editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx); editor.colors = Some(LspColorData::new(cx)); - editor.update_lsp_data(None, None, window, cx); + editor.update_lsp_data(false, None, window, cx); } editor.report_editor_event("Editor Opened", None, cx); @@ -5103,7 +5103,7 @@ impl Editor { to_insert, }) = self.inlay_hint_cache.spawn_hint_refresh( reason_description, - self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx), + self.visible_excerpts(required_languages.as_ref(), cx), invalidate_cache, ignore_debounce, cx, @@ -5121,7 +5121,7 @@ impl Editor { .collect() } - pub fn excerpts_for_inlay_hints_query( + pub fn visible_excerpts( &self, restrict_to_languages: Option<&HashSet>>, cx: &mut Context, @@ -19562,7 +19562,7 @@ impl Editor { cx.emit(SearchEvent::MatchesInvalidated); if let Some(buffer) = edited_buffer { - self.update_lsp_data(None, Some(buffer.read(cx).remote_id()), window, cx); + self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx); } if *singleton_buffer_edited { @@ -19627,7 +19627,7 @@ impl Editor { .detach(); } } - self.update_lsp_data(None, Some(buffer_id), window, cx); + self.update_lsp_data(false, Some(buffer_id), window, cx); cx.emit(EditorEvent::ExcerptsAdded { buffer: buffer.clone(), predecessor: *predecessor, @@ -19813,7 +19813,7 @@ impl Editor { if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() { self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx); } - self.refresh_colors(None, None, window, cx); + self.refresh_colors(false, None, window, cx); } cx.notify(); @@ -20714,13 +20714,13 @@ impl Editor { fn update_lsp_data( &mut self, - for_server_id: Option, + ignore_cache: bool, for_buffer: Option, window: &mut Window, cx: &mut Context<'_, Self>, ) { self.pull_diagnostics(for_buffer, window, cx); - self.refresh_colors(for_server_id, for_buffer, window, cx); + self.refresh_colors(ignore_cache, for_buffer, window, cx); } } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 020fd068fd25174d22cc39aac12fead8ec9c7ef6..a6bbe6d621d7901f85b414949cc41a3afa47248a 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -55,7 +55,8 @@ use util::{ uri, }; use workspace::{ - CloseActiveItem, CloseAllItems, CloseInactiveItems, NavigationEntry, OpenOptions, ViewId, + CloseActiveItem, CloseAllItems, CloseInactiveItems, MoveItemToPaneInDirection, NavigationEntry, + OpenOptions, ViewId, item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions}, }; @@ -22601,8 +22602,8 @@ async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppC ); } -#[gpui::test] -async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { +#[gpui::test(iterations = 10)] +async fn test_document_colors(cx: &mut TestAppContext) { let expected_color = Rgba { r: 0.33, g: 0.33, @@ -22723,24 +22724,73 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { .set_request_handler::(move |_, _| async move { panic!("Should not be called"); }); - color_request_handle.next().await.unwrap(); - cx.run_until_parked(); + cx.executor().advance_clock(Duration::from_millis(100)); color_request_handle.next().await.unwrap(); cx.run_until_parked(); assert_eq!( - 3, + 1, requests_made.load(atomic::Ordering::Acquire), - "Should query for colors once per editor open (1) and once after the language server startup (2)" + "Should query for colors once per editor open" ); - - cx.executor().advance_clock(Duration::from_millis(500)); - let save = editor.update_in(cx, |editor, window, cx| { + editor.update_in(cx, |editor, _, cx| { assert_eq!( vec![expected_color], extract_color_inlays(editor, cx), "Should have an initial inlay" ); + }); + // opening another file in a split should not influence the LSP query counter + workspace + .update(cx, |workspace, window, cx| { + assert_eq!( + workspace.panes().len(), + 1, + "Should have one pane with one editor" + ); + workspace.move_item_to_pane_in_direction( + &MoveItemToPaneInDirection { + direction: SplitDirection::Right, + focus: false, + clone: true, + }, + window, + cx, + ); + }) + .unwrap(); + cx.run_until_parked(); + workspace + .update(cx, |workspace, _, cx| { + let panes = workspace.panes(); + assert_eq!(panes.len(), 2, "Should have two panes after splitting"); + for pane in panes { + let editor = pane + .read(cx) + .active_item() + .and_then(|item| item.downcast::()) + .expect("Should have opened an editor in each split"); + let editor_file = editor + .read(cx) + .buffer() + .read(cx) + .as_singleton() + .expect("test deals with singleton buffers") + .read(cx) + .file() + .expect("test buffese should have a file") + .path(); + assert_eq!( + editor_file.as_ref(), + Path::new("first.rs"), + "Both editors should be opened for the same file" + ) + } + }) + .unwrap(); + + cx.executor().advance_clock(Duration::from_millis(500)); + let save = editor.update_in(cx, |editor, window, cx| { editor.move_to_end(&MoveToEnd, window, cx); editor.handle_input("dirty", window, cx); editor.save( @@ -22755,12 +22805,10 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { }); save.await.unwrap(); - color_request_handle.next().await.unwrap(); - cx.run_until_parked(); color_request_handle.next().await.unwrap(); cx.run_until_parked(); assert_eq!( - 5, + 3, requests_made.load(atomic::Ordering::Acquire), "Should query for colors once per save and once per formatting after save" ); @@ -22774,11 +22822,27 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { }) .unwrap(); close.await.unwrap(); + let close = workspace + .update(cx, |workspace, window, cx| { + workspace.active_pane().update(cx, |pane, cx| { + pane.close_active_item(&CloseActiveItem::default(), window, cx) + }) + }) + .unwrap(); + close.await.unwrap(); assert_eq!( - 5, + 3, requests_made.load(atomic::Ordering::Acquire), - "After saving and closing the editor, no extra requests should be made" + "After saving and closing all editors, no extra requests should be made" ); + workspace + .update(cx, |workspace, _, cx| { + assert!( + workspace.active_item(cx).is_none(), + "Should close all editors" + ) + }) + .unwrap(); workspace .update(cx, |workspace, window, cx| { @@ -22788,13 +22852,7 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { }) .unwrap(); cx.executor().advance_clock(Duration::from_millis(100)); - color_request_handle.next().await.unwrap(); cx.run_until_parked(); - assert_eq!( - 6, - requests_made.load(atomic::Ordering::Acquire), - "After navigating back to an editor and reopening it, another color request should be made" - ); let editor = workspace .update(cx, |workspace, _, cx| { workspace @@ -22804,6 +22862,12 @@ async fn test_mtime_and_document_colors(cx: &mut TestAppContext) { .expect("Should be an editor") }) .unwrap(); + color_request_handle.next().await.unwrap(); + assert_eq!( + 3, + requests_made.load(atomic::Ordering::Acquire), + "Cache should be reused on buffer close and reopen" + ); editor.update(cx, |editor, cx| { assert_eq!( vec![expected_color], diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 647f34487ffc3cd8e688dffa9051737b3e44321e..db01cc7ad1d668520f9650c7d396156814c50ba1 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -956,7 +956,7 @@ fn fetch_and_update_hints( .update(cx, |editor, cx| { if got_throttled { let query_not_around_visible_range = match editor - .excerpts_for_inlay_hints_query(None, cx) + .visible_excerpts(None, cx) .remove(&query.excerpt_id) { Some((_, _, current_visible_range)) => { @@ -2525,9 +2525,7 @@ pub mod tests { cx: &mut gpui::TestAppContext, ) -> Range { let ranges = editor - .update(cx, |editor, _window, cx| { - editor.excerpts_for_inlay_hints_query(None, cx) - }) + .update(cx, |editor, _window, cx| editor.visible_excerpts(None, cx)) .unwrap(); assert_eq!( ranges.len(), diff --git a/crates/editor/src/lsp_colors.rs b/crates/editor/src/lsp_colors.rs index bacd61199efbb1fa988ff0d9b6762d2bd24dc099..7f771b9266591aae334106087acd8278c56b9dfd 100644 --- a/crates/editor/src/lsp_colors.rs +++ b/crates/editor/src/lsp_colors.rs @@ -3,10 +3,10 @@ use std::{cmp, ops::Range}; use collections::HashMap; use futures::future::join_all; use gpui::{Hsla, Rgba}; +use itertools::Itertools; use language::point_from_lsp; -use lsp::LanguageServerId; use multi_buffer::Anchor; -use project::DocumentColor; +use project::{DocumentColor, lsp_store::ColorFetchStrategy}; use settings::Settings as _; use text::{Bias, BufferId, OffsetRangeExt as _}; use ui::{App, Context, Window}; @@ -19,6 +19,7 @@ use crate::{ #[derive(Debug)] pub(super) struct LspColorData { + cache_version_used: usize, colors: Vec<(Range, DocumentColor, InlayId)>, inlay_colors: HashMap, render_mode: DocumentColorsRenderMode, @@ -27,6 +28,7 @@ pub(super) struct LspColorData { impl LspColorData { pub fn new(cx: &App) -> Self { Self { + cache_version_used: 0, colors: Vec::new(), inlay_colors: HashMap::default(), render_mode: EditorSettings::get_global(cx).lsp_document_colors, @@ -122,7 +124,7 @@ impl LspColorData { impl Editor { pub(super) fn refresh_colors( &mut self, - for_server_id: Option, + ignore_cache: bool, buffer_id: Option, _: &Window, cx: &mut Context, @@ -141,29 +143,41 @@ impl Editor { return; } + let visible_buffers = self + .visible_excerpts(None, cx) + .into_values() + .map(|(buffer, ..)| buffer) + .filter(|editor_buffer| { + buffer_id.is_none_or(|buffer_id| buffer_id == editor_buffer.read(cx).remote_id()) + }) + .unique_by(|buffer| buffer.read(cx).remote_id()) + .collect::>(); + let all_colors_task = project.read(cx).lsp_store().update(cx, |lsp_store, cx| { - self.buffer() - .update(cx, |multi_buffer, cx| { - multi_buffer - .all_buffers() - .into_iter() - .filter(|editor_buffer| { - buffer_id.is_none_or(|buffer_id| { - buffer_id == editor_buffer.read(cx).remote_id() - }) - }) - .collect::>() - }) + visible_buffers .into_iter() .filter_map(|buffer| { let buffer_id = buffer.read(cx).remote_id(); - let colors_task = lsp_store.document_colors(for_server_id, buffer, cx)?; + let fetch_strategy = if ignore_cache { + ColorFetchStrategy::IgnoreCache + } else { + ColorFetchStrategy::UseCache { + known_cache_version: self + .colors + .as_ref() + .map(|colors| colors.cache_version_used), + } + }; + let colors_task = lsp_store.document_colors(fetch_strategy, buffer, cx)?; Some(async move { (buffer_id, colors_task.await) }) }) .collect::>() }); cx.spawn(async move |editor, cx| { let all_colors = join_all(all_colors_task).await; + if all_colors.is_empty() { + return; + } let Ok((multi_buffer_snapshot, editor_excerpts)) = editor.update(cx, |editor, cx| { let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx); let editor_excerpts = multi_buffer_snapshot.excerpts().fold( @@ -187,6 +201,7 @@ impl Editor { return; }; + let mut cache_version = None; let mut new_editor_colors = Vec::<(Range, DocumentColor)>::new(); for (buffer_id, colors) in all_colors { let Some(excerpts) = editor_excerpts.get(&buffer_id) else { @@ -194,7 +209,8 @@ impl Editor { }; match colors { Ok(colors) => { - for color in colors { + cache_version = colors.cache_version; + for color in colors.colors { let color_start = point_from_lsp(color.lsp_range.start); let color_end = point_from_lsp(color.lsp_range.end); @@ -337,6 +353,9 @@ impl Editor { } let mut updated = colors.set_colors(new_color_inlays); + if let Some(cache_version) = cache_version { + colors.cache_version_used = cache_version; + } if colors.render_mode == DocumentColorsRenderMode::Inlay && (!colors_splice.to_insert.is_empty() || !colors_splice.to_remove.is_empty()) diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 6cc483cb650d102ba3a8f569f9ac3e99cb95727c..0642b2b20ebfb7213f74ab6980889a7e07218415 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -487,8 +487,9 @@ impl Editor { if opened_first_time { cx.spawn_in(window, async move |editor, cx| { editor - .update(cx, |editor, cx| { - editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx) + .update_in(cx, |editor, window, cx| { + editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); + editor.refresh_colors(false, None, window, cx); }) .ok() }) @@ -599,6 +600,7 @@ impl Editor { ); self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx); + self.refresh_colors(false, None, window, cx); } pub fn scroll_position(&self, cx: &mut Context) -> gpui::Point { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 15057ac7f201d6f9da8478d60bcd9388e213258b..bf269ba1d7bcd0886a480376f50060d70e96a195 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -3542,23 +3542,29 @@ pub struct LspStore { _maintain_buffer_languages: Task<()>, diagnostic_summaries: HashMap, HashMap>>, - lsp_data: Option, + lsp_data: HashMap, } -type DocumentColorTask = - Shared, Arc>>>; - -#[derive(Debug)] -struct LspData { - mtime: MTime, - buffer_lsp_data: HashMap>, - colors_update: HashMap, - last_version_queried: HashMap, +#[derive(Debug, Default, Clone)] +pub struct DocumentColors { + pub colors: HashSet, + pub cache_version: Option, } +type DocumentColorTask = Shared>>>; + #[derive(Debug, Default)] -struct BufferLspData { - colors: Option>, +struct DocumentColorData { + colors_for_version: Global, + colors: HashMap>, + cache_version: usize, + colors_update: Option<(Global, DocumentColorTask)>, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ColorFetchStrategy { + IgnoreCache, + UseCache { known_cache_version: Option }, } #[derive(Debug)] @@ -3792,7 +3798,7 @@ impl LspStore { language_server_statuses: Default::default(), nonce: StdRng::from_entropy().r#gen(), diagnostic_summaries: HashMap::default(), - lsp_data: None, + lsp_data: HashMap::default(), active_entry: None, _maintain_workspace_config, _maintain_buffer_languages: Self::maintain_buffer_languages(languages, cx), @@ -3849,7 +3855,7 @@ impl LspStore { language_server_statuses: Default::default(), nonce: StdRng::from_entropy().r#gen(), diagnostic_summaries: HashMap::default(), - lsp_data: None, + lsp_data: HashMap::default(), active_entry: None, toolchain_store, _maintain_workspace_config, @@ -4138,15 +4144,20 @@ impl LspStore { local.register_buffer_with_language_servers(buffer, only_register_servers, cx); } if !ignore_refcounts { - cx.observe_release(&handle, move |this, buffer, cx| { - let local = this.as_local_mut().unwrap(); - let Some(refcount) = local.registered_buffers.get_mut(&buffer_id) else { - debug_panic!("bad refcounting"); - return; - }; + cx.observe_release(&handle, move |lsp_store, buffer, cx| { + let refcount = { + let local = lsp_store.as_local_mut().unwrap(); + let Some(refcount) = local.registered_buffers.get_mut(&buffer_id) else { + debug_panic!("bad refcounting"); + return; + }; - *refcount -= 1; - if *refcount == 0 { + *refcount -= 1; + *refcount + }; + if refcount == 0 { + lsp_store.lsp_data.remove(&buffer_id); + let local = lsp_store.as_local_mut().unwrap(); local.registered_buffers.remove(&buffer_id); if let Some(file) = File::from_dyn(buffer.read(cx).file()).cloned() { local.unregister_old_buffer_from_language_servers(&buffer, &file, cx); @@ -5012,7 +5023,7 @@ impl LspStore { .presentations .into_iter() .map(|presentation| ColorPresentation { - label: presentation.label, + label: SharedString::from(presentation.label), text_edit: presentation.text_edit.and_then(deserialize_lsp_edit), additional_text_edits: presentation .additional_text_edits @@ -5055,7 +5066,7 @@ impl LspStore { .context("color presentation resolve LSP request")? .into_iter() .map(|presentation| ColorPresentation { - label: presentation.label, + label: SharedString::from(presentation.label), text_edit: presentation.text_edit, additional_text_edits: presentation .additional_text_edits @@ -6210,135 +6221,127 @@ impl LspStore { pub fn document_colors( &mut self, - for_server_id: Option, + fetch_strategy: ColorFetchStrategy, buffer: Entity, cx: &mut Context, ) -> Option { - let buffer_mtime = buffer.read(cx).saved_mtime()?; - let buffer_version = buffer.read(cx).version(); - let abs_path = File::from_dyn(buffer.read(cx).file())?.abs_path(cx); - - let mut received_colors_data = false; - let buffer_lsp_data = self - .lsp_data - .as_ref() - .into_iter() - .filter(|lsp_data| { - if buffer_mtime == lsp_data.mtime { - lsp_data - .last_version_queried - .get(&abs_path) - .is_none_or(|version_queried| { - !buffer_version.changed_since(version_queried) - }) - } else { - !buffer_mtime.bad_is_greater_than(lsp_data.mtime) - } - }) - .flat_map(|lsp_data| lsp_data.buffer_lsp_data.values()) - .filter_map(|buffer_data| buffer_data.get(&abs_path)) - .filter_map(|buffer_data| { - let colors = buffer_data.colors.as_ref()?; - received_colors_data = true; - Some(colors) - }) - .flatten() - .cloned() - .collect::>(); - - if buffer_lsp_data.is_empty() || for_server_id.is_some() { - if received_colors_data && for_server_id.is_none() { - return None; - } - - let mut outdated_lsp_data = false; - if self.lsp_data.is_none() - || self.lsp_data.as_ref().is_some_and(|lsp_data| { - if buffer_mtime == lsp_data.mtime { - lsp_data - .last_version_queried - .get(&abs_path) - .is_none_or(|version_queried| { - buffer_version.changed_since(version_queried) - }) - } else { - buffer_mtime.bad_is_greater_than(lsp_data.mtime) - } - }) - { - self.lsp_data = Some(LspData { - mtime: buffer_mtime, - buffer_lsp_data: HashMap::default(), - colors_update: HashMap::default(), - last_version_queried: HashMap::default(), - }); - outdated_lsp_data = true; - } + let version_queried_for = buffer.read(cx).version(); + let buffer_id = buffer.read(cx).remote_id(); - { - let lsp_data = self.lsp_data.as_mut()?; - match for_server_id { - Some(for_server_id) if !outdated_lsp_data => { - lsp_data.buffer_lsp_data.remove(&for_server_id); - } - None | Some(_) => { - let existing_task = lsp_data.colors_update.get(&abs_path).cloned(); - if !outdated_lsp_data && existing_task.is_some() { - return existing_task; - } - for buffer_data in lsp_data.buffer_lsp_data.values_mut() { - if let Some(buffer_data) = buffer_data.get_mut(&abs_path) { - buffer_data.colors = None; - } + match fetch_strategy { + ColorFetchStrategy::IgnoreCache => {} + ColorFetchStrategy::UseCache { + known_cache_version, + } => { + if let Some(cached_data) = self.lsp_data.get(&buffer_id) { + if !version_queried_for.changed_since(&cached_data.colors_for_version) { + if Some(cached_data.cache_version) == known_cache_version { + return None; + } else { + return Some( + Task::ready(Ok(DocumentColors { + colors: cached_data + .colors + .values() + .flatten() + .cloned() + .collect(), + cache_version: Some(cached_data.cache_version), + })) + .shared(), + ); } } } } + } - let task_abs_path = abs_path.clone(); - let new_task = cx - .spawn(async move |lsp_store, cx| { - match fetch_document_colors( - lsp_store.clone(), - buffer, - task_abs_path.clone(), - cx, - ) + let lsp_data = self.lsp_data.entry(buffer_id).or_default(); + if let Some((updating_for, running_update)) = &lsp_data.colors_update { + if !version_queried_for.changed_since(&updating_for) { + return Some(running_update.clone()); + } + } + let query_version_queried_for = version_queried_for.clone(); + let new_task = cx + .spawn(async move |lsp_store, cx| { + cx.background_executor() + .timer(Duration::from_millis(30)) + .await; + let fetched_colors = lsp_store + .update(cx, |lsp_store, cx| { + lsp_store.fetch_document_colors_for_buffer(buffer.clone(), cx) + })? .await - { - Ok(colors) => Ok(colors), - Err(e) => { - lsp_store - .update(cx, |lsp_store, _| { - if let Some(lsp_data) = lsp_store.lsp_data.as_mut() { - lsp_data.colors_update.remove(&task_abs_path); - } - }) - .ok(); - Err(Arc::new(e)) + .context("fetching document colors") + .map_err(Arc::new); + let fetched_colors = match fetched_colors { + Ok(fetched_colors) => { + if fetch_strategy != ColorFetchStrategy::IgnoreCache + && Some(true) + == buffer + .update(cx, |buffer, _| { + buffer.version() != query_version_queried_for + }) + .ok() + { + return Ok(DocumentColors::default()); } + fetched_colors } - }) - .shared(); - let lsp_data = self.lsp_data.as_mut()?; - lsp_data - .colors_update - .insert(abs_path.clone(), new_task.clone()); - lsp_data - .last_version_queried - .insert(abs_path, buffer_version); - lsp_data.mtime = buffer_mtime; - Some(new_task) - } else { - Some(Task::ready(Ok(buffer_lsp_data)).shared()) - } + Err(e) => { + lsp_store + .update(cx, |lsp_store, _| { + lsp_store + .lsp_data + .entry(buffer_id) + .or_default() + .colors_update = None; + }) + .ok(); + return Err(e); + } + }; + + lsp_store + .update(cx, |lsp_store, _| { + let lsp_data = lsp_store.lsp_data.entry(buffer_id).or_default(); + + if lsp_data.colors_for_version == query_version_queried_for { + lsp_data.colors.extend(fetched_colors.clone()); + lsp_data.cache_version += 1; + } else if !lsp_data + .colors_for_version + .changed_since(&query_version_queried_for) + { + lsp_data.colors_for_version = query_version_queried_for; + lsp_data.colors = fetched_colors.clone(); + lsp_data.cache_version += 1; + } + lsp_data.colors_update = None; + let colors = lsp_data + .colors + .values() + .flatten() + .cloned() + .collect::>(); + DocumentColors { + colors, + cache_version: Some(lsp_data.cache_version), + } + }) + .map_err(Arc::new) + }) + .shared(); + lsp_data.colors_update = Some((version_queried_for, new_task.clone())); + Some(new_task) } fn fetch_document_colors_for_buffer( &mut self, buffer: Entity, cx: &mut Context, - ) -> Task)>>> { + ) -> Task>>> { if let Some((client, project_id)) = self.upstream_client() { let request_task = client.request(proto::MultiLspQuery { project_id, @@ -6353,7 +6356,7 @@ impl LspStore { }); cx.spawn(async move |project, cx| { let Some(project) = project.upgrade() else { - return Ok(Vec::new()); + return Ok(HashMap::default()); }; let colors = join_all( request_task @@ -6391,9 +6394,7 @@ impl LspStore { .or_insert_with(HashSet::default) .extend(colors); acc - }) - .into_iter() - .collect(); + }); Ok(colors) }) } else { @@ -8942,7 +8943,7 @@ impl LspStore { .color_presentations .into_iter() .map(|presentation| proto::ColorPresentation { - label: presentation.label, + label: presentation.label.to_string(), text_edit: presentation.text_edit.map(serialize_lsp_edit), additional_text_edits: presentation .additional_text_edits @@ -10605,8 +10606,9 @@ impl LspStore { } fn cleanup_lsp_data(&mut self, for_server: LanguageServerId) { - if let Some(lsp_data) = &mut self.lsp_data { - lsp_data.buffer_lsp_data.remove(&for_server); + for buffer_lsp_data in self.lsp_data.values_mut() { + buffer_lsp_data.colors.remove(&for_server); + buffer_lsp_data.cache_version += 1; } if let Some(local) = self.as_local_mut() { local.buffer_pull_diagnostics_result_ids.remove(&for_server); @@ -10679,53 +10681,6 @@ impl LspStore { } } -async fn fetch_document_colors( - lsp_store: WeakEntity, - buffer: Entity, - task_abs_path: PathBuf, - cx: &mut AsyncApp, -) -> anyhow::Result> { - cx.background_executor() - .timer(Duration::from_millis(50)) - .await; - let Some(buffer_mtime) = buffer.update(cx, |buffer, _| buffer.saved_mtime())? else { - return Ok(HashSet::default()); - }; - let fetched_colors = lsp_store - .update(cx, |lsp_store, cx| { - lsp_store.fetch_document_colors_for_buffer(buffer, cx) - })? - .await - .with_context(|| { - format!("Fetching document colors for buffer with path {task_abs_path:?}") - })?; - - lsp_store.update(cx, |lsp_store, _| { - let lsp_data = lsp_store.lsp_data.as_mut().with_context(|| { - format!( - "Document lsp data got updated between fetch and update for path {task_abs_path:?}" - ) - })?; - let mut lsp_colors = HashSet::default(); - anyhow::ensure!( - lsp_data.mtime == buffer_mtime, - "Buffer lsp data got updated between fetch and update for path {task_abs_path:?}" - ); - for (server_id, colors) in fetched_colors { - let colors_lsp_data = &mut lsp_data - .buffer_lsp_data - .entry(server_id) - .or_default() - .entry(task_abs_path.clone()) - .or_default() - .colors; - *colors_lsp_data = Some(colors.clone()); - lsp_colors.extend(colors); - } - Ok(lsp_colors) - })? -} - fn subscribe_to_binary_statuses( languages: &Arc, cx: &mut Context<'_, LspStore>, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ae1185c8be186c44cefa3d4ca255b508224f8b41..cfaff7fa4096d42103ae5794d2bbe72bde2d8412 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -795,7 +795,7 @@ impl std::hash::Hash for DocumentColor { #[derive(Clone, Debug, PartialEq, Eq)] pub struct ColorPresentation { - pub label: String, + pub label: SharedString, pub text_edit: Option, pub additional_text_edits: Vec, } From e5bcd720e1715f1905734f05bdd752b45fb57966 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Sat, 28 Jun 2025 23:41:44 +0200 Subject: [PATCH 045/107] debugger: Add UI for tweaking breakpoint properties directly from breakpoint list (#33097) Release Notes: - debugger: Breakpoint properties (log/hit condition/condition) can now be set directly from breakpoint list. --- Cargo.lock | 1 + assets/icons/arrow_down10.svg | 1 + assets/icons/scroll_text.svg | 1 + assets/icons/split_alt.svg | 1 + assets/keymaps/default-linux.json | 4 +- assets/keymaps/default-macos.json | 4 +- crates/debugger_ui/Cargo.toml | 1 + crates/debugger_ui/src/debugger_panel.rs | 8 +- crates/debugger_ui/src/session/running.rs | 9 +- .../src/session/running/breakpoint_list.rs | 683 ++++++++++++++++-- crates/icons/src/icons.rs | 3 + 11 files changed, 634 insertions(+), 82 deletions(-) create mode 100644 assets/icons/arrow_down10.svg create mode 100644 assets/icons/scroll_text.svg create mode 100644 assets/icons/split_alt.svg diff --git a/Cargo.lock b/Cargo.lock index 19e105e9a31692fc8ec251459a6de2e10e22289b..ef2f698d0a8e633e66502360cc9d2c38ed6c26bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4310,6 +4310,7 @@ version = "0.1.0" dependencies = [ "alacritty_terminal", "anyhow", + "bitflags 2.9.0", "client", "collections", "command_palette_hooks", diff --git a/assets/icons/arrow_down10.svg b/assets/icons/arrow_down10.svg new file mode 100644 index 0000000000000000000000000000000000000000..97ce967a8b03311dfe9df75da6ee4b26e44ba72a --- /dev/null +++ b/assets/icons/arrow_down10.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/scroll_text.svg b/assets/icons/scroll_text.svg new file mode 100644 index 0000000000000000000000000000000000000000..f066c8a84e71ad209e2ebb6b9f7404182ee63552 --- /dev/null +++ b/assets/icons/scroll_text.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/split_alt.svg b/assets/icons/split_alt.svg new file mode 100644 index 0000000000000000000000000000000000000000..3f7622701de82f4e960b3608575d59f83aca44ea --- /dev/null +++ b/assets/icons/split_alt.svg @@ -0,0 +1 @@ + diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 525907a71a9f9eaea7a02f8eafdf9b5e15faaf4b..ca94fd4853030b8f882b5fd911b8dd782149cda4 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -919,7 +919,9 @@ "context": "BreakpointList", "bindings": { "space": "debugger::ToggleEnableBreakpoint", - "backspace": "debugger::UnsetBreakpoint" + "backspace": "debugger::UnsetBreakpoint", + "left": "debugger::PreviousBreakpointProperty", + "right": "debugger::NextBreakpointProperty" } }, { diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 121dbe93e086bf79bf8d4ac9c55e5b29433e2fc7..fa38480c376858d8405bcef89c70fa55f0208884 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -980,7 +980,9 @@ "context": "BreakpointList", "bindings": { "space": "debugger::ToggleEnableBreakpoint", - "backspace": "debugger::UnsetBreakpoint" + "backspace": "debugger::UnsetBreakpoint", + "left": "debugger::PreviousBreakpointProperty", + "right": "debugger::NextBreakpointProperty" } }, { diff --git a/crates/debugger_ui/Cargo.toml b/crates/debugger_ui/Cargo.toml index 91f9acad3c73334980036880143df9c7b410b3b6..ba71e50a0830c7fbab60aa75ba14bb63d58bac07 100644 --- a/crates/debugger_ui/Cargo.toml +++ b/crates/debugger_ui/Cargo.toml @@ -28,6 +28,7 @@ test-support = [ [dependencies] alacritty_terminal.workspace = true anyhow.workspace = true +bitflags.workspace = true client.workspace = true collections.workspace = true command_palette_hooks.workspace = true diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index b7f3be0426e9c189eb0edf203859c7d2489c75d9..8ced5d1eead82984845cda1d473d9195ee4a78a6 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -100,7 +100,13 @@ impl DebugPanel { sessions: vec![], active_session: None, focus_handle, - breakpoint_list: BreakpointList::new(None, workspace.weak_handle(), &project, cx), + breakpoint_list: BreakpointList::new( + None, + workspace.weak_handle(), + &project, + window, + cx, + ), project, workspace: workspace.weak_handle(), context_menu: None, diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 6a3535fe0ebc43eb49066f0e3a81887c10ad51bc..58001ce11d50b3a1dc944ceb5e3854cf2c2852a1 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -697,8 +697,13 @@ impl RunningState { ) }); - let breakpoint_list = - BreakpointList::new(Some(session.clone()), workspace.clone(), &project, cx); + let breakpoint_list = BreakpointList::new( + Some(session.clone()), + workspace.clone(), + &project, + window, + cx, + ); let _subscriptions = vec![ cx.on_app_quit(move |this, cx| { diff --git a/crates/debugger_ui/src/session/running/breakpoint_list.rs b/crates/debugger_ui/src/session/running/breakpoint_list.rs index 8077b289a7d111cbbd1ed189206904d753ae412c..d19eb8c777a9d74e754bcd2b2e1ad0c1d2a49ee4 100644 --- a/crates/debugger_ui/src/session/running/breakpoint_list.rs +++ b/crates/debugger_ui/src/session/running/breakpoint_list.rs @@ -5,11 +5,11 @@ use std::{ time::Duration, }; -use dap::ExceptionBreakpointsFilter; +use dap::{Capabilities, ExceptionBreakpointsFilter}; use editor::Editor; use gpui::{ - Action, AppContext, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy, Stateful, - Task, UniformListScrollHandle, WeakEntity, uniform_list, + Action, AppContext, ClickEvent, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy, + Stateful, Task, UniformListScrollHandle, WeakEntity, actions, uniform_list, }; use language::Point; use project::{ @@ -21,16 +21,20 @@ use project::{ worktree_store::WorktreeStore, }; use ui::{ - AnyElement, App, ButtonCommon, Clickable, Color, Context, Disableable, Div, FluentBuilder as _, - Icon, IconButton, IconName, IconSize, Indicator, InteractiveElement, IntoElement, Label, - LabelCommon, LabelSize, ListItem, ParentElement, Render, Scrollbar, ScrollbarState, - SharedString, StatefulInteractiveElement, Styled, Toggleable, Tooltip, Window, div, h_flex, px, - v_flex, + ActiveTheme, AnyElement, App, ButtonCommon, Clickable, Color, Context, Disableable, Div, + Divider, FluentBuilder as _, Icon, IconButton, IconName, IconSize, Indicator, + InteractiveElement, IntoElement, Label, LabelCommon, LabelSize, ListItem, ParentElement, + Render, RenderOnce, Scrollbar, ScrollbarState, SharedString, StatefulInteractiveElement, + Styled, Toggleable, Tooltip, Window, div, h_flex, px, v_flex, }; use util::ResultExt; use workspace::Workspace; use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint}; +actions!( + debugger, + [PreviousBreakpointProperty, NextBreakpointProperty] +); #[derive(Clone, Copy, PartialEq)] pub(crate) enum SelectedBreakpointKind { Source, @@ -48,6 +52,8 @@ pub(crate) struct BreakpointList { focus_handle: FocusHandle, scroll_handle: UniformListScrollHandle, selected_ix: Option, + input: Entity, + strip_mode: Option, } impl Focusable for BreakpointList { @@ -56,11 +62,19 @@ impl Focusable for BreakpointList { } } +#[derive(Clone, Copy, PartialEq)] +enum ActiveBreakpointStripMode { + Log, + Condition, + HitCondition, +} + impl BreakpointList { pub(crate) fn new( session: Option>, workspace: WeakEntity, project: &Entity, + window: &mut Window, cx: &mut App, ) -> Entity { let project = project.read(cx); @@ -70,7 +84,7 @@ impl BreakpointList { let scroll_handle = UniformListScrollHandle::new(); let scrollbar_state = ScrollbarState::new(scroll_handle.clone()); - cx.new(|_| Self { + cx.new(|cx| Self { breakpoint_store, worktree_store, scrollbar_state, @@ -82,17 +96,28 @@ impl BreakpointList { focus_handle, scroll_handle, selected_ix: None, + input: cx.new(|cx| Editor::single_line(window, cx)), + strip_mode: None, }) } fn edit_line_breakpoint( - &mut self, + &self, path: Arc, row: u32, action: BreakpointEditAction, - cx: &mut Context, + cx: &mut App, + ) { + Self::edit_line_breakpoint_inner(&self.breakpoint_store, path, row, action, cx); + } + fn edit_line_breakpoint_inner( + breakpoint_store: &Entity, + path: Arc, + row: u32, + action: BreakpointEditAction, + cx: &mut App, ) { - self.breakpoint_store.update(cx, |breakpoint_store, cx| { + breakpoint_store.update(cx, |breakpoint_store, cx| { if let Some((buffer, breakpoint)) = breakpoint_store.breakpoint_at_row(&path, row, cx) { breakpoint_store.toggle_breakpoint(buffer, breakpoint, action, cx); } else { @@ -148,16 +173,63 @@ impl BreakpointList { }) } - fn select_ix(&mut self, ix: Option, cx: &mut Context) { + fn set_active_breakpoint_property( + &mut self, + prop: ActiveBreakpointStripMode, + window: &mut Window, + cx: &mut App, + ) { + self.strip_mode = Some(prop); + let placeholder = match prop { + ActiveBreakpointStripMode::Log => "Set Log Message", + ActiveBreakpointStripMode::Condition => "Set Condition", + ActiveBreakpointStripMode::HitCondition => "Set Hit Condition", + }; + let mut is_exception_breakpoint = true; + let active_value = self.selected_ix.and_then(|ix| { + self.breakpoints.get(ix).and_then(|bp| { + if let BreakpointEntryKind::LineBreakpoint(bp) = &bp.kind { + is_exception_breakpoint = false; + match prop { + ActiveBreakpointStripMode::Log => bp.breakpoint.message.clone(), + ActiveBreakpointStripMode::Condition => bp.breakpoint.condition.clone(), + ActiveBreakpointStripMode::HitCondition => { + bp.breakpoint.hit_condition.clone() + } + } + } else { + None + } + }) + }); + + self.input.update(cx, |this, cx| { + this.set_placeholder_text(placeholder, cx); + this.set_read_only(is_exception_breakpoint); + this.set_text(active_value.as_deref().unwrap_or(""), window, cx); + }); + } + + fn select_ix(&mut self, ix: Option, window: &mut Window, cx: &mut Context) { self.selected_ix = ix; if let Some(ix) = ix { self.scroll_handle .scroll_to_item(ix, ScrollStrategy::Center); } + if let Some(mode) = self.strip_mode { + self.set_active_breakpoint_property(mode, window, cx); + } + cx.notify(); } - fn select_next(&mut self, _: &menu::SelectNext, _window: &mut Window, cx: &mut Context) { + fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context) { + if self.strip_mode.is_some() { + if self.input.focus_handle(cx).contains_focused(window, cx) { + cx.propagate(); + return; + } + } let ix = match self.selected_ix { _ if self.breakpoints.len() == 0 => None, None => Some(0), @@ -169,15 +241,21 @@ impl BreakpointList { } } }; - self.select_ix(ix, cx); + self.select_ix(ix, window, cx); } fn select_previous( &mut self, _: &menu::SelectPrevious, - _window: &mut Window, + window: &mut Window, cx: &mut Context, ) { + if self.strip_mode.is_some() { + if self.input.focus_handle(cx).contains_focused(window, cx) { + cx.propagate(); + return; + } + } let ix = match self.selected_ix { _ if self.breakpoints.len() == 0 => None, None => Some(self.breakpoints.len() - 1), @@ -189,37 +267,105 @@ impl BreakpointList { } } }; - self.select_ix(ix, cx); + self.select_ix(ix, window, cx); } - fn select_first( - &mut self, - _: &menu::SelectFirst, - _window: &mut Window, - cx: &mut Context, - ) { + fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context) { + if self.strip_mode.is_some() { + if self.input.focus_handle(cx).contains_focused(window, cx) { + cx.propagate(); + return; + } + } let ix = if self.breakpoints.len() > 0 { Some(0) } else { None }; - self.select_ix(ix, cx); + self.select_ix(ix, window, cx); } - fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context) { + fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context) { + if self.strip_mode.is_some() { + if self.input.focus_handle(cx).contains_focused(window, cx) { + cx.propagate(); + return; + } + } let ix = if self.breakpoints.len() > 0 { Some(self.breakpoints.len() - 1) } else { None }; - self.select_ix(ix, cx); + self.select_ix(ix, window, cx); } + fn dismiss(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context) { + if self.input.focus_handle(cx).contains_focused(window, cx) { + self.focus_handle.focus(window); + } else if self.strip_mode.is_some() { + self.strip_mode.take(); + cx.notify(); + } else { + cx.propagate(); + } + } fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context) { let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else { return; }; + if let Some(mode) = self.strip_mode { + let handle = self.input.focus_handle(cx); + if handle.is_focused(window) { + // Go back to the main strip. Save the result as well. + let text = self.input.read(cx).text(cx); + + match mode { + ActiveBreakpointStripMode::Log => match &entry.kind { + BreakpointEntryKind::LineBreakpoint(line_breakpoint) => { + Self::edit_line_breakpoint_inner( + &self.breakpoint_store, + line_breakpoint.breakpoint.path.clone(), + line_breakpoint.breakpoint.row, + BreakpointEditAction::EditLogMessage(Arc::from(text)), + cx, + ); + } + _ => {} + }, + ActiveBreakpointStripMode::Condition => match &entry.kind { + BreakpointEntryKind::LineBreakpoint(line_breakpoint) => { + Self::edit_line_breakpoint_inner( + &self.breakpoint_store, + line_breakpoint.breakpoint.path.clone(), + line_breakpoint.breakpoint.row, + BreakpointEditAction::EditCondition(Arc::from(text)), + cx, + ); + } + _ => {} + }, + ActiveBreakpointStripMode::HitCondition => match &entry.kind { + BreakpointEntryKind::LineBreakpoint(line_breakpoint) => { + Self::edit_line_breakpoint_inner( + &self.breakpoint_store, + line_breakpoint.breakpoint.path.clone(), + line_breakpoint.breakpoint.row, + BreakpointEditAction::EditHitCondition(Arc::from(text)), + cx, + ); + } + _ => {} + }, + } + self.focus_handle.focus(window); + } else { + handle.focus(window); + } + + return; + } match &mut entry.kind { BreakpointEntryKind::LineBreakpoint(line_breakpoint) => { let path = line_breakpoint.breakpoint.path.clone(); @@ -233,12 +379,18 @@ impl BreakpointList { fn toggle_enable_breakpoint( &mut self, _: &ToggleEnableBreakpoint, - _window: &mut Window, + window: &mut Window, cx: &mut Context, ) { let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else { return; }; + if self.strip_mode.is_some() { + if self.input.focus_handle(cx).contains_focused(window, cx) { + cx.propagate(); + return; + } + } match &mut entry.kind { BreakpointEntryKind::LineBreakpoint(line_breakpoint) => { @@ -279,6 +431,50 @@ impl BreakpointList { cx.notify(); } + fn previous_breakpoint_property( + &mut self, + _: &PreviousBreakpointProperty, + window: &mut Window, + cx: &mut Context, + ) { + let next_mode = match self.strip_mode { + Some(ActiveBreakpointStripMode::Log) => None, + Some(ActiveBreakpointStripMode::Condition) => Some(ActiveBreakpointStripMode::Log), + Some(ActiveBreakpointStripMode::HitCondition) => { + Some(ActiveBreakpointStripMode::Condition) + } + None => Some(ActiveBreakpointStripMode::HitCondition), + }; + if let Some(mode) = next_mode { + self.set_active_breakpoint_property(mode, window, cx); + } else { + self.strip_mode.take(); + } + + cx.notify(); + } + fn next_breakpoint_property( + &mut self, + _: &NextBreakpointProperty, + window: &mut Window, + cx: &mut Context, + ) { + let next_mode = match self.strip_mode { + Some(ActiveBreakpointStripMode::Log) => Some(ActiveBreakpointStripMode::Condition), + Some(ActiveBreakpointStripMode::Condition) => { + Some(ActiveBreakpointStripMode::HitCondition) + } + Some(ActiveBreakpointStripMode::HitCondition) => None, + None => Some(ActiveBreakpointStripMode::Log), + }; + if let Some(mode) = next_mode { + self.set_active_breakpoint_property(mode, window, cx); + } else { + self.strip_mode.take(); + } + cx.notify(); + } + fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context) { const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1); self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |panel, cx| { @@ -294,20 +490,31 @@ impl BreakpointList { })) } - fn render_list(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render_list(&mut self, cx: &mut Context) -> impl IntoElement { let selected_ix = self.selected_ix; let focus_handle = self.focus_handle.clone(); + let supported_breakpoint_properties = self + .session + .as_ref() + .map(|session| SupportedBreakpointProperties::from(session.read(cx).capabilities())) + .unwrap_or_else(SupportedBreakpointProperties::empty); + let strip_mode = self.strip_mode; uniform_list( "breakpoint-list", self.breakpoints.len(), - cx.processor(move |this, range: Range, window, cx| { + cx.processor(move |this, range: Range, _, _| { range .clone() .zip(&mut this.breakpoints[range]) .map(|(ix, breakpoint)| { breakpoint - .render(ix, focus_handle.clone(), window, cx) - .toggle_state(Some(ix) == selected_ix) + .render( + strip_mode, + supported_breakpoint_properties, + ix, + Some(ix) == selected_ix, + focus_handle.clone(), + ) .into_any_element() }) .collect() @@ -443,7 +650,6 @@ impl BreakpointList { impl Render for BreakpointList { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl ui::IntoElement { - // let old_len = self.breakpoints.len(); let breakpoints = self.breakpoint_store.read(cx).all_source_breakpoints(cx); self.breakpoints.clear(); let weak = cx.weak_entity(); @@ -523,15 +729,46 @@ impl Render for BreakpointList { .on_action(cx.listener(Self::select_previous)) .on_action(cx.listener(Self::select_first)) .on_action(cx.listener(Self::select_last)) + .on_action(cx.listener(Self::dismiss)) .on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::toggle_enable_breakpoint)) .on_action(cx.listener(Self::unset_breakpoint)) + .on_action(cx.listener(Self::next_breakpoint_property)) + .on_action(cx.listener(Self::previous_breakpoint_property)) .size_full() .m_0p5() - .child(self.render_list(window, cx)) - .children(self.render_vertical_scrollbar(cx)) + .child( + v_flex() + .size_full() + .child(self.render_list(cx)) + .children(self.render_vertical_scrollbar(cx)), + ) + .when_some(self.strip_mode, |this, _| { + this.child(Divider::horizontal()).child( + h_flex() + // .w_full() + .m_0p5() + .p_0p5() + .border_1() + .rounded_sm() + .when( + self.input.focus_handle(cx).contains_focused(window, cx), + |this| { + let colors = cx.theme().colors(); + let border = if self.input.read(cx).read_only(cx) { + colors.border_disabled + } else { + colors.border_focused + }; + this.border_color(border) + }, + ) + .child(self.input.clone()), + ) + }) } } + #[derive(Clone, Debug)] struct LineBreakpoint { name: SharedString, @@ -543,7 +780,10 @@ struct LineBreakpoint { impl LineBreakpoint { fn render( &mut self, + props: SupportedBreakpointProperties, + strip_mode: Option, ix: usize, + is_selected: bool, focus_handle: FocusHandle, weak: WeakEntity, ) -> ListItem { @@ -594,15 +834,16 @@ impl LineBreakpoint { }) .child(Indicator::icon(Icon::new(icon_name)).color(Color::Debugger)) .on_mouse_down(MouseButton::Left, move |_, _, _| {}); + ListItem::new(SharedString::from(format!( "breakpoint-ui-item-{:?}/{}:{}", self.dir, self.name, self.line ))) .on_click({ let weak = weak.clone(); - move |_, _, cx| { + move |_, window, cx| { weak.update(cx, |breakpoint_list, cx| { - breakpoint_list.select_ix(Some(ix), cx); + breakpoint_list.select_ix(Some(ix), window, cx); }) .ok(); } @@ -613,21 +854,26 @@ impl LineBreakpoint { cx.stop_propagation(); }) .child( - v_flex() - .py_1() + h_flex() + .w_full() + .mr_4() + .py_0p5() .gap_1() .min_h(px(26.)) - .justify_center() + .justify_between() .id(SharedString::from(format!( "breakpoint-ui-on-click-go-to-line-{:?}/{}:{}", self.dir, self.name, self.line ))) - .on_click(move |_, window, cx| { - weak.update(cx, |breakpoint_list, cx| { - breakpoint_list.select_ix(Some(ix), cx); - breakpoint_list.go_to_line_breakpoint(path.clone(), row, window, cx); - }) - .ok(); + .on_click({ + let weak = weak.clone(); + move |_, window, cx| { + weak.update(cx, |breakpoint_list, cx| { + breakpoint_list.select_ix(Some(ix), window, cx); + breakpoint_list.go_to_line_breakpoint(path.clone(), row, window, cx); + }) + .ok(); + } }) .cursor_pointer() .child( @@ -644,8 +890,20 @@ impl LineBreakpoint { .size(LabelSize::Small) .line_height_style(ui::LineHeightStyle::UiLabel) })), - ), + ) + .child(BreakpointOptionsStrip { + props, + breakpoint: BreakpointEntry { + kind: BreakpointEntryKind::LineBreakpoint(self.clone()), + weak: weak, + }, + is_selected, + focus_handle, + strip_mode, + index: ix, + }), ) + .toggle_state(is_selected) } } #[derive(Clone, Debug)] @@ -658,7 +916,10 @@ struct ExceptionBreakpoint { impl ExceptionBreakpoint { fn render( &mut self, + props: SupportedBreakpointProperties, + strip_mode: Option, ix: usize, + is_selected: bool, focus_handle: FocusHandle, list: WeakEntity, ) -> ListItem { @@ -669,15 +930,15 @@ impl ExceptionBreakpoint { }; let id = SharedString::from(&self.id); let is_enabled = self.is_enabled; - + let weak = list.clone(); ListItem::new(SharedString::from(format!( "exception-breakpoint-ui-item-{}", self.id ))) .on_click({ let list = list.clone(); - move |_, _, cx| { - list.update(cx, |list, cx| list.select_ix(Some(ix), cx)) + move |_, window, cx| { + list.update(cx, |list, cx| list.select_ix(Some(ix), window, cx)) .ok(); } }) @@ -691,18 +952,21 @@ impl ExceptionBreakpoint { "exception-breakpoint-ui-item-{}-click-handler", self.id ))) - .tooltip(move |window, cx| { - Tooltip::for_action_in( - if is_enabled { - "Disable Exception Breakpoint" - } else { - "Enable Exception Breakpoint" - }, - &ToggleEnableBreakpoint, - &focus_handle, - window, - cx, - ) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |window, cx| { + Tooltip::for_action_in( + if is_enabled { + "Disable Exception Breakpoint" + } else { + "Enable Exception Breakpoint" + }, + &ToggleEnableBreakpoint, + &focus_handle, + window, + cx, + ) + } }) .on_click({ let list = list.clone(); @@ -722,21 +986,40 @@ impl ExceptionBreakpoint { .child(Indicator::icon(Icon::new(IconName::Flame)).color(color)), ) .child( - v_flex() - .py_1() - .gap_1() - .min_h(px(26.)) - .justify_center() - .id(("exception-breakpoint-label", ix)) + h_flex() + .w_full() + .mr_4() + .py_0p5() + .justify_between() .child( - Label::new(self.data.label.clone()) - .size(LabelSize::Small) - .line_height_style(ui::LineHeightStyle::UiLabel), + v_flex() + .py_1() + .gap_1() + .min_h(px(26.)) + .justify_center() + .id(("exception-breakpoint-label", ix)) + .child( + Label::new(self.data.label.clone()) + .size(LabelSize::Small) + .line_height_style(ui::LineHeightStyle::UiLabel), + ) + .when_some(self.data.description.clone(), |el, description| { + el.tooltip(Tooltip::text(description)) + }), ) - .when_some(self.data.description.clone(), |el, description| { - el.tooltip(Tooltip::text(description)) + .child(BreakpointOptionsStrip { + props, + breakpoint: BreakpointEntry { + kind: BreakpointEntryKind::ExceptionBreakpoint(self.clone()), + weak: weak, + }, + is_selected, + focus_handle, + strip_mode, + index: ix, }), ) + .toggle_state(is_selected) } } #[derive(Clone, Debug)] @@ -754,18 +1037,264 @@ struct BreakpointEntry { impl BreakpointEntry { fn render( &mut self, + strip_mode: Option, + props: SupportedBreakpointProperties, ix: usize, + is_selected: bool, focus_handle: FocusHandle, - _: &mut Window, - _: &mut App, ) -> ListItem { match &mut self.kind { + BreakpointEntryKind::LineBreakpoint(line_breakpoint) => line_breakpoint.render( + props, + strip_mode, + ix, + is_selected, + focus_handle, + self.weak.clone(), + ), + BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => exception_breakpoint + .render( + props.for_exception_breakpoints(), + strip_mode, + ix, + is_selected, + focus_handle, + self.weak.clone(), + ), + } + } + + fn id(&self) -> SharedString { + match &self.kind { + BreakpointEntryKind::LineBreakpoint(line_breakpoint) => format!( + "source-breakpoint-control-strip-{:?}:{}", + line_breakpoint.breakpoint.path, line_breakpoint.breakpoint.row + ) + .into(), + BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => format!( + "exception-breakpoint-control-strip--{}", + exception_breakpoint.id + ) + .into(), + } + } + + fn has_log(&self) -> bool { + match &self.kind { BreakpointEntryKind::LineBreakpoint(line_breakpoint) => { - line_breakpoint.render(ix, focus_handle, self.weak.clone()) + line_breakpoint.breakpoint.message.is_some() } - BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => { - exception_breakpoint.render(ix, focus_handle, self.weak.clone()) + _ => false, + } + } + + fn has_condition(&self) -> bool { + match &self.kind { + BreakpointEntryKind::LineBreakpoint(line_breakpoint) => { + line_breakpoint.breakpoint.condition.is_some() + } + // We don't support conditions on exception breakpoints + BreakpointEntryKind::ExceptionBreakpoint(_) => false, + } + } + + fn has_hit_condition(&self) -> bool { + match &self.kind { + BreakpointEntryKind::LineBreakpoint(line_breakpoint) => { + line_breakpoint.breakpoint.hit_condition.is_some() + } + _ => false, + } + } +} +bitflags::bitflags! { + #[derive(Clone, Copy)] + pub struct SupportedBreakpointProperties: u32 { + const LOG = 1 << 0; + const CONDITION = 1 << 1; + const HIT_CONDITION = 1 << 2; + // Conditions for exceptions can be set only when exception filters are supported. + const EXCEPTION_FILTER_OPTIONS = 1 << 3; + } +} + +impl From<&Capabilities> for SupportedBreakpointProperties { + fn from(caps: &Capabilities) -> Self { + let mut this = Self::empty(); + for (prop, offset) in [ + (caps.supports_log_points, Self::LOG), + (caps.supports_conditional_breakpoints, Self::CONDITION), + ( + caps.supports_hit_conditional_breakpoints, + Self::HIT_CONDITION, + ), + ( + caps.supports_exception_options, + Self::EXCEPTION_FILTER_OPTIONS, + ), + ] { + if prop.unwrap_or_default() { + this.insert(offset); } } + this + } +} + +impl SupportedBreakpointProperties { + fn for_exception_breakpoints(self) -> Self { + // TODO: we don't yet support conditions for exception breakpoints at the data layer, hence all props are disabled here. + Self::empty() + } +} +#[derive(IntoElement)] +struct BreakpointOptionsStrip { + props: SupportedBreakpointProperties, + breakpoint: BreakpointEntry, + is_selected: bool, + focus_handle: FocusHandle, + strip_mode: Option, + index: usize, +} + +impl BreakpointOptionsStrip { + fn is_toggled(&self, expected_mode: ActiveBreakpointStripMode) -> bool { + self.is_selected && self.strip_mode == Some(expected_mode) + } + fn on_click_callback( + &self, + mode: ActiveBreakpointStripMode, + ) -> impl for<'a> Fn(&ClickEvent, &mut Window, &'a mut App) + use<> { + let list = self.breakpoint.weak.clone(); + let ix = self.index; + move |_, window, cx| { + list.update(cx, |this, cx| { + if this.strip_mode != Some(mode) { + this.set_active_breakpoint_property(mode, window, cx); + } else if this.selected_ix == Some(ix) { + this.strip_mode.take(); + } else { + cx.propagate(); + } + }) + .ok(); + } + } + fn add_border( + &self, + kind: ActiveBreakpointStripMode, + available: bool, + window: &Window, + cx: &App, + ) -> impl Fn(Div) -> Div { + move |this: Div| { + // Avoid layout shifts in case there's no colored border + let this = this.border_2().rounded_sm(); + if self.is_selected && self.strip_mode == Some(kind) { + let theme = cx.theme().colors(); + if self.focus_handle.is_focused(window) { + this.border_color(theme.border_selected) + } else { + this.border_color(theme.border_disabled) + } + } else if !available { + this.border_color(cx.theme().colors().border_disabled) + } else { + this + } + } + } +} +impl RenderOnce for BreakpointOptionsStrip { + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + let id = self.breakpoint.id(); + let supports_logs = self.props.contains(SupportedBreakpointProperties::LOG); + let supports_condition = self + .props + .contains(SupportedBreakpointProperties::CONDITION); + let supports_hit_condition = self + .props + .contains(SupportedBreakpointProperties::HIT_CONDITION); + let has_logs = self.breakpoint.has_log(); + let has_condition = self.breakpoint.has_condition(); + let has_hit_condition = self.breakpoint.has_hit_condition(); + let style_for_toggle = |mode, is_enabled| { + if is_enabled && self.strip_mode == Some(mode) && self.is_selected { + ui::ButtonStyle::Filled + } else { + ui::ButtonStyle::Subtle + } + }; + let color_for_toggle = |is_enabled| { + if is_enabled { + ui::Color::Default + } else { + ui::Color::Muted + } + }; + + h_flex() + .gap_2() + .child( + div() .map(self.add_border(ActiveBreakpointStripMode::Log, supports_logs, window, cx)) + .child( + IconButton::new( + SharedString::from(format!("{id}-log-toggle")), + IconName::ScrollText, + ) + .style(style_for_toggle(ActiveBreakpointStripMode::Log, has_logs)) + .icon_color(color_for_toggle(has_logs)) + .disabled(!supports_logs) + .toggle_state(self.is_toggled(ActiveBreakpointStripMode::Log)) + .on_click(self.on_click_callback(ActiveBreakpointStripMode::Log)).tooltip(|window, cx| Tooltip::with_meta("Set Log Message", None, "Set log message to display (instead of stopping) when a breakpoint is hit", window, cx)) + ) + .when(!has_logs && !self.is_selected, |this| this.invisible()), + ) + .child( + div().map(self.add_border( + ActiveBreakpointStripMode::Condition, + supports_condition, + window, cx + )) + .child( + IconButton::new( + SharedString::from(format!("{id}-condition-toggle")), + IconName::SplitAlt, + ) + .style(style_for_toggle( + ActiveBreakpointStripMode::Condition, + has_condition + )) + .icon_color(color_for_toggle(has_condition)) + .disabled(!supports_condition) + .toggle_state(self.is_toggled(ActiveBreakpointStripMode::Condition)) + .on_click(self.on_click_callback(ActiveBreakpointStripMode::Condition)) + .tooltip(|window, cx| Tooltip::with_meta("Set Condition", None, "Set condition to evaluate when a breakpoint is hit. Program execution will stop only when the condition is met", window, cx)) + ) + .when(!has_condition && !self.is_selected, |this| this.invisible()), + ) + .child( + div() .map(self.add_border( + ActiveBreakpointStripMode::HitCondition, + supports_hit_condition,window, cx + )) + .child( + IconButton::new( + SharedString::from(format!("{id}-hit-condition-toggle")), + IconName::ArrowDown10, + ) + .style(style_for_toggle( + ActiveBreakpointStripMode::HitCondition, + has_hit_condition, + )) + .icon_color(color_for_toggle(has_hit_condition)) + .disabled(!supports_hit_condition) + .toggle_state(self.is_toggled(ActiveBreakpointStripMode::HitCondition)) + .on_click(self.on_click_callback(ActiveBreakpointStripMode::HitCondition)).tooltip(|window, cx| Tooltip::with_meta("Set Hit Condition", None, "Set expression that controls how many hits of the breakpoint are ignored.", window, cx)) + ) + .when(!has_hit_condition && !self.is_selected, |this| { + this.invisible() + }), + ) } } diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs index ffbe148a3bb3725cfbafff9ddab53f2b39a609d1..332e38b038a51f533f22cccc3c4ffec3d83a4898 100644 --- a/crates/icons/src/icons.rs +++ b/crates/icons/src/icons.rs @@ -23,6 +23,7 @@ pub enum IconName { AiZed, ArrowCircle, ArrowDown, + ArrowDown10, ArrowDownFromLine, ArrowDownRight, ArrowLeft, @@ -212,6 +213,7 @@ pub enum IconName { Save, Scissors, Screen, + ScrollText, SearchCode, SearchSelection, SelectAll, @@ -231,6 +233,7 @@ pub enum IconName { SparkleFilled, Spinner, Split, + SplitAlt, SquareDot, SquareMinus, SquarePlus, From 047d515abf6f8eb9594f48b3e8e97930c859901c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sun, 29 Jun 2025 12:43:56 +0300 Subject: [PATCH 046/107] Rework color indicators visual representation (#33605) Use a div-based rendering code instead of using a text Closes https://github.com/zed-industries/zed/discussions/33507 Before: before_dark After: after_dark Before: before_light After: after_light The border is not that contrast as in VSCode examples in the issue, but I'm supposed to use the right thing in https://github.com/zed-industries/zed/blob/1e11de48eeba01dc12761ec1274f8b0963de5514/crates/editor/src/display_map/inlay_map.rs#L357 based on https://github.com/zed-industries/zed/blob/41583fb066629d1e54d600e930be068a68984c5c/crates/theme/src/styles/colors.rs#L16-L17 Another oddity is that the border starts to shrink on `cmd-=` (`zed::IncreaseBufferFontSize`): image but that needs a different part of code to be adjusted hence skipped. Tailwind CSS example: image Release Notes: - Reworked color indicators visual representation --- crates/editor/src/display_map/fold_map.rs | 11 ++-- crates/editor/src/display_map/inlay_map.rs | 73 ++++++++++++++++------ crates/editor/src/editor.rs | 3 + 3 files changed, 64 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 92456836a9766b1ab6fb5e3d4dfc406dc0bc393b..197b85497b3abbc61885ebed1a80f1a707f0eb20 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,3 +1,5 @@ +use crate::display_map::inlay_map::InlayChunk; + use super::{ Highlights, inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, @@ -1060,7 +1062,7 @@ impl sum_tree::Summary for TransformSummary { } #[derive(Copy, Clone, Eq, PartialEq, Debug, Default, Ord, PartialOrd, Hash)] -pub struct FoldId(usize); +pub struct FoldId(pub(super) usize); impl From for ElementId { fn from(val: FoldId) -> Self { @@ -1311,7 +1313,7 @@ impl DerefMut for ChunkRendererContext<'_, '_> { pub struct FoldChunks<'a> { transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>, inlay_chunks: InlayChunks<'a>, - inlay_chunk: Option<(InlayOffset, language::Chunk<'a>)>, + inlay_chunk: Option<(InlayOffset, InlayChunk<'a>)>, inlay_offset: InlayOffset, output_offset: FoldOffset, max_output_offset: FoldOffset, @@ -1403,7 +1405,8 @@ impl<'a> Iterator for FoldChunks<'a> { } // Otherwise, take a chunk from the buffer's text. - if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk.clone() { + if let Some((buffer_chunk_start, mut inlay_chunk)) = self.inlay_chunk.clone() { + let chunk = &mut inlay_chunk.chunk; let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len()); let transform_end = self.transform_cursor.end(&()).1; let chunk_end = buffer_chunk_end.min(transform_end); @@ -1428,7 +1431,7 @@ impl<'a> Iterator for FoldChunks<'a> { is_tab: chunk.is_tab, is_inlay: chunk.is_inlay, underline: chunk.underline, - renderer: None, + renderer: inlay_chunk.renderer, }); } diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index e7d8868d42ced70485de4e718f0b57d82aa257c1..4f7c4962f2d14e9e1436f5abb3a567972e43d69f 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1,4 +1,4 @@ -use crate::{HighlightStyles, InlayId}; +use crate::{ChunkRenderer, HighlightStyles, InlayId, display_map::FoldId}; use collections::BTreeSet; use gpui::{Hsla, Rgba}; use language::{Chunk, Edit, Point, TextSummary}; @@ -8,9 +8,11 @@ use multi_buffer::{ use std::{ cmp, ops::{Add, AddAssign, Range, Sub, SubAssign}, + sync::Arc, }; use sum_tree::{Bias, Cursor, SumTree}; use text::{Patch, Rope}; +use ui::{ActiveTheme, IntoElement as _, ParentElement as _, Styled as _, div}; use super::{Highlights, custom_highlights::CustomHighlightsChunks}; @@ -252,6 +254,13 @@ pub struct InlayChunks<'a> { snapshot: &'a InlaySnapshot, } +#[derive(Clone)] +pub struct InlayChunk<'a> { + pub chunk: Chunk<'a>, + /// Whether the inlay should be customly rendered. + pub renderer: Option, +} + impl InlayChunks<'_> { pub fn seek(&mut self, new_range: Range) { self.transforms.seek(&new_range.start, Bias::Right, &()); @@ -271,7 +280,7 @@ impl InlayChunks<'_> { } impl<'a> Iterator for InlayChunks<'a> { - type Item = Chunk<'a>; + type Item = InlayChunk<'a>; fn next(&mut self) -> Option { if self.output_offset == self.max_output_offset { @@ -296,9 +305,12 @@ impl<'a> Iterator for InlayChunks<'a> { chunk.text = suffix; self.output_offset.0 += prefix.len(); - Chunk { - text: prefix, - ..chunk.clone() + InlayChunk { + chunk: Chunk { + text: prefix, + ..chunk.clone() + }, + renderer: None, } } Transform::Inlay(inlay) => { @@ -313,6 +325,7 @@ impl<'a> Iterator for InlayChunks<'a> { } } + let mut renderer = None; let mut highlight_style = match inlay.id { InlayId::InlineCompletion(_) => { self.highlight_styles.inline_completion.map(|s| { @@ -325,14 +338,33 @@ impl<'a> Iterator for InlayChunks<'a> { } InlayId::Hint(_) => self.highlight_styles.inlay_hint, InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint, - InlayId::Color(_) => match inlay.color { - Some(color) => { - let mut style = self.highlight_styles.inlay_hint.unwrap_or_default(); - style.color = Some(color); - Some(style) + InlayId::Color(id) => { + if let Some(color) = inlay.color { + renderer = Some(ChunkRenderer { + id: FoldId(id), + render: Arc::new(move |cx| { + div() + .w_4() + .h_4() + .relative() + .child( + div() + .absolute() + .right_1() + .w_3p5() + .h_3p5() + .border_2() + .border_color(cx.theme().colors().border) + .bg(color), + ) + .into_any_element() + }), + constrain_width: false, + measured_width: None, + }); } - None => self.highlight_styles.inlay_hint, - }, + self.highlight_styles.inlay_hint + } }; let next_inlay_highlight_endpoint; let offset_in_inlay = self.output_offset - self.transforms.start().0; @@ -370,11 +402,14 @@ impl<'a> Iterator for InlayChunks<'a> { self.output_offset.0 += chunk.len(); - Chunk { - text: chunk, - highlight_style, - is_inlay: true, - ..Default::default() + InlayChunk { + chunk: Chunk { + text: chunk, + highlight_style, + is_inlay: true, + ..Chunk::default() + }, + renderer, } } }; @@ -1066,7 +1101,7 @@ impl InlaySnapshot { #[cfg(test)] pub fn text(&self) -> String { self.chunks(Default::default()..self.len(), false, Highlights::default()) - .map(|chunk| chunk.text) + .map(|chunk| chunk.chunk.text) .collect() } @@ -1704,7 +1739,7 @@ mod tests { ..Highlights::default() }, ) - .map(|chunk| chunk.text) + .map(|chunk| chunk.chunk.text) .collect::(); assert_eq!( actual_text, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fedd9222ec00fedeeae58b0526504d3762100c08..419e3c4ae9693e3984bf09777dce006cd5ce03a9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -547,6 +547,7 @@ pub enum SoftWrap { #[derive(Clone)] pub struct EditorStyle { pub background: Hsla, + pub border: Hsla, pub local_player: PlayerColor, pub text: TextStyle, pub scrollbar_width: Pixels, @@ -562,6 +563,7 @@ impl Default for EditorStyle { fn default() -> Self { Self { background: Hsla::default(), + border: Hsla::default(), local_player: PlayerColor::default(), text: TextStyle::default(), scrollbar_width: Pixels::default(), @@ -22405,6 +22407,7 @@ impl Render for Editor { &cx.entity(), EditorStyle { background, + border: cx.theme().colors().border, local_player: cx.theme().players().local(), text: text_style, scrollbar_width: EditorElement::SCROLLBAR_WIDTH, From a602b4b305a374ead16afca51278f35e9ac152af Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Sun, 29 Jun 2025 12:21:10 -0400 Subject: [PATCH 047/107] Improve R documentation (#33594) Release Notes: - N/A --- docs/src/languages/r.md | 75 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/docs/src/languages/r.md b/docs/src/languages/r.md index ce123d4606a8199bd7660a45b44648bfb3d0da50..6b40f6970dda16b9405bfd15842452fbe28fe947 100644 --- a/docs/src/languages/r.md +++ b/docs/src/languages/r.md @@ -1,9 +1,14 @@ # R -R support is available through the [R extension](https://github.com/ocsmit/zed-r). +R support is available via multiple R Zed extensions: -- Tree-sitter: [r-lib/tree-sitter-r](https://github.com/r-lib/tree-sitter-r) -- Language-Server: [REditorSupport/languageserver](https://github.com/REditorSupport/languageserver) +- [ocsmit/zed-r](https://github.com/ocsmit/zed-r) + + - Tree-sitter: [r-lib/tree-sitter-r](https://github.com/r-lib/tree-sitter-r) + - Language-Server: [REditorSupport/languageserver](https://github.com/REditorSupport/languageserver) + +- [posit-dev/air](https://github.com/posit-dev/air/tree/main/editors/zed) + - Language-Server: [posit-dev/air](https://github.com/posit-dev/air) ## Installation @@ -15,7 +20,7 @@ install.packages("languageserver") install.packages("lintr") ``` -3. Install the [R Zed extension](https://github.com/ocsmit/zed-r) through Zed's extensions manager. +3. Install the [ocsmit/zed-r](https://github.com/ocsmit/zed-r) through Zed's extensions manager. For example on macOS: @@ -28,7 +33,65 @@ Rscript -e 'packageVersion("languageserver")' Rscript -e 'packageVersion("lintr")' ``` -## Ark Installation +## Configuration + +### Linting + +`REditorSupport/languageserver` bundles support for [r-lib/lintr](https://github.com/r-lib/lintr) as a linter. This can be configured via the use of a `.lintr` inside your project (or in your home directory for global defaults). + +```r +linters: linters_with_defaults( + line_length_linter(120), + commented_code_linter = NULL + ) +exclusions: list( + "inst/doc/creating_linters.R" = 1, + "inst/example/bad.R", + "tests/testthat/exclusions-test" + ) +``` + +Or exclude it from linting anything, + +```r +exclusions: list(".") +``` + +See [Using lintr](https://lintr.r-lib.org/articles/lintr.html) for a complete list of options, + +### Formatting + +`REditorSupport/languageserver` bundles support for [r-lib/styler](https://github.com/r-lib/styler) as a formatter. See [Customizing Styler](https://cran.r-project.org/web/packages/styler/vignettes/customizing_styler.html) for more information on how to customize its behavior. + +### REditorSupport/languageserver Configuration + +You can configure the [R languageserver settings](https://github.com/REditorSupport/languageserver#settings) via Zed Project Settings `.zed/settings.json` or Zed User Settings `~/.config/zed/settings.json`: + +For example to disable Lintr linting and suppress code snippet suggestions (both enabled by default): + +```json +{ + "lsp": { + "r_language_server": { + "settings": { + "r": { + "lsp": { + "diagnostics": false, + "snippet_support": false + } + } + } + } + } +} +``` + + From 86161aa427b9e8b18486272ca436c344224e8ba4 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Sun, 29 Jun 2025 12:33:05 -0600 Subject: [PATCH 048/107] Use refs to deduplicate settings JSON schema (~1.7mb to ~0.26mb) (#33618) Release Notes: - N/A --- crates/settings/src/settings_store.rs | 40 +++++++++++++++++++++------ 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 38e2e4968a115f684690427957cee75e62182b67..c4cf97bd6cea34f72c1e6d5c79e89c9f34504717 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -8,7 +8,7 @@ use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal}; use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name}; use schemars::{JsonSchema, r#gen::SchemaGenerator, schema::RootSchema}; use serde::{Deserialize, Serialize, de::DeserializeOwned}; -use serde_json::Value; +use serde_json::{Value, json}; use smallvec::SmallVec; use std::{ any::{Any, TypeId, type_name}, @@ -967,16 +967,38 @@ impl SettingsStore { } } - for release_stage in ["dev", "nightly", "stable", "preview"] { - let schema = combined_schema.schema.clone(); - combined_schema - .schema - .object() - .properties - .insert(release_stage.to_string(), schema.into()); + const ZED_SETTINGS: &str = "ZedSettings"; + let RootSchema { + meta_schema, + schema: zed_settings_schema, + mut definitions, + } = combined_schema; + definitions.insert(ZED_SETTINGS.to_string(), zed_settings_schema.into()); + let zed_settings_ref = Schema::new_ref(format!("#/definitions/{ZED_SETTINGS}")); + + // settings file contents matches ZedSettings + overrides for each release stage + let mut root_schema = json!({ + "allOf": [ + zed_settings_ref, + { + "properties": { + "dev": zed_settings_ref, + "nightly": zed_settings_ref, + "stable": zed_settings_ref, + "preview": zed_settings_ref, + } + } + ], + "definitions": definitions, + }); + + if let Some(meta_schema) = meta_schema { + if let Some(root_schema_object) = root_schema.as_object_mut() { + root_schema_object.insert("$schema".to_string(), meta_schema.into()); + } } - serde_json::to_value(&combined_schema).unwrap() + root_schema } fn recompute_values( From d2cf995e279906830ac7af1738c53c8981e493dc Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 30 Jun 2025 00:48:14 +0200 Subject: [PATCH 049/107] debugger: Tweak layout of debug landing page in vertical dock position (#33625) Release Notes: - Reorganized layout of a debug panel without any sessions for a vertical dock position. - Moved parent directories of source breakpoints into a tooltip. --- crates/debugger_ui/src/debugger_panel.rs | 164 +++++++++++------- .../src/session/running/breakpoint_list.rs | 19 +- 2 files changed, 111 insertions(+), 72 deletions(-) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 8ced5d1eead82984845cda1d473d9195ee4a78a6..7490b50530ba026b4802d57140f66f248aadcb74 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -1468,6 +1468,94 @@ impl Render for DebugPanel { if has_sessions { this.children(self.active_session.clone()) } else { + let docked_to_bottom = self.position(window, cx) == DockPosition::Bottom; + let welcome_experience = v_flex() + .when_else( + docked_to_bottom, + |this| this.w_2_3().h_full().pr_8(), + |this| this.w_full().h_1_3(), + ) + .items_center() + .justify_center() + .gap_2() + .child( + Button::new("spawn-new-session-empty-state", "New Session") + .icon(IconName::Plus) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .icon_position(IconPosition::Start) + .on_click(|_, window, cx| { + window.dispatch_action(crate::Start.boxed_clone(), cx); + }), + ) + .child( + Button::new("edit-debug-settings", "Edit debug.json") + .icon(IconName::Code) + .icon_size(IconSize::XSmall) + .color(Color::Muted) + .icon_color(Color::Muted) + .icon_position(IconPosition::Start) + .on_click(|_, window, cx| { + window.dispatch_action( + zed_actions::OpenProjectDebugTasks.boxed_clone(), + cx, + ); + }), + ) + .child( + Button::new("open-debugger-docs", "Debugger Docs") + .icon(IconName::Book) + .color(Color::Muted) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .icon_position(IconPosition::Start) + .on_click(|_, _, cx| cx.open_url("https://zed.dev/docs/debugger")), + ) + .child( + Button::new( + "spawn-new-session-install-extensions", + "Debugger Extensions", + ) + .icon(IconName::Blocks) + .color(Color::Muted) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .icon_position(IconPosition::Start) + .on_click(|_, window, cx| { + window.dispatch_action( + zed_actions::Extensions { + category_filter: Some( + zed_actions::ExtensionCategoryFilter::DebugAdapters, + ), + } + .boxed_clone(), + cx, + ); + }), + ); + let breakpoint_list = + v_flex() + .group("base-breakpoint-list") + .items_start() + .when_else( + docked_to_bottom, + |this| this.min_w_1_3().h_full(), + |this| this.w_full().h_2_3(), + ) + .p_1() + .child( + h_flex() + .pl_1() + .w_full() + .justify_between() + .child(Label::new("Breakpoints").size(LabelSize::Small)) + .child(h_flex().visible_on_hover("base-breakpoint-list").child( + self.breakpoint_list.read(cx).render_control_strip(), + )) + .track_focus(&self.breakpoint_list.focus_handle(cx)), + ) + .child(Divider::horizontal()) + .child(self.breakpoint_list.clone()); this.child( v_flex() .h_full() @@ -1475,65 +1563,23 @@ impl Render for DebugPanel { .items_center() .justify_center() .child( - h_flex().size_full() - .items_start() - - .child(v_flex().group("base-breakpoint-list").items_start().min_w_1_3().h_full().p_1() - .child(h_flex().pl_1().w_full().justify_between() - .child(Label::new("Breakpoints").size(LabelSize::Small)) - .child(h_flex().visible_on_hover("base-breakpoint-list").child(self.breakpoint_list.read(cx).render_control_strip()))) - .child(Divider::horizontal()) - .child(self.breakpoint_list.clone())) - .child(Divider::vertical()) - .child( - v_flex().w_2_3().h_full().items_center().justify_center() - .gap_2() - .pr_8() - .child( - Button::new("spawn-new-session-empty-state", "New Session") - .icon(IconName::Plus) - .icon_size(IconSize::XSmall) - .icon_color(Color::Muted) - .icon_position(IconPosition::Start) - .on_click(|_, window, cx| { - window.dispatch_action(crate::Start.boxed_clone(), cx); - }) - ) - .child( - Button::new("edit-debug-settings", "Edit debug.json") - .icon(IconName::Code) - .icon_size(IconSize::XSmall) - .color(Color::Muted) - .icon_color(Color::Muted) - .icon_position(IconPosition::Start) - .on_click(|_, window, cx| { - window.dispatch_action(zed_actions::OpenProjectDebugTasks.boxed_clone(), cx); - }) - ) - .child( - Button::new("open-debugger-docs", "Debugger Docs") - .icon(IconName::Book) - .color(Color::Muted) - .icon_size(IconSize::XSmall) - .icon_color(Color::Muted) - .icon_position(IconPosition::Start) - .on_click(|_, _, cx| { - cx.open_url("https://zed.dev/docs/debugger") - }) - ) - .child( - Button::new("spawn-new-session-install-extensions", "Debugger Extensions") - .icon(IconName::Blocks) - .color(Color::Muted) - .icon_size(IconSize::XSmall) - .icon_color(Color::Muted) - .icon_position(IconPosition::Start) - .on_click(|_, window, cx| { - window.dispatch_action(zed_actions::Extensions { category_filter: Some(zed_actions::ExtensionCategoryFilter::DebugAdapters)}.boxed_clone(), cx); - }) - ) - ) - ) + div() + .when_else(docked_to_bottom, Div::h_flex, Div::v_flex) + .size_full() + .map(|this| { + if docked_to_bottom { + this.items_start() + .child(breakpoint_list) + .child(Divider::vertical()) + .child(welcome_experience) + } else { + this.items_end() + .child(welcome_experience) + .child(Divider::horizontal()) + .child(breakpoint_list) + } + }), + ), ) } }) diff --git a/crates/debugger_ui/src/session/running/breakpoint_list.rs b/crates/debugger_ui/src/session/running/breakpoint_list.rs index d19eb8c777a9d74e754bcd2b2e1ad0c1d2a49ee4..407552c3025262f60d40988076ad8176ccfe7c91 100644 --- a/crates/debugger_ui/src/session/running/breakpoint_list.rs +++ b/crates/debugger_ui/src/session/running/breakpoint_list.rs @@ -877,20 +877,13 @@ impl LineBreakpoint { }) .cursor_pointer() .child( - h_flex() - .gap_1() - .child( - Label::new(format!("{}:{}", self.name, self.line)) - .size(LabelSize::Small) - .line_height_style(ui::LineHeightStyle::UiLabel), - ) - .children(self.dir.clone().map(|dir| { - Label::new(dir) - .color(Color::Muted) - .size(LabelSize::Small) - .line_height_style(ui::LineHeightStyle::UiLabel) - })), + Label::new(format!("{}:{}", self.name, self.line)) + .size(LabelSize::Small) + .line_height_style(ui::LineHeightStyle::UiLabel), ) + .when_some(self.dir.as_ref(), |this, parent_dir| { + this.tooltip(Tooltip::text(format!("Worktree parent path: {parent_dir}"))) + }) .child(BreakpointOptionsStrip { props, breakpoint: BreakpointEntry { From bc5927d5afc3487e4c6c83cb004709c81578ff33 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 30 Jun 2025 01:38:16 +0200 Subject: [PATCH 050/107] debugger: Fix spec violation with threads request being issued before debug session is initialized (#33627) Follow-up to #32852. This time we'll check if the debug session is initialized before querying threads. Release Notes: - Fix Zed's debugger issuing threads request before it is allowed to do so per DAP specification. --- crates/debugger_ui/src/debugger_panel.rs | 2 +- crates/debugger_ui/src/session/running/console.rs | 2 +- crates/project/src/debugger/session.rs | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 7490b50530ba026b4802d57140f66f248aadcb74..b2cd62134f2f8bcab82ed609d06117b7a0a0825a 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -868,7 +868,7 @@ impl DebugPanel { let threads = running_state.update(cx, |running_state, cx| { let session = running_state.session(); - session.read(cx).is_running().then(|| { + session.read(cx).is_started().then(|| { session.update(cx, |session, cx| { session.threads(cx) }) diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index 83d2d46547ada9da328cc44443813a87a6f681f1..aaac63640188b2b277d1ff8bfb9b75b114f5554b 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -114,7 +114,7 @@ impl Console { } fn is_running(&self, cx: &Context) -> bool { - self.session.read(cx).is_running() + self.session.read(cx).is_started() } fn handle_stack_frame_list_events( diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index 300c598bfb9e1daa198baecda0ce5ef5c08aa3e7..255a580e36aaa919a954307f46bba8dc13f44094 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -1037,10 +1037,6 @@ impl Session { matches!(self.mode, Mode::Building) } - pub fn is_running(&self) -> bool { - matches!(self.mode, Mode::Running(_)) - } - pub fn as_running_mut(&mut self) -> Option<&mut RunningMode> { match &mut self.mode { Mode::Running(local_mode) => Some(local_mode), From c3d0230f89ff21257d965d4c410a274e52c4fe35 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Sun, 29 Jun 2025 20:49:28 -0300 Subject: [PATCH 051/107] docs: Adjust heading sizes (#33628) Just fine-tuning some heading sizes that were off, particularly h4s and h5s. Release Notes: - N/A --- docs/theme/css/general.css | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/theme/css/general.css b/docs/theme/css/general.css index 61b373651a07e8d96499e5aeee90494ade583518..138a3a325b8a975ca36fb63dee09dc7893137a14 100644 --- a/docs/theme/css/general.css +++ b/docs/theme/css/general.css @@ -79,20 +79,34 @@ h6 code { display: none !important; } +h1 { + font-size: 3.4rem; +} + h2 { padding-bottom: 1rem; border-bottom: 1px solid; border-color: var(--border-light); } -h2, h3 { - margin-block-start: 1.5em; - margin-block-end: 0; + font-size: 2rem; } + +h4 { + font-size: 1.8rem; +} + +h5 { + font-size: 1.6rem; +} + +h2, +h3, h4, h5 { - margin-block-start: 2em; + margin-block-start: 1.5em; + margin-block-end: 0; } .header + .header h3, From d63909c598d7960da8c54903c7b4db0ea52f7d11 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Mon, 30 Jun 2025 10:05:52 +0200 Subject: [PATCH 052/107] agent: Use standardized MCP configuration format in settings (#33539) Changes our MCP settings from: ```json { "context_servers": { "some-mcp-server": { "source": "custom", "command": { "path": "npx", "args": [ "-y", "@supabase/mcp-server-supabase@latest", "--read-only", "--project-ref=", ], "env": { "SUPABASE_ACCESS_TOKEN": "", }, }, }, }, } ``` to: ```json { "context_servers": { "some-mcp-server": { "source": "custom", "command": "npx", "args": [ "-y", "@supabase/mcp-server-supabase@latest", "--read-only", "--project-ref=", ], "env": { "SUPABASE_ACCESS_TOKEN": "", }, }, }, } ``` Which seems to be somewhat of a standard now (VSCode, Cursor, Windsurf, ...) Release Notes: - agent: Use standardised format for configuring MCP Servers --- .../configure_context_server_modal.rs | 19 ++- crates/context_server/src/context_server.rs | 1 + crates/migrator/src/migrations.rs | 6 + .../src/migrations/m_2025_06_27/settings.rs | 133 ++++++++++++++++++ crates/migrator/src/migrator.rs | 128 ++++++++++++++++- crates/project/src/project_settings.rs | 5 +- 6 files changed, 276 insertions(+), 16 deletions(-) create mode 100644 crates/migrator/src/migrations/m_2025_06_27/settings.rs diff --git a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs index af08eaf935ba9887a36ef6093ca3dcd53f1608f0..299f3cee34b1c7635c3c0a8f46a52cc730993b01 100644 --- a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs +++ b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs @@ -180,7 +180,7 @@ impl ConfigurationSource { } fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)>) -> String { - let (name, path, args, env) = match existing { + let (name, command, args, env) = match existing { Some((id, cmd)) => { let args = serde_json::to_string(&cmd.args).unwrap(); let env = serde_json::to_string(&cmd.env.unwrap_or_default()).unwrap(); @@ -198,14 +198,12 @@ fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand) r#"{{ /// The name of your MCP server "{name}": {{ - "command": {{ - /// The path to the executable - "path": "{path}", - /// The arguments to pass to the executable - "args": {args}, - /// The environment variables to set for the executable - "env": {env} - }} + /// The command which runs the MCP server + "command": "{command}", + /// The arguments to pass to the MCP server + "args": {args}, + /// The environment variables to set + "env": {env} }} }}"# ) @@ -439,8 +437,7 @@ fn parse_input(text: &str) -> Result<(ContextServerId, ContextServerCommand)> { let object = value.as_object().context("Expected object")?; anyhow::ensure!(object.len() == 1, "Expected exactly one key-value pair"); let (context_server_name, value) = object.into_iter().next().unwrap(); - let command = value.get("command").context("Expected command")?; - let command: ContextServerCommand = serde_json::from_value(command.clone())?; + let command: ContextServerCommand = serde_json::from_value(value.clone())?; Ok((ContextServerId(context_server_name.clone().into()), command)) } diff --git a/crates/context_server/src/context_server.rs b/crates/context_server/src/context_server.rs index f774deef170086f4b17e8f83d8fd5ec0557528e0..905435fcce57dc8ce8719e5056b28118168e9a04 100644 --- a/crates/context_server/src/context_server.rs +++ b/crates/context_server/src/context_server.rs @@ -29,6 +29,7 @@ impl Display for ContextServerId { #[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)] pub struct ContextServerCommand { + #[serde(rename = "command")] pub path: String, pub args: Vec, pub env: Option>, diff --git a/crates/migrator/src/migrations.rs b/crates/migrator/src/migrations.rs index d43521faa958782f902151b99233f511732786fb..4e3839358b6e55b0cbec72f92ca12d9b2b30c168 100644 --- a/crates/migrator/src/migrations.rs +++ b/crates/migrator/src/migrations.rs @@ -87,3 +87,9 @@ pub(crate) mod m_2025_06_25 { pub(crate) use settings::SETTINGS_PATTERNS; } + +pub(crate) mod m_2025_06_27 { + mod settings; + + pub(crate) use settings::SETTINGS_PATTERNS; +} diff --git a/crates/migrator/src/migrations/m_2025_06_27/settings.rs b/crates/migrator/src/migrations/m_2025_06_27/settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..6156308fcec05dfb10b5b258d31077e5d4b09adc --- /dev/null +++ b/crates/migrator/src/migrations/m_2025_06_27/settings.rs @@ -0,0 +1,133 @@ +use std::ops::Range; +use tree_sitter::{Query, QueryMatch}; + +use crate::MigrationPatterns; + +pub const SETTINGS_PATTERNS: MigrationPatterns = &[( + SETTINGS_CONTEXT_SERVER_PATTERN, + flatten_context_server_command, +)]; + +const SETTINGS_CONTEXT_SERVER_PATTERN: &str = r#"(document + (object + (pair + key: (string (string_content) @context-servers) + value: (object + (pair + key: (string (string_content) @server-name) + value: (object + (pair + key: (string (string_content) @source-key) + value: (string (string_content) @source-value) + ) + (pair + key: (string (string_content) @command-key) + value: (object) @command-object + ) @command-pair + ) @server-settings + ) + ) + ) + ) + (#eq? @context-servers "context_servers") + (#eq? @source-key "source") + (#eq? @source-value "custom") + (#eq? @command-key "command") +)"#; + +fn flatten_context_server_command( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let command_pair_index = query.capture_index_for_name("command-pair")?; + let command_pair = mat.nodes_for_capture_index(command_pair_index).next()?; + + let command_object_index = query.capture_index_for_name("command-object")?; + let command_object = mat.nodes_for_capture_index(command_object_index).next()?; + + let server_settings_index = query.capture_index_for_name("server-settings")?; + let _server_settings = mat.nodes_for_capture_index(server_settings_index).next()?; + + // Parse the command object to extract path, args, and env + let mut path_value = None; + let mut args_value = None; + let mut env_value = None; + + let mut cursor = command_object.walk(); + for child in command_object.children(&mut cursor) { + if child.kind() == "pair" { + if let Some(key_node) = child.child_by_field_name("key") { + if let Some(string_content) = key_node.child(1) { + let key = &contents[string_content.byte_range()]; + if let Some(value_node) = child.child_by_field_name("value") { + let value_range = value_node.byte_range(); + match key { + "path" => path_value = Some(&contents[value_range]), + "args" => args_value = Some(&contents[value_range]), + "env" => env_value = Some(&contents[value_range]), + _ => {} + } + } + } + } + } + } + + let path = path_value?; + + // Get the proper indentation from the command pair + let command_pair_start = command_pair.start_byte(); + let line_start = contents[..command_pair_start] + .rfind('\n') + .map(|pos| pos + 1) + .unwrap_or(0); + let indent = &contents[line_start..command_pair_start]; + + // Build the replacement string + let mut replacement = format!("\"command\": {}", path); + + // Add args if present - need to reduce indentation + if let Some(args) = args_value { + replacement.push_str(",\n"); + replacement.push_str(indent); + replacement.push_str("\"args\": "); + let reduced_args = reduce_indentation(args, 4); + replacement.push_str(&reduced_args); + } + + // Add env if present - need to reduce indentation + if let Some(env) = env_value { + replacement.push_str(",\n"); + replacement.push_str(indent); + replacement.push_str("\"env\": "); + replacement.push_str(&reduce_indentation(env, 4)); + } + + let range_to_replace = command_pair.byte_range(); + Some((range_to_replace, replacement)) +} + +fn reduce_indentation(text: &str, spaces: usize) -> String { + let lines: Vec<&str> = text.lines().collect(); + let mut result = String::new(); + + for (i, line) in lines.iter().enumerate() { + if i > 0 { + result.push('\n'); + } + + // Count leading spaces + let leading_spaces = line.chars().take_while(|&c| c == ' ').count(); + + if leading_spaces >= spaces { + // Reduce indentation + result.push_str(&line[spaces..]); + } else { + // Keep line as is if it doesn't have enough indentation + result.push_str(line); + } + } + + result +} diff --git a/crates/migrator/src/migrator.rs b/crates/migrator/src/migrator.rs index bcd41836e6f8d1d3dabf1f37c5ba456d475f12e1..be32b2734e66ebd621ff858a79eef322468b11ae 100644 --- a/crates/migrator/src/migrator.rs +++ b/crates/migrator/src/migrator.rs @@ -156,6 +156,10 @@ pub fn migrate_settings(text: &str) -> Result> { migrations::m_2025_06_25::SETTINGS_PATTERNS, &SETTINGS_QUERY_2025_06_25, ), + ( + migrations::m_2025_06_27::SETTINGS_PATTERNS, + &SETTINGS_QUERY_2025_06_27, + ), ]; run_migrations(text, migrations) } @@ -262,6 +266,10 @@ define_query!( SETTINGS_QUERY_2025_06_25, migrations::m_2025_06_25::SETTINGS_PATTERNS ); +define_query!( + SETTINGS_QUERY_2025_06_27, + migrations::m_2025_06_27::SETTINGS_PATTERNS +); // custom query static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock = LazyLock::new(|| { @@ -286,6 +294,15 @@ mod tests { pretty_assertions::assert_eq!(migrated.as_deref(), output); } + fn assert_migrate_settings_with_migrations( + migrations: &[(MigrationPatterns, &Query)], + input: &str, + output: Option<&str>, + ) { + let migrated = run_migrations(input, migrations).unwrap(); + pretty_assertions::assert_eq!(migrated.as_deref(), output); + } + #[test] fn test_replace_array_with_single_string() { assert_migrate_keymap( @@ -873,7 +890,11 @@ mod tests { #[test] fn test_mcp_settings_migration() { - assert_migrate_settings( + assert_migrate_settings_with_migrations( + &[( + migrations::m_2025_06_16::SETTINGS_PATTERNS, + &SETTINGS_QUERY_2025_06_16, + )], r#"{ "context_servers": { "empty_server": {}, @@ -1058,7 +1079,14 @@ mod tests { } } }"#; - assert_migrate_settings(settings, None); + assert_migrate_settings_with_migrations( + &[( + migrations::m_2025_06_16::SETTINGS_PATTERNS, + &SETTINGS_QUERY_2025_06_16, + )], + settings, + None, + ); } #[test] @@ -1131,4 +1159,100 @@ mod tests { None, ); } + + #[test] + fn test_flatten_context_server_command() { + assert_migrate_settings( + r#"{ + "context_servers": { + "some-mcp-server": { + "source": "custom", + "command": { + "path": "npx", + "args": [ + "-y", + "@supabase/mcp-server-supabase@latest", + "--read-only", + "--project-ref=" + ], + "env": { + "SUPABASE_ACCESS_TOKEN": "" + } + } + } + } +}"#, + Some( + r#"{ + "context_servers": { + "some-mcp-server": { + "source": "custom", + "command": "npx", + "args": [ + "-y", + "@supabase/mcp-server-supabase@latest", + "--read-only", + "--project-ref=" + ], + "env": { + "SUPABASE_ACCESS_TOKEN": "" + } + } + } +}"#, + ), + ); + + // Test with additional keys in server object + assert_migrate_settings( + r#"{ + "context_servers": { + "server-with-extras": { + "source": "custom", + "command": { + "path": "/usr/bin/node", + "args": ["server.js"] + }, + "settings": {} + } + } +}"#, + Some( + r#"{ + "context_servers": { + "server-with-extras": { + "source": "custom", + "command": "/usr/bin/node", + "args": ["server.js"], + "settings": {} + } + } +}"#, + ), + ); + + // Test command without args or env + assert_migrate_settings( + r#"{ + "context_servers": { + "simple-server": { + "source": "custom", + "command": { + "path": "simple-mcp-server" + } + } + } +}"#, + Some( + r#"{ + "context_servers": { + "simple-server": { + "source": "custom", + "command": "simple-mcp-server" + } + } +}"#, + ), + ); + } } diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index d2a4e5126c973bf9a1454c0a96c17e17c4c593e2..f45393fd5c3b08f1b62826aa54f4d7d519fc2ed1 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -97,9 +97,8 @@ pub enum ContextServerSettings { /// Whether the context server is enabled. #[serde(default = "default_true")] enabled: bool, - /// The command to run this context server. - /// - /// This will override the command set by an extension. + + #[serde(flatten)] command: ContextServerCommand, }, Extension { From ac3328adb633dbb6d431caea07d51cc268a3e2aa Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Mon, 30 Jun 2025 11:12:12 +0200 Subject: [PATCH 053/107] agent: Fix issue where web search could return 401 (#33639) Closes #33524 Release Notes: - agent: Fix an issue where performing a web search request would sometimes fail --- crates/web_search_providers/src/cloud.rs | 68 +++++++++++++++--------- 1 file changed, 44 insertions(+), 24 deletions(-) diff --git a/crates/web_search_providers/src/cloud.rs b/crates/web_search_providers/src/cloud.rs index 39602e071466d6aff126af075ade782e9da42928..de280cd5c0cda47570f48b3a956188a3101e97a2 100644 --- a/crates/web_search_providers/src/cloud.rs +++ b/crates/web_search_providers/src/cloud.rs @@ -8,7 +8,8 @@ use http_client::{HttpClient, Method}; use language_model::{LlmApiToken, RefreshLlmTokenListener}; use web_search::{WebSearchProvider, WebSearchProviderId}; use zed_llm_client::{ - CLIENT_SUPPORTS_EXA_WEB_SEARCH_PROVIDER_HEADER_NAME, WebSearchBody, WebSearchResponse, + CLIENT_SUPPORTS_EXA_WEB_SEARCH_PROVIDER_HEADER_NAME, EXPIRED_LLM_TOKEN_HEADER_NAME, + WebSearchBody, WebSearchResponse, }; pub struct CloudWebSearchProvider { @@ -73,32 +74,51 @@ async fn perform_web_search( llm_api_token: LlmApiToken, body: WebSearchBody, ) -> Result { + const MAX_RETRIES: usize = 3; + let http_client = &client.http_client(); + let mut retries_remaining = MAX_RETRIES; + let mut token = llm_api_token.acquire(&client).await?; - let token = llm_api_token.acquire(&client).await?; + loop { + if retries_remaining == 0 { + return Err(anyhow::anyhow!( + "error performing web search, max retries exceeded" + )); + } - let request = http_client::Request::builder() - .method(Method::POST) - .uri(http_client.build_zed_llm_url("/web_search", &[])?.as_ref()) - .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {token}")) - .header(CLIENT_SUPPORTS_EXA_WEB_SEARCH_PROVIDER_HEADER_NAME, "true") - .body(serde_json::to_string(&body)?.into())?; - let mut response = http_client - .send(request) - .await - .context("failed to send web search request")?; + let request = http_client::Request::builder() + .method(Method::POST) + .uri(http_client.build_zed_llm_url("/web_search", &[])?.as_ref()) + .header("Content-Type", "application/json") + .header("Authorization", format!("Bearer {token}")) + .header(CLIENT_SUPPORTS_EXA_WEB_SEARCH_PROVIDER_HEADER_NAME, "true") + .body(serde_json::to_string(&body)?.into())?; + let mut response = http_client + .send(request) + .await + .context("failed to send web search request")?; - if response.status().is_success() { - let mut body = String::new(); - response.body_mut().read_to_string(&mut body).await?; - return Ok(serde_json::from_str(&body)?); - } else { - let mut body = String::new(); - response.body_mut().read_to_string(&mut body).await?; - anyhow::bail!( - "error performing web search.\nStatus: {:?}\nBody: {body}", - response.status(), - ); + if response.status().is_success() { + let mut body = String::new(); + response.body_mut().read_to_string(&mut body).await?; + return Ok(serde_json::from_str(&body)?); + } else if response + .headers() + .get(EXPIRED_LLM_TOKEN_HEADER_NAME) + .is_some() + { + token = llm_api_token.refresh(&client).await?; + retries_remaining -= 1; + } else { + // For now we will only retry if the LLM token is expired, + // not if the request failed for any other reason. + let mut body = String::new(); + response.body_mut().read_to_string(&mut body).await?; + anyhow::bail!( + "error performing web search.\nStatus: {:?}\nBody: {body}", + response.status(), + ); + } } } From ae6237178c35d1aee9f2250ea79685554207d215 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 30 Jun 2025 12:18:43 +0300 Subject: [PATCH 054/107] Further improve color inlay hints in multi buffers (#33642) Follow-up of https://github.com/zed-industries/zed/pull/33605 Release Notes: - N/A --- crates/editor/src/display_map.rs | 6 +- crates/editor/src/display_map/fold_map.rs | 19 +- crates/editor/src/display_map/inlay_map.rs | 8 +- crates/editor/src/editor.rs | 4 +- crates/editor/src/element.rs | 10 +- crates/editor/src/lsp_colors.rs | 231 ++++++++++++--------- crates/project/src/lsp_store.rs | 80 +++++-- 7 files changed, 230 insertions(+), 128 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index a10a5f074c1c1922f6cdb053e7a4b37dd2230312..3352d21ef878835987e0227a926dfb61c893a182 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -37,7 +37,9 @@ pub use block_map::{ use block_map::{BlockRow, BlockSnapshot}; use collections::{HashMap, HashSet}; pub use crease_map::*; -pub use fold_map::{ChunkRenderer, ChunkRendererContext, Fold, FoldId, FoldPlaceholder, FoldPoint}; +pub use fold_map::{ + ChunkRenderer, ChunkRendererContext, ChunkRendererId, Fold, FoldId, FoldPlaceholder, FoldPoint, +}; use fold_map::{FoldMap, FoldSnapshot}; use gpui::{App, Context, Entity, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle}; pub use inlay_map::Inlay; @@ -538,7 +540,7 @@ impl DisplayMap { pub fn update_fold_widths( &mut self, - widths: impl IntoIterator, + widths: impl IntoIterator, cx: &mut Context, ) -> bool { let snapshot = self.buffer.read(cx).snapshot(cx); diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 197b85497b3abbc61885ebed1a80f1a707f0eb20..f37e7063e7228176b0f5455c278f331ed31d6ba0 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,4 +1,4 @@ -use crate::display_map::inlay_map::InlayChunk; +use crate::{InlayId, display_map::inlay_map::InlayChunk}; use super::{ Highlights, @@ -277,13 +277,16 @@ impl FoldMapWriter<'_> { pub(crate) fn update_fold_widths( &mut self, - new_widths: impl IntoIterator, + new_widths: impl IntoIterator, ) -> (FoldSnapshot, Vec) { let mut edits = Vec::new(); let inlay_snapshot = self.0.snapshot.inlay_snapshot.clone(); let buffer = &inlay_snapshot.buffer; for (id, new_width) in new_widths { + let ChunkRendererId::Fold(id) = id else { + continue; + }; if let Some(metadata) = self.0.snapshot.fold_metadata_by_id.get(&id).cloned() { if Some(new_width) != metadata.width { let buffer_start = metadata.range.start.to_offset(buffer); @@ -529,7 +532,7 @@ impl FoldMap { placeholder: Some(TransformPlaceholder { text: ELLIPSIS, renderer: ChunkRenderer { - id: fold.id, + id: ChunkRendererId::Fold(fold.id), render: Arc::new(move |cx| { (fold.placeholder.render)( fold_id, @@ -1267,11 +1270,17 @@ pub struct Chunk<'a> { pub renderer: Option, } +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum ChunkRendererId { + Fold(FoldId), + Inlay(InlayId), +} + /// A recipe for how the chunk should be presented. #[derive(Clone)] pub struct ChunkRenderer { - /// The id of the fold associated with this chunk. - pub id: FoldId, + /// The id of the renderer associated with this chunk. + pub id: ChunkRendererId, /// Creates a custom element to represent this chunk. pub render: Arc AnyElement>, /// If true, the element is constrained to the shaped width of the text. diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 4f7c4962f2d14e9e1436f5abb3a567972e43d69f..488e31c90a385a2200b9fe0e253067b26a423fc4 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1,4 +1,4 @@ -use crate::{ChunkRenderer, HighlightStyles, InlayId, display_map::FoldId}; +use crate::{ChunkRenderer, HighlightStyles, InlayId}; use collections::BTreeSet; use gpui::{Hsla, Rgba}; use language::{Chunk, Edit, Point, TextSummary}; @@ -14,7 +14,7 @@ use sum_tree::{Bias, Cursor, SumTree}; use text::{Patch, Rope}; use ui::{ActiveTheme, IntoElement as _, ParentElement as _, Styled as _, div}; -use super::{Highlights, custom_highlights::CustomHighlightsChunks}; +use super::{Highlights, custom_highlights::CustomHighlightsChunks, fold_map::ChunkRendererId}; /// Decides where the [`Inlay`]s should be displayed. /// @@ -338,10 +338,10 @@ impl<'a> Iterator for InlayChunks<'a> { } InlayId::Hint(_) => self.highlight_styles.inlay_hint, InlayId::DebuggerValue(_) => self.highlight_styles.inlay_hint, - InlayId::Color(id) => { + InlayId::Color(_) => { if let Some(color) = inlay.color { renderer = Some(ChunkRenderer { - id: FoldId(id), + id: ChunkRendererId::Inlay(inlay.id), render: Arc::new(move |cx| { div() .w_4() diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 419e3c4ae9693e3984bf09777dce006cd5ce03a9..66f5f2d5fa86edc2d37fb0eb2360dc9f43630011 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -17333,9 +17333,9 @@ impl Editor { self.active_indent_guides_state.dirty = true; } - pub fn update_fold_widths( + pub fn update_renderer_widths( &mut self, - widths: impl IntoIterator, + widths: impl IntoIterator, cx: &mut Context, ) -> bool { self.display_map diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 426053707649c01aa655902f1a94c302125ef103..1c55ff2d092835f6162e3321fd4da00a1ad83d59 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -12,8 +12,8 @@ use crate::{ ToggleFold, code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP}, display_map::{ - Block, BlockContext, BlockStyle, DisplaySnapshot, EditorMargins, FoldId, HighlightKey, - HighlightedChunk, ToDisplayPoint, + Block, BlockContext, BlockStyle, ChunkRendererId, DisplaySnapshot, EditorMargins, + HighlightKey, HighlightedChunk, ToDisplayPoint, }, editor_settings::{ CurrentLineHighlight, DocumentColorsRenderMode, DoubleClickInMultibuffer, Minimap, @@ -7119,7 +7119,7 @@ pub(crate) struct LineWithInvisibles { enum LineFragment { Text(ShapedLine), Element { - id: FoldId, + id: ChunkRendererId, element: Option, size: Size, len: usize, @@ -8297,7 +8297,7 @@ impl Element for EditorElement { window, cx, ); - let new_fold_widths = line_layouts + let new_renrerer_widths = line_layouts .iter() .flat_map(|layout| &layout.fragments) .filter_map(|fragment| { @@ -8308,7 +8308,7 @@ impl Element for EditorElement { } }); if self.editor.update(cx, |editor, cx| { - editor.update_fold_widths(new_fold_widths, cx) + editor.update_renderer_widths(new_renrerer_widths, cx) }) { // If the fold widths have changed, we need to prepaint // the element again to account for any changes in diff --git a/crates/editor/src/lsp_colors.rs b/crates/editor/src/lsp_colors.rs index 7f771b9266591aae334106087acd8278c56b9dfd..ce07dd43fe8ffc2a3705eefec96e2382312301a7 100644 --- a/crates/editor/src/lsp_colors.rs +++ b/crates/editor/src/lsp_colors.rs @@ -19,18 +19,21 @@ use crate::{ #[derive(Debug)] pub(super) struct LspColorData { - cache_version_used: usize, + buffer_colors: HashMap, + render_mode: DocumentColorsRenderMode, +} + +#[derive(Debug, Default)] +struct BufferColors { colors: Vec<(Range, DocumentColor, InlayId)>, inlay_colors: HashMap, - render_mode: DocumentColorsRenderMode, + cache_version_used: usize, } impl LspColorData { pub fn new(cx: &App) -> Self { Self { - cache_version_used: 0, - colors: Vec::new(), - inlay_colors: HashMap::default(), + buffer_colors: HashMap::default(), render_mode: EditorSettings::get_global(cx).lsp_document_colors, } } @@ -47,8 +50,9 @@ impl LspColorData { DocumentColorsRenderMode::Inlay => Some(InlaySplice { to_remove: Vec::new(), to_insert: self - .colors + .buffer_colors .iter() + .flat_map(|(_, buffer_colors)| buffer_colors.colors.iter()) .map(|(range, color, id)| { Inlay::color( id.id(), @@ -63,33 +67,49 @@ impl LspColorData { }) .collect(), }), - DocumentColorsRenderMode::None => { - self.colors.clear(); - Some(InlaySplice { - to_remove: self.inlay_colors.drain().map(|(id, _)| id).collect(), - to_insert: Vec::new(), - }) - } + DocumentColorsRenderMode::None => Some(InlaySplice { + to_remove: self + .buffer_colors + .drain() + .flat_map(|(_, buffer_colors)| buffer_colors.inlay_colors) + .map(|(id, _)| id) + .collect(), + to_insert: Vec::new(), + }), DocumentColorsRenderMode::Border | DocumentColorsRenderMode::Background => { Some(InlaySplice { - to_remove: self.inlay_colors.drain().map(|(id, _)| id).collect(), + to_remove: self + .buffer_colors + .iter_mut() + .flat_map(|(_, buffer_colors)| buffer_colors.inlay_colors.drain()) + .map(|(id, _)| id) + .collect(), to_insert: Vec::new(), }) } } } - fn set_colors(&mut self, colors: Vec<(Range, DocumentColor, InlayId)>) -> bool { - if self.colors == colors { + fn set_colors( + &mut self, + buffer_id: BufferId, + colors: Vec<(Range, DocumentColor, InlayId)>, + cache_version: Option, + ) -> bool { + let buffer_colors = self.buffer_colors.entry(buffer_id).or_default(); + if let Some(cache_version) = cache_version { + buffer_colors.cache_version_used = cache_version; + } + if buffer_colors.colors == colors { return false; } - self.inlay_colors = colors + buffer_colors.inlay_colors = colors .iter() .enumerate() .map(|(i, (_, _, id))| (*id, i)) .collect(); - self.colors = colors; + buffer_colors.colors = colors; true } @@ -103,8 +123,9 @@ impl LspColorData { { Vec::new() } else { - self.colors + self.buffer_colors .iter() + .flat_map(|(_, buffer_colors)| &buffer_colors.colors) .map(|(range, color, _)| { let display_range = range.clone().to_display_points(snapshot); let color = Hsla::from(Rgba { @@ -162,10 +183,9 @@ impl Editor { ColorFetchStrategy::IgnoreCache } else { ColorFetchStrategy::UseCache { - known_cache_version: self - .colors - .as_ref() - .map(|colors| colors.cache_version_used), + known_cache_version: self.colors.as_ref().and_then(|colors| { + Some(colors.buffer_colors.get(&buffer_id)?.cache_version_used) + }), } }; let colors_task = lsp_store.document_colors(fetch_strategy, buffer, cx)?; @@ -201,15 +221,13 @@ impl Editor { return; }; - let mut cache_version = None; - let mut new_editor_colors = Vec::<(Range, DocumentColor)>::new(); + let mut new_editor_colors = HashMap::default(); for (buffer_id, colors) in all_colors { let Some(excerpts) = editor_excerpts.get(&buffer_id) else { continue; }; match colors { Ok(colors) => { - cache_version = colors.cache_version; for color in colors.colors { let color_start = point_from_lsp(color.lsp_range.start); let color_end = point_from_lsp(color.lsp_range.end); @@ -243,8 +261,15 @@ impl Editor { continue; }; + let new_entry = + new_editor_colors.entry(buffer_id).or_insert_with(|| { + (Vec::<(Range, DocumentColor)>::new(), None) + }); + new_entry.1 = colors.cache_version; + let new_buffer_colors = &mut new_entry.0; + let (Ok(i) | Err(i)) = - new_editor_colors.binary_search_by(|(probe, _)| { + new_buffer_colors.binary_search_by(|(probe, _)| { probe .start .cmp(&color_start_anchor, &multi_buffer_snapshot) @@ -254,7 +279,7 @@ impl Editor { .cmp(&color_end_anchor, &multi_buffer_snapshot) }) }); - new_editor_colors + new_buffer_colors .insert(i, (color_start_anchor..color_end_anchor, color)); break; } @@ -267,45 +292,70 @@ impl Editor { editor .update(cx, |editor, cx| { let mut colors_splice = InlaySplice::default(); - let mut new_color_inlays = Vec::with_capacity(new_editor_colors.len()); let Some(colors) = &mut editor.colors else { return; }; - let mut existing_colors = colors.colors.iter().peekable(); - for (new_range, new_color) in new_editor_colors { - let rgba_color = Rgba { - r: new_color.color.red, - g: new_color.color.green, - b: new_color.color.blue, - a: new_color.color.alpha, - }; + let mut updated = false; + for (buffer_id, (new_buffer_colors, new_cache_version)) in new_editor_colors { + let mut new_buffer_color_inlays = + Vec::with_capacity(new_buffer_colors.len()); + let mut existing_buffer_colors = colors + .buffer_colors + .entry(buffer_id) + .or_default() + .colors + .iter() + .peekable(); + for (new_range, new_color) in new_buffer_colors { + let rgba_color = Rgba { + r: new_color.color.red, + g: new_color.color.green, + b: new_color.color.blue, + a: new_color.color.alpha, + }; - loop { - match existing_colors.peek() { - Some((existing_range, existing_color, existing_inlay_id)) => { - match existing_range - .start - .cmp(&new_range.start, &multi_buffer_snapshot) - .then_with(|| { - existing_range - .end - .cmp(&new_range.end, &multi_buffer_snapshot) - }) { - cmp::Ordering::Less => { - colors_splice.to_remove.push(*existing_inlay_id); - existing_colors.next(); - continue; - } - cmp::Ordering::Equal => { - if existing_color == &new_color { - new_color_inlays.push(( - new_range, - new_color, - *existing_inlay_id, - )); - } else { + loop { + match existing_buffer_colors.peek() { + Some((existing_range, existing_color, existing_inlay_id)) => { + match existing_range + .start + .cmp(&new_range.start, &multi_buffer_snapshot) + .then_with(|| { + existing_range + .end + .cmp(&new_range.end, &multi_buffer_snapshot) + }) { + cmp::Ordering::Less => { colors_splice.to_remove.push(*existing_inlay_id); + existing_buffer_colors.next(); + continue; + } + cmp::Ordering::Equal => { + if existing_color == &new_color { + new_buffer_color_inlays.push(( + new_range, + new_color, + *existing_inlay_id, + )); + } else { + colors_splice + .to_remove + .push(*existing_inlay_id); + let inlay = Inlay::color( + post_inc(&mut editor.next_color_inlay_id), + new_range.start, + rgba_color, + ); + let inlay_id = inlay.id; + colors_splice.to_insert.push(inlay); + new_buffer_color_inlays + .push((new_range, new_color, inlay_id)); + } + existing_buffer_colors.next(); + break; + } + cmp::Ordering::Greater => { let inlay = Inlay::color( post_inc(&mut editor.next_color_inlay_id), new_range.start, @@ -313,49 +363,40 @@ impl Editor { ); let inlay_id = inlay.id; colors_splice.to_insert.push(inlay); - new_color_inlays + new_buffer_color_inlays .push((new_range, new_color, inlay_id)); + break; } - existing_colors.next(); - break; - } - cmp::Ordering::Greater => { - let inlay = Inlay::color( - post_inc(&mut editor.next_color_inlay_id), - new_range.start, - rgba_color, - ); - let inlay_id = inlay.id; - colors_splice.to_insert.push(inlay); - new_color_inlays.push((new_range, new_color, inlay_id)); - break; } } - } - None => { - let inlay = Inlay::color( - post_inc(&mut editor.next_color_inlay_id), - new_range.start, - rgba_color, - ); - let inlay_id = inlay.id; - colors_splice.to_insert.push(inlay); - new_color_inlays.push((new_range, new_color, inlay_id)); - break; + None => { + let inlay = Inlay::color( + post_inc(&mut editor.next_color_inlay_id), + new_range.start, + rgba_color, + ); + let inlay_id = inlay.id; + colors_splice.to_insert.push(inlay); + new_buffer_color_inlays + .push((new_range, new_color, inlay_id)); + break; + } } } } - } - if existing_colors.peek().is_some() { - colors_splice - .to_remove - .extend(existing_colors.map(|(_, _, id)| *id)); - } - let mut updated = colors.set_colors(new_color_inlays); - if let Some(cache_version) = cache_version { - colors.cache_version_used = cache_version; + if existing_buffer_colors.peek().is_some() { + colors_splice + .to_remove + .extend(existing_buffer_colors.map(|(_, _, id)| *id)); + } + updated |= colors.set_colors( + buffer_id, + new_buffer_color_inlays, + new_cache_version, + ); } + if colors.render_mode == DocumentColorsRenderMode::Inlay && (!colors_splice.to_insert.is_empty() || !colors_splice.to_remove.is_empty()) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index bf269ba1d7bcd0886a480376f50060d70e96a195..dc402be2b6db4070e9754b549e847ce653593297 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -170,6 +170,7 @@ pub struct LocalLspStore { _subscription: gpui::Subscription, lsp_tree: Entity, registered_buffers: HashMap, + buffers_opened_in_servers: HashMap>, buffer_pull_diagnostics_result_ids: HashMap>>, } @@ -2546,6 +2547,10 @@ impl LocalLspStore { vec![snapshot] }); + self.buffers_opened_in_servers + .entry(buffer_id) + .or_default() + .insert(server.server_id()); cx.emit(LspStoreEvent::LanguageServerUpdate { language_server_id: server.server_id(), name: None, @@ -3208,6 +3213,9 @@ impl LocalLspStore { self.language_servers.remove(server_id_to_remove); self.buffer_pull_diagnostics_result_ids .remove(server_id_to_remove); + for buffer_servers in self.buffers_opened_in_servers.values_mut() { + buffer_servers.remove(server_id_to_remove); + } cx.emit(LspStoreEvent::LanguageServerRemoved(*server_id_to_remove)); } servers_to_remove.into_keys().collect() @@ -3787,6 +3795,7 @@ impl LspStore { }), lsp_tree: LanguageServerTree::new(manifest_tree, languages.clone(), cx), registered_buffers: HashMap::default(), + buffers_opened_in_servers: HashMap::default(), buffer_pull_diagnostics_result_ids: HashMap::default(), }), last_formatting_failure: None, @@ -4159,6 +4168,7 @@ impl LspStore { lsp_store.lsp_data.remove(&buffer_id); let local = lsp_store.as_local_mut().unwrap(); local.registered_buffers.remove(&buffer_id); + local.buffers_opened_in_servers.remove(&buffer_id); if let Some(file) = File::from_dyn(buffer.read(cx).file()).cloned() { local.unregister_old_buffer_from_language_servers(&buffer, &file, cx); } @@ -6235,21 +6245,31 @@ impl LspStore { } => { if let Some(cached_data) = self.lsp_data.get(&buffer_id) { if !version_queried_for.changed_since(&cached_data.colors_for_version) { - if Some(cached_data.cache_version) == known_cache_version { - return None; - } else { - return Some( - Task::ready(Ok(DocumentColors { - colors: cached_data - .colors - .values() - .flatten() - .cloned() - .collect(), - cache_version: Some(cached_data.cache_version), - })) - .shared(), - ); + let has_different_servers = self.as_local().is_some_and(|local| { + local + .buffers_opened_in_servers + .get(&buffer_id) + .cloned() + .unwrap_or_default() + != cached_data.colors.keys().copied().collect() + }); + if !has_different_servers { + if Some(cached_data.cache_version) == known_cache_version { + return None; + } else { + return Some( + Task::ready(Ok(DocumentColors { + colors: cached_data + .colors + .values() + .flatten() + .cloned() + .collect(), + cache_version: Some(cached_data.cache_version), + })) + .shared(), + ); + } } } } @@ -7522,6 +7542,14 @@ impl LspStore { .unwrap_or(true) }) .map(|(_, server)| server.server_id()) + .filter(|server_id| { + self.as_local().is_none_or(|local| { + local + .buffers_opened_in_servers + .get(&snapshot.remote_id()) + .is_some_and(|servers| servers.contains(server_id)) + }) + }) .collect::>() }); @@ -10084,6 +10112,7 @@ impl LspStore { } // Tell the language server about every open buffer in the worktree that matches the language. + let mut buffer_paths_registered = Vec::new(); self.buffer_store.clone().update(cx, |buffer_store, cx| { for buffer_handle in buffer_store.buffers() { let buffer = buffer_handle.read(cx); @@ -10142,6 +10171,12 @@ impl LspStore { version, initial_snapshot.text(), ); + buffer_paths_registered.push(file.abs_path(cx)); + local + .buffers_opened_in_servers + .entry(buffer.remote_id()) + .or_default() + .insert(server_id); } buffer_handle.update(cx, |buffer, cx| { buffer.set_completion_triggers( @@ -10163,6 +10198,18 @@ impl LspStore { } }); + for abs_path in buffer_paths_registered { + cx.emit(LspStoreEvent::LanguageServerUpdate { + language_server_id: server_id, + name: Some(adapter.name()), + message: proto::update_language_server::Variant::RegisteredForBuffer( + proto::RegisteredForBuffer { + buffer_abs_path: abs_path.to_string_lossy().to_string(), + }, + ), + }); + } + cx.notify(); } @@ -10612,6 +10659,9 @@ impl LspStore { } if let Some(local) = self.as_local_mut() { local.buffer_pull_diagnostics_result_ids.remove(&for_server); + for buffer_servers in local.buffers_opened_in_servers.values_mut() { + buffer_servers.remove(&for_server); + } } } From ca0bd53bede259c78f78bd4f0ab10303ea3c8b0b Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Mon, 30 Jun 2025 11:31:40 +0200 Subject: [PATCH 055/107] agent: Fix an issue with messages containing trailing whitespace (#33643) Seeing this come up in our server logs when sending requests to Anthropic: `final assistant content cannot end with trailing whitespace`. Release Notes: - agent: Fixed an issue where Anthropic requests would sometimes fail because of malformed assistant messages --- crates/agent/src/thread.rs | 1 + crates/language_models/src/provider/anthropic.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index 32c376ca67fdfa492233ccb6ce1b2947abba0538..2f965e232af876147feb906bd926438450dc78da 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -1343,6 +1343,7 @@ impl Thread { for segment in &message.segments { match segment { MessageSegment::Text(text) => { + let text = text.trim_end(); if !text.is_empty() { request_message .content diff --git a/crates/language_models/src/provider/anthropic.rs b/crates/language_models/src/provider/anthropic.rs index 48bea47fec02a0cb5b51b59883492caf00d1c982..aa500f4b4ddaac6f7c57da24d4300f192d134e2d 100644 --- a/crates/language_models/src/provider/anthropic.rs +++ b/crates/language_models/src/provider/anthropic.rs @@ -528,6 +528,11 @@ pub fn into_anthropic( .into_iter() .filter_map(|content| match content { MessageContent::Text(text) => { + let text = if text.chars().last().map_or(false, |c| c.is_whitespace()) { + text.trim_end().to_string() + } else { + text + }; if !text.is_empty() { Some(anthropic::RequestContent::Text { text, From f4aeeda2d93ba2f16cc123b17a0bf785aacea41c Mon Sep 17 00:00:00 2001 From: Umesh Yadav <23421535+imumesh18@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:21:58 +0530 Subject: [PATCH 056/107] script: Fix license symlink and path in `new-crate.sh` (#33620) While creating a new crate I realised the License symlink and path are broken. The symlink was broken for LICENSE-GPL. Also the file created in the new crate was not using the expected file name as per the check-license script which was failing due to wrong filename in the new crate. I fixed that as well. Release Notes: - N/A Signed-off-by: Umesh Yadav --- script/new-crate | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/new-crate b/script/new-crate index 44b5a6e5c8de2444354e6959e4539b3261547aea..df574981e739a465f3f4f92d8a05c8df7cffdb82 100755 --- a/script/new-crate +++ b/script/new-crate @@ -27,7 +27,7 @@ elif [[ "$LICENSE_FLAG" == *"agpl"* ]]; then LICENSE_FILE="LICENSE-AGPL" else LICENSE_MODE="GPL-3.0-or-later" - LICENSE_FILE="LICENSE" + LICENSE_FILE="LICENSE-GPL" fi if [[ ! "$CRATE_NAME" =~ ^[a-z0-9_]+$ ]]; then @@ -39,7 +39,7 @@ CRATE_PATH="crates/$CRATE_NAME" mkdir -p "$CRATE_PATH/src" # Symlink the license -ln -sf "../../../$LICENSE_FILE" "$CRATE_PATH/LICENSE" +ln -sf "../../../$LICENSE_FILE" "$CRATE_PATH/$LICENSE_FILE" CARGO_TOML_TEMPLATE=$(cat << 'EOF' [package] From aa7ccecc49bd4ffb57261262f051edfbcae82bc5 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Mon, 30 Jun 2025 12:26:14 +0200 Subject: [PATCH 057/107] agent: Reduce log spam for context servers (#33644) Previously we would always run `maintain_servers` even if the settings did not change. While this would not cause any MCP servers to restart, we would still go through all configured servers and call the `command(...)` function on each installed MCP extension. This can cause lots of logs to show up when an MCP server is not configured correctly. Release Notes: - N/A --- crates/project/src/context_server_store.rs | 52 +++++++++++++++++----- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/crates/project/src/context_server_store.rs b/crates/project/src/context_server_store.rs index 3bde9d6b36b42fe30aaf0f0fce903c3c0e373f3f..6f93238cc9b6f8d6fad08e25baa819eae4ef9b4b 100644 --- a/crates/project/src/context_server_store.rs +++ b/crates/project/src/context_server_store.rs @@ -135,6 +135,7 @@ pub type ContextServerFactory = Box) -> Arc>; pub struct ContextServerStore { + context_server_settings: HashMap, ContextServerSettings>, servers: HashMap, worktree_store: Entity, registry: Entity, @@ -202,6 +203,11 @@ impl ContextServerStore { this.available_context_servers_changed(cx); }), cx.observe_global::(|this, cx| { + let settings = Self::resolve_context_server_settings(&this.worktree_store, cx); + if &this.context_server_settings == settings { + return; + } + this.context_server_settings = settings.clone(); this.available_context_servers_changed(cx); }), ] @@ -211,6 +217,8 @@ impl ContextServerStore { let mut this = Self { _subscriptions: subscriptions, + context_server_settings: Self::resolve_context_server_settings(&worktree_store, cx) + .clone(), worktree_store, registry, needs_server_update: false, @@ -268,10 +276,8 @@ impl ContextServerStore { cx.spawn(async move |this, cx| { let this = this.upgrade().context("Context server store dropped")?; let settings = this - .update(cx, |this, cx| { - this.context_server_settings(cx) - .get(&server.id().0) - .cloned() + .update(cx, |this, _| { + this.context_server_settings.get(&server.id().0).cloned() }) .ok() .flatten() @@ -439,12 +445,11 @@ impl ContextServerStore { } } - fn context_server_settings<'a>( - &'a self, + fn resolve_context_server_settings<'a>( + worktree_store: &'a Entity, cx: &'a App, ) -> &'a HashMap, ContextServerSettings> { - let location = self - .worktree_store + let location = worktree_store .read(cx) .visible_worktrees(cx) .next() @@ -492,9 +497,9 @@ impl ContextServerStore { } async fn maintain_servers(this: WeakEntity, cx: &mut AsyncApp) -> Result<()> { - let (mut configured_servers, registry, worktree_store) = this.update(cx, |this, cx| { + let (mut configured_servers, registry, worktree_store) = this.update(cx, |this, _| { ( - this.context_server_settings(cx).clone(), + this.context_server_settings.clone(), this.registry.clone(), this.worktree_store.clone(), ) @@ -990,6 +995,33 @@ mod tests { assert_eq!(store.read(cx).status_for_server(&server_2_id), None); }); } + + // Ensure that nothing happens if the settings do not change + { + let _server_events = assert_server_events(&store, vec![], cx); + set_context_server_configuration( + vec![( + server_1_id.0.clone(), + ContextServerSettings::Extension { + enabled: true, + settings: json!({ + "somevalue": false + }), + }, + )], + cx, + ); + + cx.run_until_parked(); + + cx.update(|cx| { + assert_eq!( + store.read(cx).status_for_server(&server_1_id), + Some(ContextServerStatus::Running) + ); + assert_eq!(store.read(cx).status_for_server(&server_2_id), None); + }); + } } #[gpui::test] From 8c04f12499689b531b2c21b9d979772343693bb4 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 30 Jun 2025 14:49:09 +0200 Subject: [PATCH 058/107] debugger: Tighten up breakpoint list (#33645) Release Notes: - N/A --- crates/debugger_ui/src/debugger_panel.rs | 5 +++ .../src/session/running/breakpoint_list.rs | 33 +++++++++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index b2cd62134f2f8bcab82ed609d06117b7a0a0825a..795b4caf9e43a28c8bf115755332fa9976d89d93 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -1298,6 +1298,11 @@ impl Render for DebugPanel { } v_flex() + .when_else( + self.position(window, cx) == DockPosition::Bottom, + |this| this.max_h(self.size), + |this| this.max_w(self.size), + ) .size_full() .key_context("DebugPanel") .child(h_flex().children(self.top_controls_strip(window, cx))) diff --git a/crates/debugger_ui/src/session/running/breakpoint_list.rs b/crates/debugger_ui/src/session/running/breakpoint_list.rs index 407552c3025262f60d40988076ad8176ccfe7c91..5576435a0875ae298a7a7f5fb9d509a6a7ea16f1 100644 --- a/crates/debugger_ui/src/session/running/breakpoint_list.rs +++ b/crates/debugger_ui/src/session/running/breakpoint_list.rs @@ -877,9 +877,27 @@ impl LineBreakpoint { }) .cursor_pointer() .child( - Label::new(format!("{}:{}", self.name, self.line)) - .size(LabelSize::Small) - .line_height_style(ui::LineHeightStyle::UiLabel), + h_flex() + .gap_0p5() + .child( + Label::new(format!("{}:{}", self.name, self.line)) + .size(LabelSize::Small) + .line_height_style(ui::LineHeightStyle::UiLabel), + ) + .children(self.dir.as_ref().and_then(|dir| { + let path_without_root = Path::new(dir.as_ref()) + .components() + .skip(1) + .collect::(); + path_without_root.components().next()?; + Some( + Label::new(path_without_root.to_string_lossy().into_owned()) + .color(Color::Muted) + .size(LabelSize::Small) + .line_height_style(ui::LineHeightStyle::UiLabel) + .truncate(), + ) + })), ) .when_some(self.dir.as_ref(), |this, parent_dir| { this.tooltip(Tooltip::text(format!("Worktree parent path: {parent_dir}"))) @@ -1227,14 +1245,15 @@ impl RenderOnce for BreakpointOptionsStrip { }; h_flex() - .gap_2() + .gap_1() .child( - div() .map(self.add_border(ActiveBreakpointStripMode::Log, supports_logs, window, cx)) + div().map(self.add_border(ActiveBreakpointStripMode::Log, supports_logs, window, cx)) .child( IconButton::new( SharedString::from(format!("{id}-log-toggle")), IconName::ScrollText, ) + .icon_size(IconSize::XSmall) .style(style_for_toggle(ActiveBreakpointStripMode::Log, has_logs)) .icon_color(color_for_toggle(has_logs)) .disabled(!supports_logs) @@ -1254,6 +1273,7 @@ impl RenderOnce for BreakpointOptionsStrip { SharedString::from(format!("{id}-condition-toggle")), IconName::SplitAlt, ) + .icon_size(IconSize::XSmall) .style(style_for_toggle( ActiveBreakpointStripMode::Condition, has_condition @@ -1267,7 +1287,7 @@ impl RenderOnce for BreakpointOptionsStrip { .when(!has_condition && !self.is_selected, |this| this.invisible()), ) .child( - div() .map(self.add_border( + div().map(self.add_border( ActiveBreakpointStripMode::HitCondition, supports_hit_condition,window, cx )) @@ -1276,6 +1296,7 @@ impl RenderOnce for BreakpointOptionsStrip { SharedString::from(format!("{id}-hit-condition-toggle")), IconName::ArrowDown10, ) + .icon_size(IconSize::XSmall) .style(style_for_toggle( ActiveBreakpointStripMode::HitCondition, has_hit_condition, From 1c050624823d2aa66d57c845662405d9d44556f6 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:32:27 -0300 Subject: [PATCH 059/107] agent: Always focus on to the active model in the picker (#33567) Release Notes: - agent: Improved the model selector by ensuring the active model is always focused on open. --- crates/agent_ui/src/language_model_selector.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/agent_ui/src/language_model_selector.rs b/crates/agent_ui/src/language_model_selector.rs index d9d11231edbe128fcfd9a486278ed8e1542b4397..55c0974fc1d2bdbd65e0b6d746abf7f4ef10654d 100644 --- a/crates/agent_ui/src/language_model_selector.rs +++ b/crates/agent_ui/src/language_model_selector.rs @@ -399,7 +399,7 @@ impl PickerDelegate for LanguageModelPickerDelegate { cx: &mut Context>, ) -> Task<()> { let all_models = self.all_models.clone(); - let current_index = self.selected_index; + let active_model = (self.get_active_model)(cx); let bg_executor = cx.background_executor(); let language_model_registry = LanguageModelRegistry::global(cx); @@ -441,12 +441,9 @@ impl PickerDelegate for LanguageModelPickerDelegate { cx.spawn_in(window, async move |this, cx| { this.update_in(cx, |this, window, cx| { this.delegate.filtered_entries = filtered_models.entries(); - // Preserve selection focus - let new_index = if current_index >= this.delegate.filtered_entries.len() { - 0 - } else { - current_index - }; + // Finds the currently selected model in the list + let new_index = + Self::get_active_model_index(&this.delegate.filtered_entries, active_model); this.set_selected_index(new_index, Some(picker::Direction::Down), true, window, cx); cx.notify(); }) From e37ef2a99110eea68ebb25dcb458ee4eda1d33b1 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 30 Jun 2025 16:40:31 +0300 Subject: [PATCH 060/107] Use more generic error messages in gpui (#33651) Follow-up of https://github.com/zed-industries/zed/pull/32537 Release Notes: - N/A --- crates/gpui/src/platform.rs | 2 +- crates/gpui/src/platform/windows/window.rs | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index ac21c5dea12c783790d3877735a7074f7dbd9c95..277f2d9ab8762c43473c7c07ef58a3c5188d091b 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -151,7 +151,7 @@ pub fn guess_compositor() -> &'static str { pub(crate) fn current_platform(_headless: bool) -> Rc { Rc::new( WindowsPlatform::new() - .inspect_err(|err| show_error("Error: Zed failed to launch", err.to_string())) + .inspect_err(|err| show_error("Failed to launch", err.to_string())) .unwrap(), ) } diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index c363d5854deccbb6d0f29391b2d47316f228b57d..27c843932bb2a38f37f3b01b354a7eed7f8354fe 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -1299,12 +1299,8 @@ mod windows_renderer { size: Default::default(), transparent, }; - BladeRenderer::new(context, &raw, config).inspect_err(|err| { - show_error( - "Error: Zed failed to initialize BladeRenderer", - err.to_string(), - ) - }) + BladeRenderer::new(context, &raw, config) + .inspect_err(|err| show_error("Failed to initialize BladeRenderer", err.to_string())) } struct RawWindow { From f106ea7641c585d31ff9703b270d7f39d602f8ec Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 30 Jun 2025 10:42:38 -0300 Subject: [PATCH 061/107] docs: Update custom MCP format template (#33649) To match the new format added in https://github.com/zed-industries/zed/pull/33539. Release Notes: - N/A --- docs/src/ai/mcp.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/src/ai/mcp.md b/docs/src/ai/mcp.md index 8fbea0fef11fa6b18480ea1a1b73501fcea59c89..202b14102209ae8d3dbf338ff11bb8b443432cf9 100644 --- a/docs/src/ai/mcp.md +++ b/docs/src/ai/mcp.md @@ -40,13 +40,11 @@ You can connect them by adding their commands directly to your `settings.json`, ```json { "context_servers": { - "some-context-server": { + "your-mcp-server": { "source": "custom", - "command": { - "path": "some-command", - "args": ["arg-1", "arg-2"], - "env": {} - } + "command": "some-command", + "args": ["arg-1", "arg-2"], + "env": {} } } } From 22ab4c53d119213454a6e2fad2bd44f47c9c5af5 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Mon, 30 Jun 2025 10:03:09 -0400 Subject: [PATCH 062/107] R docs: Remove non-working configuration (#33654) This config was meant to be commented out in #33594 because it does not work. Release Notes: - N/A --- docs/src/languages/r.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/languages/r.md b/docs/src/languages/r.md index 6b40f6970dda16b9405bfd15842452fbe28fe947..226a6f866846da43a3f32668dd19e1efb3f657ce 100644 --- a/docs/src/languages/r.md +++ b/docs/src/languages/r.md @@ -63,6 +63,9 @@ See [Using lintr](https://lintr.r-lib.org/articles/lintr.html) for a complete li `REditorSupport/languageserver` bundles support for [r-lib/styler](https://github.com/r-lib/styler) as a formatter. See [Customizing Styler](https://cran.r-project.org/web/packages/styler/vignettes/customizing_styler.html) for more information on how to customize its behavior. + + "] -autoclose_before = "}])>" +autoclose_before = ";:.,=}])>" brackets = [ { start = "{", end = "}", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true }, From 0629804390cc2e6c7d35b3f4f88154286ccf0c43 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Tue, 1 Jul 2025 13:32:14 +0200 Subject: [PATCH 095/107] agent: Clarify upgrade path when starting trial (#33706) Release Notes: - N/A --- crates/language_models/src/provider/cloud.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index 9472fed18e78a3005ea3ceb7e373292ecdc2b4aa..5417c329abaa107f9750c8a35a791afcc11b87ae 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -1106,10 +1106,17 @@ impl RenderOnce for ZedAIConfiguration { .on_click(|_, _, cx| cx.open_url(ZED_PRICING_URL)), ) .child( - Button::new("upgrade", "Upgrade") - .style(ButtonStyle::Subtle) - .color(Color::Accent) - .on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))), + Button::new( + "upgrade", + if self.plan.is_none() && self.eligible_for_trial { + "Start Trial" + } else { + "Upgrade" + }, + ) + .style(ButtonStyle::Subtle) + .color(Color::Accent) + .on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))), ) }; From a5b2428897bc441bd7ac8bf9a62368622c12fbe5 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Tue, 1 Jul 2025 07:34:50 -0400 Subject: [PATCH 096/107] debugger: Fix Go locator for subtests (#33694) Closes #33054 Release Notes: - Fixed debugging Go subtests. --- crates/project/src/debugger/locators/go.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/project/src/debugger/locators/go.rs b/crates/project/src/debugger/locators/go.rs index 79d7a1721c5f4013443bdbda7970571377c6a65d..61436fce8f3659d4b12c3010b82e0d845654c4e9 100644 --- a/crates/project/src/debugger/locators/go.rs +++ b/crates/project/src/debugger/locators/go.rs @@ -117,7 +117,20 @@ impl DapLocator for GoLocator { // HACK: tasks assume that they are run in a shell context, // so the -run regex has escaped specials. Delve correctly // handles escaping, so we undo that here. - if arg.starts_with("\\^") && arg.ends_with("\\$") { + if let Some((left, right)) = arg.split_once("/") + && left.starts_with("\\^") + && left.ends_with("\\$") + && right.starts_with("\\^") + && right.ends_with("\\$") + { + let mut left = left[1..left.len() - 2].to_string(); + left.push('$'); + + let mut right = right[1..right.len() - 2].to_string(); + right.push('$'); + + args.push(format!("{left}/{right}")); + } else if arg.starts_with("\\^") && arg.ends_with("\\$") { let mut arg = arg[1..arg.len() - 2].to_string(); arg.push('$'); args.push(arg); From 42f788185a568b43fa6845968bff59a73cd1fbfd Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Tue, 1 Jul 2025 09:00:20 -0300 Subject: [PATCH 097/107] agent: Use callout for displaying errors instead of toasts (#33680) This PR makes all errors in the agent panel to use the `Callout` component instead of toasts. Reason for that is because the toasts obscured part of the panel's UI, which wasn't ideal. We can also be more expressive here with a background color, which I think helps with parsing the message. Release Notes: - agent: Improved how we display errors in the panel. --- crates/agent_ui/src/agent_panel.rs | 266 ++++++++++++---------------- crates/ui/src/components/callout.rs | 63 ++++--- 2 files changed, 155 insertions(+), 174 deletions(-) diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 978a4a4f2797ef2bcc7e6058281103df9466cc6a..5f58e0bd8d1a6c3c7faed310898f4ee858afb4f8 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -41,7 +41,7 @@ use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer}; use fs::Fs; use gpui::{ Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem, - Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, FontWeight, + Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, Hsla, KeyContext, Pixels, Subscription, Task, UpdateGlobal, WeakEntity, linear_color_stop, linear_gradient, prelude::*, pulsating_between, }; @@ -59,7 +59,7 @@ use theme::ThemeSettings; use time::UtcOffset; use ui::utils::WithRemSize; use ui::{ - Banner, CheckboxWithLabel, ContextMenu, ElevationIndex, KeyBinding, PopoverMenu, + Banner, Callout, CheckboxWithLabel, ContextMenu, ElevationIndex, KeyBinding, PopoverMenu, PopoverMenuHandle, ProgressBar, Tab, Tooltip, Vector, VectorName, prelude::*, }; use util::ResultExt as _; @@ -2689,58 +2689,90 @@ impl AgentPanel { Some(div().px_2().pb_2().child(banner).into_any_element()) } + fn create_copy_button(&self, message: impl Into) -> impl IntoElement { + let message = message.into(); + + IconButton::new("copy", IconName::Copy) + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .tooltip(Tooltip::text("Copy Error Message")) + .on_click(move |_, _, cx| { + cx.write_to_clipboard(ClipboardItem::new_string(message.clone())) + }) + } + + fn dismiss_error_button( + &self, + thread: &Entity, + cx: &mut Context, + ) -> impl IntoElement { + IconButton::new("dismiss", IconName::Close) + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .tooltip(Tooltip::text("Dismiss Error")) + .on_click(cx.listener({ + let thread = thread.clone(); + move |_, _, _, cx| { + thread.update(cx, |this, _cx| { + this.clear_last_error(); + }); + + cx.notify(); + } + })) + } + + fn upgrade_button( + &self, + thread: &Entity, + cx: &mut Context, + ) -> impl IntoElement { + Button::new("upgrade", "Upgrade") + .label_size(LabelSize::Small) + .style(ButtonStyle::Tinted(ui::TintColor::Accent)) + .on_click(cx.listener({ + let thread = thread.clone(); + move |_, _, _, cx| { + thread.update(cx, |this, _cx| { + this.clear_last_error(); + }); + + cx.open_url(&zed_urls::account_url(cx)); + cx.notify(); + } + })) + } + + fn error_callout_bg(&self, cx: &Context) -> Hsla { + cx.theme().status().error.opacity(0.08) + } + fn render_payment_required_error( &self, thread: &Entity, cx: &mut Context, ) -> AnyElement { - const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used."; - - v_flex() - .gap_0p5() - .child( - h_flex() - .gap_1p5() - .items_center() - .child(Icon::new(IconName::XCircle).color(Color::Error)) - .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)), - ) - .child( - div() - .id("error-message") - .max_h_24() - .overflow_y_scroll() - .child(Label::new(ERROR_MESSAGE)), - ) - .child( - h_flex() - .justify_end() - .mt_1() - .gap_1() - .child(self.create_copy_button(ERROR_MESSAGE)) - .child(Button::new("subscribe", "Subscribe").on_click(cx.listener({ - let thread = thread.clone(); - move |_, _, _, cx| { - thread.update(cx, |this, _cx| { - this.clear_last_error(); - }); + const ERROR_MESSAGE: &str = + "You reached your free usage limit. Upgrade to Zed Pro for more prompts."; - cx.open_url(&zed_urls::account_url(cx)); - cx.notify(); - } - }))) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener({ - let thread = thread.clone(); - move |_, _, _, cx| { - thread.update(cx, |this, _cx| { - this.clear_last_error(); - }); + let icon = Icon::new(IconName::XCircle) + .size(IconSize::Small) + .color(Color::Error); - cx.notify(); - } - }))), + div() + .border_t_1() + .border_color(cx.theme().colors().border) + .child( + Callout::new() + .icon(icon) + .title("Free Usage Exceeded") + .description(ERROR_MESSAGE) + .tertiary_action(self.upgrade_button(thread, cx)) + .secondary_action(self.create_copy_button(ERROR_MESSAGE)) + .primary_action(self.dismiss_error_button(thread, cx)) + .bg_color(self.error_callout_bg(cx)), ) - .into_any() + .into_any_element() } fn render_model_request_limit_reached_error( @@ -2750,67 +2782,28 @@ impl AgentPanel { cx: &mut Context, ) -> AnyElement { let error_message = match plan { - Plan::ZedPro => { - "Model request limit reached. Upgrade to usage-based billing for more requests." - } - Plan::ZedProTrial => { - "Model request limit reached. Upgrade to Zed Pro for more requests." - } - Plan::Free => "Model request limit reached. Upgrade to Zed Pro for more requests.", - }; - let call_to_action = match plan { - Plan::ZedPro => "Upgrade to usage-based billing", - Plan::ZedProTrial => "Upgrade to Zed Pro", - Plan::Free => "Upgrade to Zed Pro", + Plan::ZedPro => "Upgrade to usage-based billing for more prompts.", + Plan::ZedProTrial | Plan::Free => "Upgrade to Zed Pro for more prompts.", }; - v_flex() - .gap_0p5() - .child( - h_flex() - .gap_1p5() - .items_center() - .child(Icon::new(IconName::XCircle).color(Color::Error)) - .child(Label::new("Model Request Limit Reached").weight(FontWeight::MEDIUM)), - ) - .child( - div() - .id("error-message") - .max_h_24() - .overflow_y_scroll() - .child(Label::new(error_message)), - ) - .child( - h_flex() - .justify_end() - .mt_1() - .gap_1() - .child(self.create_copy_button(error_message)) - .child( - Button::new("subscribe", call_to_action).on_click(cx.listener({ - let thread = thread.clone(); - move |_, _, _, cx| { - thread.update(cx, |this, _cx| { - this.clear_last_error(); - }); - - cx.open_url(&zed_urls::account_url(cx)); - cx.notify(); - } - })), - ) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener({ - let thread = thread.clone(); - move |_, _, _, cx| { - thread.update(cx, |this, _cx| { - this.clear_last_error(); - }); + let icon = Icon::new(IconName::XCircle) + .size(IconSize::Small) + .color(Color::Error); - cx.notify(); - } - }))), + div() + .border_t_1() + .border_color(cx.theme().colors().border) + .child( + Callout::new() + .icon(icon) + .title("Model Prompt Limit Reached") + .description(error_message) + .tertiary_action(self.upgrade_button(thread, cx)) + .secondary_action(self.create_copy_button(error_message)) + .primary_action(self.dismiss_error_button(thread, cx)) + .bg_color(self.error_callout_bg(cx)), ) - .into_any() + .into_any_element() } fn render_error_message( @@ -2821,40 +2814,24 @@ impl AgentPanel { cx: &mut Context, ) -> AnyElement { let message_with_header = format!("{}\n{}", header, message); - v_flex() - .gap_0p5() - .child( - h_flex() - .gap_1p5() - .items_center() - .child(Icon::new(IconName::XCircle).color(Color::Error)) - .child(Label::new(header).weight(FontWeight::MEDIUM)), - ) - .child( - div() - .id("error-message") - .max_h_32() - .overflow_y_scroll() - .child(Label::new(message.clone())), - ) - .child( - h_flex() - .justify_end() - .mt_1() - .gap_1() - .child(self.create_copy_button(message_with_header)) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener({ - let thread = thread.clone(); - move |_, _, _, cx| { - thread.update(cx, |this, _cx| { - this.clear_last_error(); - }); - cx.notify(); - } - }))), + let icon = Icon::new(IconName::XCircle) + .size(IconSize::Small) + .color(Color::Error); + + div() + .border_t_1() + .border_color(cx.theme().colors().border) + .child( + Callout::new() + .icon(icon) + .title(header) + .description(message.clone()) + .primary_action(self.dismiss_error_button(thread, cx)) + .secondary_action(self.create_copy_button(message_with_header)) + .bg_color(self.error_callout_bg(cx)), ) - .into_any() + .into_any_element() } fn render_prompt_editor( @@ -2999,15 +2976,6 @@ impl AgentPanel { } } - fn create_copy_button(&self, message: impl Into) -> impl IntoElement { - let message = message.into(); - IconButton::new("copy", IconName::Copy) - .on_click(move |_, _, cx| { - cx.write_to_clipboard(ClipboardItem::new_string(message.clone())) - }) - .tooltip(Tooltip::text("Copy Error Message")) - } - fn key_context(&self) -> KeyContext { let mut key_context = KeyContext::new_with_defaults(); key_context.add("AgentPanel"); @@ -3089,18 +3057,9 @@ impl Render for AgentPanel { thread.clone().into_any_element() }) .children(self.render_tool_use_limit_reached(window, cx)) - .child(h_flex().child(message_editor.clone())) .when_some(thread.read(cx).last_error(), |this, last_error| { this.child( div() - .absolute() - .right_3() - .bottom_12() - .max_w_96() - .py_2() - .px_3() - .elevation_2(cx) - .occlude() .child(match last_error { ThreadError::PaymentRequired => { self.render_payment_required_error(thread, cx) @@ -3114,6 +3073,7 @@ impl Render for AgentPanel { .into_any(), ) }) + .child(h_flex().child(message_editor.clone())) .child(self.render_drag_target(cx)), ActiveView::History => parent.child(self.history.clone()), ActiveView::TextThread { diff --git a/crates/ui/src/components/callout.rs b/crates/ui/src/components/callout.rs index b3f3758db6ce331eb17f4fe50e579dc148afb1da..d15fa122ed95e5e9a922c8bc694d1c35d975f9a4 100644 --- a/crates/ui/src/components/callout.rs +++ b/crates/ui/src/components/callout.rs @@ -1,4 +1,4 @@ -use gpui::AnyElement; +use gpui::{AnyElement, Hsla}; use crate::prelude::*; @@ -24,7 +24,9 @@ pub struct Callout { description: Option, primary_action: Option, secondary_action: Option, + tertiary_action: Option, line_height: Option, + bg_color: Option, } impl Callout { @@ -36,7 +38,9 @@ impl Callout { description: None, primary_action: None, secondary_action: None, + tertiary_action: None, line_height: None, + bg_color: None, } } @@ -71,64 +75,81 @@ impl Callout { self } + /// Sets an optional tertiary call-to-action button. + pub fn tertiary_action(mut self, action: impl IntoElement) -> Self { + self.tertiary_action = Some(action.into_any_element()); + self + } + /// Sets a custom line height for the callout content. pub fn line_height(mut self, line_height: Pixels) -> Self { self.line_height = Some(line_height); self } + + /// Sets a custom background color for the callout content. + pub fn bg_color(mut self, color: Hsla) -> Self { + self.bg_color = Some(color); + self + } } impl RenderOnce for Callout { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { let line_height = self.line_height.unwrap_or(window.line_height()); + let bg_color = self + .bg_color + .unwrap_or(cx.theme().colors().panel_background); + let has_actions = self.primary_action.is_some() + || self.secondary_action.is_some() + || self.tertiary_action.is_some(); h_flex() - .w_full() .p_2() .gap_2() .items_start() - .bg(cx.theme().colors().panel_background) + .bg(bg_color) .overflow_x_hidden() .when_some(self.icon, |this, icon| { this.child(h_flex().h(line_height).justify_center().child(icon)) }) .child( v_flex() + .min_w_0() .w_full() .child( h_flex() .h(line_height) .w_full() .gap_1() - .flex_wrap() .justify_between() .when_some(self.title, |this, title| { this.child(h_flex().child(Label::new(title).size(LabelSize::Small))) }) - .when( - self.primary_action.is_some() || self.secondary_action.is_some(), - |this| { - this.child( - h_flex() - .gap_0p5() - .when_some(self.secondary_action, |this, action| { - this.child(action) - }) - .when_some(self.primary_action, |this, action| { - this.child(action) - }), - ) - }, - ), + .when(has_actions, |this| { + this.child( + h_flex() + .gap_0p5() + .when_some(self.tertiary_action, |this, action| { + this.child(action) + }) + .when_some(self.secondary_action, |this, action| { + this.child(action) + }) + .when_some(self.primary_action, |this, action| { + this.child(action) + }), + ) + }), ) .when_some(self.description, |this, description| { this.child( div() .w_full() .flex_1() - .child(description) .text_ui_sm(cx) - .text_color(cx.theme().colors().text_muted), + .text_color(cx.theme().colors().text_muted) + .child(description), ) }), ) From 6e9c6c568465b12d9be13e35f56fa5fef0050433 Mon Sep 17 00:00:00 2001 From: Umesh Yadav <23421535+imumesh18@users.noreply.github.com> Date: Tue, 1 Jul 2025 17:35:08 +0530 Subject: [PATCH 098/107] git_ui: Fix list in git commit message (#33409) Follow up: #32114 Closes #33274 Use the new support for language-specific rewrap_prefixes added in https://github.com/zed-industries/zed/pull/33702. Release Notes: - Fix git commit message line break getting stripped after committing. --------- Signed-off-by: Umesh Yadav --- crates/languages/src/gitcommit/config.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/languages/src/gitcommit/config.toml b/crates/languages/src/gitcommit/config.toml index ae4b836ed66dc7558cf7b033e555dd2dba3b309c..c2421ce00613e5848aacab5d1230ab839c8b1388 100644 --- a/crates/languages/src/gitcommit/config.toml +++ b/crates/languages/src/gitcommit/config.toml @@ -16,3 +16,9 @@ brackets = [ { start = "{", end = "}", close = true, newline = false }, { start = "[", end = "]", close = true, newline = false }, ] +rewrap_prefixes = [ + "[-*+]\\s+", + "\\d+\\.\\s+", + ">\\s*", + "[-*+]\\s+\\[[\\sx]\\]\\s+" +] From 0fe73a99e50a5bdfeb383d2cddb8178af7bb5c62 Mon Sep 17 00:00:00 2001 From: Vitaly Slobodin Date: Tue, 1 Jul 2025 15:12:08 +0200 Subject: [PATCH 099/107] ruby: Add basic documentation about debugging (#33572) Hi, this pull request adds basic documentation about debugging feature available in the Ruby extension. Release Notes: - N/A --- docs/src/languages/ruby.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/src/languages/ruby.md b/docs/src/languages/ruby.md index 4c563ca1f41d98f5c9b32fcafe0fd5f151540ed5..8b3094a3b714b12e310b7b30ff7b5d196e1893d8 100644 --- a/docs/src/languages/ruby.md +++ b/docs/src/languages/ruby.md @@ -340,3 +340,41 @@ Plain minitest does not support running tests by line number, only by name, so w ``` Similar task syntax can be used for other test frameworks such as `quickdraw` or `tldr`. + +## Debugging + +The Ruby extension provides a debug adapter for debugging Ruby code. Zed's name for the adapter (in the UI and `debug.json`) is `rdbg`, and under the hood, it uses the [`debug`](https://github.com/ruby/debug) gem. The extension uses the [same activation logic](#language-server-activation) as the language servers. + +### Examples + +#### Debug a Ruby script + +```jsonc +[ + { + "label": "Debug current file", + "adapter": "rdbg", + "request": "launch", + "script": "$ZED_FILE", + "cwd": "$ZED_WORKTREE_ROOT", + }, +] +``` + +#### Debug Rails server + +```jsonc +[ + { + "label": "Debug Rails server", + "adapter": "rdbg", + "request": "launch", + "command": "$ZED_WORKTREE_ROOT/bin/rails", + "args": ["server"], + "cwd": "$ZED_WORKTREE_ROOT", + "env": { + "RUBY_DEBUG_OPEN": "true", + }, + }, +] +``` From 62e8f4530436ed4b0cb6e793be1ad6e19da7123d Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Tue, 1 Jul 2025 15:17:36 +0200 Subject: [PATCH 100/107] settings: Remove `version` field migration (#33711) This reverts some parts of #33372, as it will break the settings for users running stable and preview at the same time. We can add it back once the changes make it to stable. Release Notes: - N/A --- crates/migrator/src/migrations.rs | 6 - .../src/migrations/m_2025_06_25/settings.rs | 133 ------------------ crates/migrator/src/migrator.rs | 79 ----------- 3 files changed, 218 deletions(-) delete mode 100644 crates/migrator/src/migrations/m_2025_06_25/settings.rs diff --git a/crates/migrator/src/migrations.rs b/crates/migrator/src/migrations.rs index 4e3839358b6e55b0cbec72f92ca12d9b2b30c168..84ef0f0456e65da7dbb604e7733fec8ab1cdd386 100644 --- a/crates/migrator/src/migrations.rs +++ b/crates/migrator/src/migrations.rs @@ -82,12 +82,6 @@ pub(crate) mod m_2025_06_16 { pub(crate) use settings::SETTINGS_PATTERNS; } -pub(crate) mod m_2025_06_25 { - mod settings; - - pub(crate) use settings::SETTINGS_PATTERNS; -} - pub(crate) mod m_2025_06_27 { mod settings; diff --git a/crates/migrator/src/migrations/m_2025_06_25/settings.rs b/crates/migrator/src/migrations/m_2025_06_25/settings.rs deleted file mode 100644 index 5dd6c3093a43b00acff3db6c1e316a3fc6664175..0000000000000000000000000000000000000000 --- a/crates/migrator/src/migrations/m_2025_06_25/settings.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::ops::Range; -use tree_sitter::{Query, QueryMatch}; - -use crate::MigrationPatterns; - -pub const SETTINGS_PATTERNS: MigrationPatterns = &[ - (SETTINGS_VERSION_PATTERN, remove_version_fields), - ( - SETTINGS_NESTED_VERSION_PATTERN, - remove_nested_version_fields, - ), -]; - -const SETTINGS_VERSION_PATTERN: &str = r#"(document - (object - (pair - key: (string (string_content) @key) - value: (object - (pair - key: (string (string_content) @version_key) - value: (_) @version_value - ) @version_pair - ) - ) - ) - (#eq? @key "agent") - (#eq? @version_key "version") -)"#; - -const SETTINGS_NESTED_VERSION_PATTERN: &str = r#"(document - (object - (pair - key: (string (string_content) @language_models) - value: (object - (pair - key: (string (string_content) @provider) - value: (object - (pair - key: (string (string_content) @version_key) - value: (_) @version_value - ) @version_pair - ) - ) - ) - ) - ) - (#eq? @language_models "language_models") - (#match? @provider "^(anthropic|openai)$") - (#eq? @version_key "version") -)"#; - -fn remove_version_fields( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let version_pair_ix = query.capture_index_for_name("version_pair")?; - let version_pair_node = mat.nodes_for_capture_index(version_pair_ix).next()?; - - remove_pair_with_whitespace(contents, version_pair_node) -} - -fn remove_nested_version_fields( - contents: &str, - mat: &QueryMatch, - query: &Query, -) -> Option<(Range, String)> { - let version_pair_ix = query.capture_index_for_name("version_pair")?; - let version_pair_node = mat.nodes_for_capture_index(version_pair_ix).next()?; - - remove_pair_with_whitespace(contents, version_pair_node) -} - -fn remove_pair_with_whitespace( - contents: &str, - pair_node: tree_sitter::Node, -) -> Option<(Range, String)> { - let mut range_to_remove = pair_node.byte_range(); - - // Check if there's a comma after this pair - if let Some(next_sibling) = pair_node.next_sibling() { - if next_sibling.kind() == "," { - range_to_remove.end = next_sibling.end_byte(); - } - } else { - // If no next sibling, check if there's a comma before - if let Some(prev_sibling) = pair_node.prev_sibling() { - if prev_sibling.kind() == "," { - range_to_remove.start = prev_sibling.start_byte(); - } - } - } - - // Include any leading whitespace/newline, including comments - let text_before = &contents[..range_to_remove.start]; - if let Some(last_newline) = text_before.rfind('\n') { - let whitespace_start = last_newline + 1; - let potential_whitespace = &contents[whitespace_start..range_to_remove.start]; - - // Check if it's only whitespace or comments - let mut is_whitespace_or_comment = true; - let mut in_comment = false; - let mut chars = potential_whitespace.chars().peekable(); - - while let Some(ch) = chars.next() { - if in_comment { - if ch == '\n' { - in_comment = false; - } - } else if ch == '/' && chars.peek() == Some(&'/') { - in_comment = true; - chars.next(); // Skip the second '/' - } else if !ch.is_whitespace() { - is_whitespace_or_comment = false; - break; - } - } - - if is_whitespace_or_comment { - range_to_remove.start = whitespace_start; - } - } - - // Also check if we need to include trailing whitespace up to the next line - let text_after = &contents[range_to_remove.end..]; - if let Some(newline_pos) = text_after.find('\n') { - if text_after[..newline_pos].chars().all(|c| c.is_whitespace()) { - range_to_remove.end += newline_pos + 1; - } - } - - Some((range_to_remove, String::new())) -} diff --git a/crates/migrator/src/migrator.rs b/crates/migrator/src/migrator.rs index be32b2734e66ebd621ff858a79eef322468b11ae..06e96a6f865c227579ab2452426b5d8cf46fda7c 100644 --- a/crates/migrator/src/migrator.rs +++ b/crates/migrator/src/migrator.rs @@ -152,10 +152,6 @@ pub fn migrate_settings(text: &str) -> Result> { migrations::m_2025_06_16::SETTINGS_PATTERNS, &SETTINGS_QUERY_2025_06_16, ), - ( - migrations::m_2025_06_25::SETTINGS_PATTERNS, - &SETTINGS_QUERY_2025_06_25, - ), ( migrations::m_2025_06_27::SETTINGS_PATTERNS, &SETTINGS_QUERY_2025_06_27, @@ -262,10 +258,6 @@ define_query!( SETTINGS_QUERY_2025_06_16, migrations::m_2025_06_16::SETTINGS_PATTERNS ); -define_query!( - SETTINGS_QUERY_2025_06_25, - migrations::m_2025_06_25::SETTINGS_PATTERNS -); define_query!( SETTINGS_QUERY_2025_06_27, migrations::m_2025_06_27::SETTINGS_PATTERNS @@ -1089,77 +1081,6 @@ mod tests { ); } - #[test] - fn test_remove_version_fields() { - assert_migrate_settings( - r#"{ - "language_models": { - "anthropic": { - "version": "1", - "api_url": "https://api.anthropic.com" - }, - "openai": { - "version": "1", - "api_url": "https://api.openai.com/v1" - } - }, - "agent": { - "version": "2", - "enabled": true, - "preferred_completion_mode": "normal", - "button": true, - "dock": "right", - "default_width": 640, - "default_height": 320, - "default_model": { - "provider": "zed.dev", - "model": "claude-sonnet-4" - } - } -}"#, - Some( - r#"{ - "language_models": { - "anthropic": { - "api_url": "https://api.anthropic.com" - }, - "openai": { - "api_url": "https://api.openai.com/v1" - } - }, - "agent": { - "enabled": true, - "preferred_completion_mode": "normal", - "button": true, - "dock": "right", - "default_width": 640, - "default_height": 320, - "default_model": { - "provider": "zed.dev", - "model": "claude-sonnet-4" - } - } -}"#, - ), - ); - - // Test that version fields in other contexts are not removed - assert_migrate_settings( - r#"{ - "language_models": { - "other_provider": { - "version": "1", - "api_url": "https://api.example.com" - } - }, - "other_section": { - "version": "1" - } -}"#, - None, - ); - } - #[test] fn test_flatten_context_server_command() { assert_migrate_settings( From 52c42125a7c77834aead079575932bd51824175d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 1 Jul 2025 09:29:43 -0400 Subject: [PATCH 101/107] language_models: Fix casing of `ZedAiConfiguration` (#33712) This PR fixes the casing of the `ZedAiConfiguration` identifier. Release Notes: - N/A --- crates/language_models/src/provider/cloud.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index 5417c329abaa107f9750c8a35a791afcc11b87ae..505caa2e42b27f21e07cda9dc55252dfdde403b1 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -1057,7 +1057,7 @@ fn response_lines( } #[derive(IntoElement, RegisterComponent)] -struct ZedAIConfiguration { +struct ZedAiConfiguration { is_connected: bool, plan: Option, subscription_period: Option<(DateTime, DateTime)>, @@ -1068,7 +1068,7 @@ struct ZedAIConfiguration { sign_in_callback: Arc, } -impl RenderOnce for ZedAIConfiguration { +impl RenderOnce for ZedAiConfiguration { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { const ZED_PRICING_URL: &str = "https://zed.dev/pricing"; @@ -1195,7 +1195,7 @@ impl Render for ConfigurationView { let state = self.state.read(cx); let user_store = state.user_store.read(cx); - ZedAIConfiguration { + ZedAiConfiguration { is_connected: !state.is_signed_out(), plan: user_store.current_plan(), subscription_period: user_store.subscription_period(), @@ -1208,7 +1208,7 @@ impl Render for ConfigurationView { } } -impl Component for ZedAIConfiguration { +impl Component for ZedAiConfiguration { fn scope() -> ComponentScope { ComponentScope::Agent } @@ -1220,7 +1220,7 @@ impl Component for ZedAIConfiguration { eligible_for_trial: bool, has_accepted_terms_of_service: bool, ) -> AnyElement { - ZedAIConfiguration { + ZedAiConfiguration { is_connected, plan, subscription_period: plan From 3041de0cdfa1581ec560be24eeeedbe7f6bed7c7 Mon Sep 17 00:00:00 2001 From: Abdelhakim Qbaich Date: Tue, 1 Jul 2025 10:54:53 -0400 Subject: [PATCH 102/107] Suggest Typst extension for .typ files (#33632) Release Notes: - N/A --- crates/extensions_ui/src/extension_suggest.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/extensions_ui/src/extension_suggest.rs b/crates/extensions_ui/src/extension_suggest.rs index 9b1d1f8cdfc5e3f9201e6513d632c1ec96f15058..ab990881cca337e72361a0a79ce1ded5739595da 100644 --- a/crates/extensions_ui/src/extension_suggest.rs +++ b/crates/extensions_ui/src/extension_suggest.rs @@ -70,6 +70,7 @@ const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[ ("templ", &["templ"]), ("terraform", &["tf", "tfvars", "hcl"]), ("toml", &["Cargo.lock", "toml"]), + ("typst", &["typ"]), ("vue", &["vue"]), ("wgsl", &["wgsl"]), ("wit", &["wit"]), From 351ba5023b7ee8f09712437016ca33ea6c1cbb8e Mon Sep 17 00:00:00 2001 From: G36maid <53391375+G36maid@users.noreply.github.com> Date: Tue, 1 Jul 2025 23:18:34 +0800 Subject: [PATCH 103/107] docs: Add FreeBSD build instructions and current status (#33617) This adds documentation for building Zed on FreeBSD. Notice WebRTC/LiveKit remains unsupported on this platform for now. Follow-up to: - #33162 - #30981 Release Notes: - N/A --------- Co-authored-by: Peter Tripp --- docs/src/development/freebsd.md | 37 ++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/docs/src/development/freebsd.md b/docs/src/development/freebsd.md index 33ff9a56d94c3f3882d7465d82f236b463fac7d6..199e653a65c5cf3d86881c4c038677d60ac2fec5 100644 --- a/docs/src/development/freebsd.md +++ b/docs/src/development/freebsd.md @@ -16,15 +16,36 @@ Clone the [Zed repository](https://github.com/zed-industries/zed). If preferred, you can inspect [`script/freebsd`](https://github.com/zed-industries/zed/blob/main/script/freebsd) and perform the steps manually. ---- +## Building from source -### ⚠️ WebRTC Notice +Once the dependencies are installed, you can build Zed using [Cargo](https://doc.rust-lang.org/cargo/). -Currently, building `webrtc-sys` on FreeBSD fails due to missing upstream support and unavailable prebuilt binaries. -This is actively being worked on. +For a debug build of the editor: -More progress and discussion can be found in [Zed’s GitHub Discussions](https://github.com/zed-industries/zed/discussions/29550). +```sh +cargo run +``` -_Environment: -FreeBSD 14.2-RELEASE -Architecture: amd64 (x86_64)_ +And to run the tests: + +```sh +cargo test --workspace +``` + +In release mode, the primary user interface is the `cli` crate. You can run it in development with: + +```sh +cargo run -p cli +``` + +### WebRTC Notice + +Currently, building `webrtc-sys` on FreeBSD fails due to missing upstream support and unavailable prebuilt binaries. As a result, some collaboration features (audio calls and screensharing) that depend on WebRTC are temporarily disabled. + +See [Issue #15309: FreeBSD Support] and [Discussion #29550: Unofficial FreeBSD port for Zed] for more. + +## Troubleshooting + +### Cargo errors claiming that a dependency is using unstable features + +Try `cargo clean` and `cargo build`. From 31b7786be7edb7f9a12bcc13530a062bfafdee9c Mon Sep 17 00:00:00 2001 From: Alex Shi Date: Tue, 1 Jul 2025 23:43:39 +0800 Subject: [PATCH 104/107] Fix IndentGuides story (#32781) This PR updates the `Model` to `Entity` also fixes the `IndentGuidesStory`. In this [commit](https://github.com/zed-industries/zed/commit/6fca1d2b0ba93cdbc3255657a990828a0f22b199), `Entity` replaces `View`/`Model`. Other than this, I noticed the storybook fails on my MacOS and Ubuntu, see error below ``` thread 'main' panicked at crates/gpui/src/colors.rs:99:15: called `Result::unwrap()` on an `Err` value: no state of type gpui::colors::GlobalColors exists note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace ``` This was resolved by explicitly specifying `GlobalColors` in Storybook. Release Notes: - N/A --- crates/storybook/src/stories.rs | 2 ++ crates/storybook/src/stories/indent_guides.rs | 28 ++++++++----------- crates/storybook/src/story_selector.rs | 2 ++ crates/storybook/src/storybook.rs | 6 +++- crates/ui_input/src/ui_input.rs | 2 +- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/crates/storybook/src/stories.rs b/crates/storybook/src/stories.rs index b824235b00b5d49502734515ca14b853ca3be435..63992d259c7a1cb76a3684f53c55fe255522aced 100644 --- a/crates/storybook/src/stories.rs +++ b/crates/storybook/src/stories.rs @@ -1,6 +1,7 @@ mod auto_height_editor; mod cursor; mod focus; +mod indent_guides; mod kitchen_sink; mod overflow_scroll; mod picker; @@ -12,6 +13,7 @@ mod with_rem_size; pub use auto_height_editor::*; pub use cursor::*; pub use focus::*; +pub use indent_guides::*; pub use kitchen_sink::*; pub use overflow_scroll::*; pub use picker::*; diff --git a/crates/storybook/src/stories/indent_guides.rs b/crates/storybook/src/stories/indent_guides.rs index 068890ae50c524fa9242c53327ed0b929d098363..e83c9ed3837b49c4c701d4434ca1533fef83a5d7 100644 --- a/crates/storybook/src/stories/indent_guides.rs +++ b/crates/storybook/src/stories/indent_guides.rs @@ -1,13 +1,10 @@ -use std::fmt::format; +use std::ops::Range; + +use gpui::{Entity, Render, div, uniform_list}; +use gpui::{prelude::*, *}; +use ui::{AbsoluteLength, Color, DefiniteLength, Label, LabelCommon, px, v_flex}; -use gpui::{ - DefaultColor, DefaultThemeAppearance, Hsla, Render, colors, div, prelude::*, uniform_list, -}; use story::Story; -use strum::IntoEnumIterator; -use ui::{ - AbsoluteLength, ActiveTheme, Color, DefiniteLength, Label, LabelCommon, h_flex, px, v_flex, -}; const LENGTH: usize = 100; @@ -16,7 +13,7 @@ pub struct IndentGuidesStory { } impl IndentGuidesStory { - pub fn model(window: &mut Window, cx: &mut AppContext) -> Model { + pub fn model(_window: &mut Window, cx: &mut App) -> Entity { let mut depths = Vec::new(); depths.push(0); depths.push(1); @@ -33,16 +30,15 @@ impl IndentGuidesStory { } impl Render for IndentGuidesStory { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { Story::container(cx) - .child(Story::title("Indent guides")) + .child(Story::title("Indent guides", cx)) .child( v_flex().size_full().child( uniform_list( - cx.entity().clone(), "some-list", self.depths.len(), - |this, range, cx| { + cx.processor(move |this, range: Range, _window, _cx| { this.depths .iter() .enumerate() @@ -56,7 +52,7 @@ impl Render for IndentGuidesStory { .child(Label::new(format!("Item {}", i)).color(Color::Info)) }) .collect() - }, + }), ) .with_sizing_behavior(gpui::ListSizingBehavior::Infer) .with_decoration(ui::indent_guides( @@ -64,10 +60,10 @@ impl Render for IndentGuidesStory { px(16.), ui::IndentGuideColors { default: Color::Info.color(cx), - hovered: Color::Accent.color(cx), + hover: Color::Accent.color(cx), active: Color::Accent.color(cx), }, - |this, range, cx| { + |this, range, _cx, _context| { this.depths .iter() .skip(range.start) diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index 1de6191367fb821ffeb41f88db0b9c5b275c499a..fd0be97ff6f8e5ef04126a4de60f41d4f31e2bef 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -31,6 +31,7 @@ pub enum ComponentStory { ToggleButton, ViewportUnits, WithRemSize, + IndentGuides, } impl ComponentStory { @@ -60,6 +61,7 @@ impl ComponentStory { Self::ToggleButton => cx.new(|_| ui::ToggleButtonStory).into(), Self::ViewportUnits => cx.new(|_| crate::stories::ViewportUnitsStory).into(), Self::WithRemSize => cx.new(|_| crate::stories::WithRemSizeStory).into(), + Self::IndentGuides => crate::stories::IndentGuidesStory::model(window, cx).into(), } } } diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index c8b055a67e60a07c87696515013b1a6fd5fefb1d..4c5b6272ef1f26d1fd065f76032e327ce59d1e12 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -9,7 +9,9 @@ use std::sync::Arc; use clap::Parser; use dialoguer::FuzzySelect; use gpui::{ - AnyView, App, Bounds, Context, Render, Window, WindowBounds, WindowOptions, div, px, size, + AnyView, App, Bounds, Context, Render, Window, WindowBounds, WindowOptions, + colors::{Colors, GlobalColors}, + div, px, size, }; use log::LevelFilter; use project::Project; @@ -68,6 +70,8 @@ fn main() { gpui::Application::new().with_assets(Assets).run(move |cx| { load_embedded_fonts(cx).unwrap(); + cx.set_global(GlobalColors(Arc::new(Colors::default()))); + let http_client = ReqwestClient::user_agent("zed_storybook").unwrap(); cx.set_http_client(Arc::new(http_client)); diff --git a/crates/ui_input/src/ui_input.rs b/crates/ui_input/src/ui_input.rs index dfecc08dac203d597cb72e11a11acafe965b9571..bd99814cb30534165ad2bfba3911233e2946271b 100644 --- a/crates/ui_input/src/ui_input.rs +++ b/crates/ui_input/src/ui_input.rs @@ -29,7 +29,7 @@ pub struct SingleLineInput { label: Option, /// The placeholder text for the text field. placeholder: SharedString, - /// Exposes the underlying [`Model`] to allow for customizing the editor beyond the provided API. + /// Exposes the underlying [`Entity`] to allow for customizing the editor beyond the provided API. /// /// This likely will only be public in the short term, ideally the API will be expanded to cover necessary use cases. pub editor: Entity, From 274f2e90da29401e978738f45cd7b1a44d0080ee Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Tue, 1 Jul 2025 12:12:46 -0400 Subject: [PATCH 105/107] Add support for more python operators (#33720) Closes: https://github.com/zed-industries/zed/issues/33683 | Before | After | | - | - | | Screenshot 2025-07-01 at 11 42 56 | Screenshot 2025-07-01 at 11 44 45 | Release Notes: - python: Properly highlight additional operators ("&=", "<<=", ">>=", "@=", "^=" and "|=") --- crates/languages/src/python/highlights.scm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/languages/src/python/highlights.scm b/crates/languages/src/python/highlights.scm index 97d5fb52755c7c9e25d1016f085dc9660a081f30..77db9b2f4c17519e966b68c44fede2aa9bc4c29f 100644 --- a/crates/languages/src/python/highlights.scm +++ b/crates/languages/src/python/highlights.scm @@ -226,6 +226,12 @@ ">>" "|" "~" + "&=" + "<<=" + ">>=" + "@=" + "^=" + "|=" ] @operator [ From a11647d07f30a410983615a9af66cc01af9cdc18 Mon Sep 17 00:00:00 2001 From: Julia Ryan Date: Tue, 1 Jul 2025 09:14:25 -0700 Subject: [PATCH 106/107] ci: Block PRs on Nix build failures (#33688) Closes #17458 For now we're being conservative and only running CI on changes to the following files: - `flake.{nix,lock}` - `Cargo.{lock,toml}` - `nix/*` - `.cargo/config.toml` - `rust-toolchain.toml` Release Notes: - N/A --- .github/workflows/ci.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b6e014d25900931a02926ec69d64f211590c99e..39036ef5649e699ffda1636f304629fce6184371 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,7 @@ jobs: run_tests: ${{ steps.filter.outputs.run_tests }} run_license: ${{ steps.filter.outputs.run_license }} run_docs: ${{ steps.filter.outputs.run_docs }} + run_nix: ${{ steps.filter.outputs.run_nix }} runs-on: - ubuntu-latest steps: @@ -69,6 +70,12 @@ jobs: else echo "run_license=false" >> $GITHUB_OUTPUT fi + NIX_REGEX='^(nix/|flake\.|Cargo\.|rust-toolchain.toml|\.cargo/config.toml)' + if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep "$NIX_REGEX") ]]; then + echo "run_nix=true" >> $GITHUB_OUTPUT + else + echo "run_nix=false" >> $GITHUB_OUTPUT + fi migration_checks: name: Check Postgres and Protobuf migrations, mergability @@ -746,7 +753,10 @@ jobs: nix-build: name: Build with Nix uses: ./.github/workflows/nix.yml - if: github.repository_owner == 'zed-industries' && contains(github.event.pull_request.labels.*.name, 'run-nix') + needs: [job_spec] + if: github.repository_owner == 'zed-industries' && + (contains(github.event.pull_request.labels.*.name, 'run-nix') || + needs.job_spec.outputs.run_nix == 'true') secrets: inherit with: flake-output: debug From 0068de03867488207b8022134895b205dfdafd87 Mon Sep 17 00:00:00 2001 From: Julia Ryan Date: Tue, 1 Jul 2025 09:14:59 -0700 Subject: [PATCH 107/107] debugger: Handle the `envFile` setting for Go (#33666) Fixes #32984 Release Notes: - The Go debugger now respects the `envFile` setting. --- Cargo.lock | 10 ++--- Cargo.toml | 2 +- crates/dap_adapters/Cargo.toml | 2 + crates/dap_adapters/src/go.rs | 82 +++++++++++++++++++++++++++++----- crates/eval/Cargo.toml | 2 +- crates/eval/src/eval.rs | 2 +- 6 files changed, 80 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c523c12b135bcf1c2ec7e612318ca35030cd644e..36d08ec201e47fa87db1df64e698654a227e7c80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4147,6 +4147,8 @@ dependencies = [ "async-trait", "collections", "dap", + "dotenvy", + "fs", "futures 0.3.31", "gpui", "json_dotpath", @@ -4675,12 +4677,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - [[package]] name = "dotenvy" version = "0.15.7" @@ -5114,7 +5110,7 @@ dependencies = [ "collections", "debug_adapter_extension", "dirs 4.0.0", - "dotenv", + "dotenvy", "env_logger 0.11.8", "extension", "fs", diff --git a/Cargo.toml b/Cargo.toml index bc686419e59b1ff151e66c861f2c04d417b85698..e7926f025fd4b367d366876ec120b43da487aa38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -449,7 +449,7 @@ dashmap = "6.0" derive_more = "0.99.17" dirs = "4.0" documented = "0.9.1" -dotenv = "0.15.0" +dotenvy = "0.15.0" ec4rs = "1.1" emojis = "0.6.1" env_logger = "0.11" diff --git a/crates/dap_adapters/Cargo.toml b/crates/dap_adapters/Cargo.toml index 07356c20849918f9ee5b8bbd426f672af3d888f2..65544fbb6a1b7565c4fe641058e4e6c725b21016 100644 --- a/crates/dap_adapters/Cargo.toml +++ b/crates/dap_adapters/Cargo.toml @@ -25,7 +25,9 @@ anyhow.workspace = true async-trait.workspace = true collections.workspace = true dap.workspace = true +dotenvy.workspace = true futures.workspace = true +fs.workspace = true gpui.workspace = true json_dotpath.workspace = true language.workspace = true diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index bc3f5007454adee4cfcbc8a3cf09c87ae0100b97..d32f5cbf3426f1b669132e74e389862e7944267b 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -7,13 +7,22 @@ use dap::{ latest_github_release, }, }; - +use fs::Fs; use gpui::{AsyncApp, SharedString}; use language::LanguageName; -use std::{env::consts, ffi::OsStr, path::PathBuf, sync::OnceLock}; +use log::warn; +use serde_json::{Map, Value}; use task::TcpArgumentsTemplate; use util; +use std::{ + env::consts, + ffi::OsStr, + path::{Path, PathBuf}, + str::FromStr, + sync::OnceLock, +}; + use crate::*; #[derive(Default, Debug)] @@ -437,22 +446,34 @@ impl DebugAdapter for GoDebugAdapter { adapter_path.join("dlv").to_string_lossy().to_string() }; - let cwd = task_definition - .config - .get("cwd") - .and_then(|s| s.as_str()) - .map(PathBuf::from) - .unwrap_or_else(|| delegate.worktree_root_path().to_path_buf()); + let cwd = Some( + task_definition + .config + .get("cwd") + .and_then(|s| s.as_str()) + .map(PathBuf::from) + .unwrap_or_else(|| delegate.worktree_root_path().to_path_buf()), + ); let arguments; let command; let connection; let mut configuration = task_definition.config.clone(); + let mut envs = HashMap::default(); + if let Some(configuration) = configuration.as_object_mut() { configuration .entry("cwd") .or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into()); + + handle_envs( + configuration, + &mut envs, + cwd.as_deref(), + delegate.fs().clone(), + ) + .await; } if let Some(connection_options) = &task_definition.tcp_connection { @@ -494,8 +515,8 @@ impl DebugAdapter for GoDebugAdapter { Ok(DebugAdapterBinary { command, arguments, - cwd: Some(cwd), - envs: HashMap::default(), + cwd, + envs, connection, request_args: StartDebuggingRequestArguments { configuration, @@ -504,3 +525,44 @@ impl DebugAdapter for GoDebugAdapter { }) } } + +// delve doesn't do anything with the envFile setting, so we intercept it +async fn handle_envs( + config: &mut Map, + envs: &mut HashMap, + cwd: Option<&Path>, + fs: Arc, +) -> Option<()> { + let env_files = match config.get("envFile")? { + Value::Array(arr) => arr.iter().map(|v| v.as_str()).collect::>(), + Value::String(s) => vec![Some(s.as_str())], + _ => return None, + }; + + let rebase_path = |path: PathBuf| { + if path.is_absolute() { + Some(path) + } else { + cwd.map(|p| p.join(path)) + } + }; + + for path in env_files { + let Some(path) = path + .and_then(|s| PathBuf::from_str(s).ok()) + .and_then(rebase_path) + else { + continue; + }; + + if let Ok(file) = fs.open_sync(&path).await { + envs.extend(dotenvy::from_read_iter(file).filter_map(Result::ok)) + } else { + warn!("While starting Go debug session: failed to read env file {path:?}"); + }; + } + + // remove envFile now that it's been handled + config.remove("entry"); + Some(()) +} diff --git a/crates/eval/Cargo.toml b/crates/eval/Cargo.toml index 7ecba7c1ec91facef139eb0b8e971a12f76361a7..d5db7f71a4593a66ee8218c053109041035428ab 100644 --- a/crates/eval/Cargo.toml +++ b/crates/eval/Cargo.toml @@ -32,7 +32,7 @@ client.workspace = true collections.workspace = true debug_adapter_extension.workspace = true dirs.workspace = true -dotenv.workspace = true +dotenvy.workspace = true env_logger.workspace = true extension.workspace = true fs.workspace = true diff --git a/crates/eval/src/eval.rs b/crates/eval/src/eval.rs index 5e8dd8961c8c3416fa84303eff722c22c31738e6..39121377bba907dbf38983156e1e0f55d187829a 100644 --- a/crates/eval/src/eval.rs +++ b/crates/eval/src/eval.rs @@ -63,7 +63,7 @@ struct Args { } fn main() { - dotenv::from_filename(CARGO_MANIFEST_DIR.join(".env")).ok(); + dotenvy::from_filename(CARGO_MANIFEST_DIR.join(".env")).ok(); env_logger::init();