1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod document_colors;
21mod document_symbols;
22mod editor_settings;
23mod element;
24mod folding_ranges;
25mod git;
26mod highlight_matching_bracket;
27mod hover_links;
28pub mod hover_popover;
29mod indent_guides;
30mod inlays;
31pub mod items;
32mod jsx_tag_auto_close;
33mod linked_editing_ranges;
34mod lsp_ext;
35mod mouse_context_menu;
36pub mod movement;
37mod persistence;
38mod runnables;
39mod rust_analyzer_ext;
40pub mod scroll;
41mod selections_collection;
42pub mod semantic_tokens;
43mod split;
44pub mod split_editor_view;
45
46#[cfg(test)]
47mod code_completion_tests;
48#[cfg(test)]
49mod edit_prediction_tests;
50#[cfg(test)]
51mod editor_block_comment_tests;
52#[cfg(test)]
53mod editor_tests;
54mod signature_help;
55#[cfg(any(test, feature = "test-support"))]
56pub mod test;
57
58pub(crate) use actions::*;
59pub use display_map::{
60 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
61 SemanticTokenHighlight,
62};
63pub use edit_prediction_types::Direction;
64pub use editor_settings::{
65 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
66 EditorSettings, EditorSettingsScrollbarProxy, HideMouseMode, ScrollBeyondLastLine,
67 ScrollbarAxes, SearchSettings, ShowMinimap, ui_scrollbar_settings_from_raw,
68};
69pub use element::{
70 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
71 render_breadcrumb_text,
72};
73pub use git::blame::BlameRenderer;
74pub use hover_popover::hover_markdown_style;
75pub use inlays::Inlay;
76pub use items::MAX_TAB_TITLE_LEN;
77pub use linked_editing_ranges::LinkedEdits;
78pub use lsp::CompletionContext;
79pub use lsp_ext::lsp_tasks;
80pub use multi_buffer::{
81 Anchor, AnchorRangeExt, BufferOffset, ExcerptRange, MBTextSummary, MultiBuffer,
82 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
83 ToPoint,
84};
85pub use split::{SplittableEditor, ToggleSplitDiff};
86pub use split_editor_view::SplitEditorView;
87pub use text::Bias;
88
89use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
90use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
91use anyhow::{Context as _, Result, anyhow, bail};
92use blink_manager::BlinkManager;
93use buffer_diff::DiffHunkStatus;
94use client::{Collaborator, ParticipantIndex, parse_zed_link};
95use clock::ReplicaId;
96use code_context_menus::{
97 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
98 CompletionsMenu, ContextMenuOrigin,
99};
100use collections::{BTreeMap, HashMap, HashSet, VecDeque};
101use convert_case::{Case, Casing};
102use dap::TelemetrySpawnLocation;
103use display_map::*;
104use document_colors::LspColorData;
105use edit_prediction_types::{
106 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
107 EditPredictionGranularity, SuggestionDisplayType,
108};
109use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
110use element::{LineWithInvisibles, PositionMap, layout_line};
111use futures::{
112 FutureExt,
113 future::{self, Shared, join},
114};
115use fuzzy::{StringMatch, StringMatchCandidate};
116use git::blame::{GitBlame, GlobalBlameRenderer};
117use gpui::{
118 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
119 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
120 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
121 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
122 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
123 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
124 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
125 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
126 pulsating_between, px, relative, size,
127};
128use hover_links::{HoverLink, HoveredLinkState, find_file};
129use hover_popover::{HoverState, hide_hover};
130use indent_guides::ActiveIndentGuidesState;
131use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
132use itertools::{Either, Itertools};
133use language::{
134 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
135 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
136 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
137 IndentSize, Language, LanguageAwareStyling, LanguageName, LanguageRegistry, LanguageScope,
138 LocalFile, OffsetRangeExt, OutlineItem, Point, Selection, SelectionGoal, TextObject,
139 TransactionId, TreeSitterOptions, WordsQuery,
140 language_settings::{
141 self, AllLanguageSettings, LanguageSettings, LspInsertMode, RewrapBehavior,
142 WordsCompletionMode, all_language_settings,
143 },
144 point_from_lsp, point_to_lsp, text_diff_with_options,
145};
146use linked_editing_ranges::refresh_linked_ranges;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId,
150};
151use markdown::Markdown;
152use mouse_context_menu::MouseContextMenu;
153use movement::TextLayoutDetails;
154use multi_buffer::{
155 ExcerptBoundaryInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint,
156 MultiBufferRow,
157};
158use parking_lot::Mutex;
159use persistence::EditorDb;
160use project::{
161 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
162 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
163 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
164 ProjectItem, ProjectPath, ProjectTransaction,
165 debugger::{
166 breakpoint_store::{
167 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
168 BreakpointStore, BreakpointStoreEvent,
169 },
170 session::{Session, SessionEvent},
171 },
172 git_store::GitStoreEvent,
173 lsp_store::{
174 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
175 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
176 },
177 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
178};
179use rand::seq::SliceRandom;
180use regex::Regex;
181use rpc::{ErrorCode, ErrorExt, proto::PeerId};
182use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
183use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
184use serde::{Deserialize, Serialize};
185use settings::{
186 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
187 update_settings_file,
188};
189use smallvec::{SmallVec, smallvec};
190use snippet::Snippet;
191use std::{
192 any::{Any, TypeId},
193 borrow::Cow,
194 cell::{OnceCell, RefCell},
195 cmp::{self, Ordering, Reverse},
196 collections::hash_map,
197 iter::{self, Peekable},
198 mem,
199 num::NonZeroU32,
200 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
201 path::{Path, PathBuf},
202 rc::Rc,
203 sync::Arc,
204 time::{Duration, Instant},
205};
206use task::TaskVariables;
207use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
208use theme::{
209 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
210};
211use theme_settings::{ThemeSettings, observe_buffer_font_size_adjustment};
212use ui::{
213 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
214 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
215 utils::WithRemSize,
216};
217use ui_input::ErasedEditor;
218use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
219use workspace::{
220 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
221 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
222 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
223 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
224 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
225 searchable::SearchEvent,
226};
227pub use zed_actions::editor::RevealInFileManager;
228use zed_actions::editor::{MoveDown, MoveUp};
229
230use crate::{
231 code_context_menus::CompletionsMenuSource,
232 editor_settings::MultiCursorModifier,
233 hover_links::{find_url, find_url_from_range},
234 inlays::{
235 InlineValueCache,
236 inlay_hints::{LspInlayHintData, inlay_hint_settings},
237 },
238 runnables::{ResolvedTasks, RunnableData, RunnableTasks},
239 scroll::{ScrollOffset, ScrollPixelOffset},
240 selections_collection::resolve_selections_wrapping_blocks,
241 semantic_tokens::SemanticTokenState,
242 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
243};
244
245pub const FILE_HEADER_HEIGHT: u32 = 2;
246pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
247pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
248const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
249const MAX_LINE_LEN: usize = 1024;
250const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
251const MAX_SELECTION_HISTORY_LEN: usize = 1024;
252pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
253#[doc(hidden)]
254pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
255pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
256
257pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
258pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
259pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
260pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
261
262pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
263pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
264
265pub type RenderDiffHunkControlsFn = Arc<
266 dyn Fn(
267 u32,
268 &DiffHunkStatus,
269 Range<Anchor>,
270 bool,
271 Pixels,
272 &Entity<Editor>,
273 &mut Window,
274 &mut App,
275 ) -> AnyElement,
276>;
277
278enum ReportEditorEvent {
279 Saved { auto_saved: bool },
280 EditorOpened,
281 Closed,
282}
283
284impl ReportEditorEvent {
285 pub fn event_type(&self) -> &'static str {
286 match self {
287 Self::Saved { .. } => "Editor Saved",
288 Self::EditorOpened => "Editor Opened",
289 Self::Closed => "Editor Closed",
290 }
291 }
292}
293
294pub enum ActiveDebugLine {}
295pub enum DebugStackFrameLine {}
296
297pub enum ConflictsOuter {}
298pub enum ConflictsOurs {}
299pub enum ConflictsTheirs {}
300pub enum ConflictsOursMarker {}
301pub enum ConflictsTheirsMarker {}
302
303pub struct HunkAddedColor;
304pub struct HunkRemovedColor;
305
306#[derive(Debug, Copy, Clone, PartialEq, Eq)]
307pub enum Navigated {
308 Yes,
309 No,
310}
311
312impl Navigated {
313 pub fn from_bool(yes: bool) -> Navigated {
314 if yes { Navigated::Yes } else { Navigated::No }
315 }
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
319enum DisplayDiffHunk {
320 Folded {
321 display_row: DisplayRow,
322 },
323 Unfolded {
324 is_created_file: bool,
325 diff_base_byte_range: Range<usize>,
326 display_row_range: Range<DisplayRow>,
327 multi_buffer_range: Range<Anchor>,
328 status: DiffHunkStatus,
329 word_diffs: Vec<Range<MultiBufferOffset>>,
330 },
331}
332
333pub enum HideMouseCursorOrigin {
334 TypingAction,
335 MovementAction,
336}
337
338pub fn init(cx: &mut App) {
339 cx.set_global(GlobalBlameRenderer(Arc::new(())));
340 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
341
342 workspace::register_project_item::<Editor>(cx);
343 workspace::FollowableViewRegistry::register::<Editor>(cx);
344 workspace::register_serializable_item::<Editor>(cx);
345
346 cx.observe_new(
347 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
348 workspace.register_action(Editor::new_file);
349 workspace.register_action(Editor::new_file_split);
350 workspace.register_action(Editor::new_file_vertical);
351 workspace.register_action(Editor::new_file_horizontal);
352 workspace.register_action(Editor::cancel_language_server_work);
353 workspace.register_action(Editor::toggle_focus);
354 },
355 )
356 .detach();
357
358 cx.on_action(move |_: &workspace::NewFile, cx| {
359 let app_state = workspace::AppState::global(cx);
360 workspace::open_new(
361 Default::default(),
362 app_state,
363 cx,
364 |workspace, window, cx| Editor::new_file(workspace, &Default::default(), window, cx),
365 )
366 .detach_and_log_err(cx);
367 })
368 .on_action(move |_: &workspace::NewWindow, cx| {
369 let app_state = workspace::AppState::global(cx);
370 workspace::open_new(
371 Default::default(),
372 app_state,
373 cx,
374 |workspace, window, cx| {
375 cx.activate(true);
376 Editor::new_file(workspace, &Default::default(), window, cx)
377 },
378 )
379 .detach_and_log_err(cx);
380 });
381 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
382 Arc::new(ErasedEditorImpl(
383 cx.new(|cx| Editor::single_line(window, cx)),
384 )) as Arc<dyn ErasedEditor>
385 });
386 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
387}
388
389pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
390 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
391}
392
393pub trait DiagnosticRenderer {
394 fn render_group(
395 &self,
396 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
397 buffer_id: BufferId,
398 snapshot: EditorSnapshot,
399 editor: WeakEntity<Editor>,
400 language_registry: Option<Arc<LanguageRegistry>>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 language_registry: Option<Arc<LanguageRegistry>>,
410 cx: &mut App,
411 ) -> Option<Entity<markdown::Markdown>>;
412
413 fn open_link(
414 &self,
415 editor: &mut Editor,
416 link: SharedString,
417 window: &mut Window,
418 cx: &mut Context<Editor>,
419 );
420}
421
422pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
423
424impl GlobalDiagnosticRenderer {
425 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
426 cx.try_global::<Self>().map(|g| g.0.clone())
427 }
428}
429
430impl gpui::Global for GlobalDiagnosticRenderer {}
431pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
432 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
433}
434
435pub struct SearchWithinRange;
436
437trait InvalidationRegion {
438 fn ranges(&self) -> &[Range<Anchor>];
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum SelectPhase {
443 Begin {
444 position: DisplayPoint,
445 add: bool,
446 click_count: usize,
447 },
448 BeginColumnar {
449 position: DisplayPoint,
450 reset: bool,
451 mode: ColumnarMode,
452 goal_column: u32,
453 },
454 Extend {
455 position: DisplayPoint,
456 click_count: usize,
457 },
458 Update {
459 position: DisplayPoint,
460 goal_column: u32,
461 scroll_delta: gpui::Point<f32>,
462 },
463 End,
464}
465
466#[derive(Clone, Debug, PartialEq)]
467pub enum ColumnarMode {
468 FromMouse,
469 FromSelection,
470}
471
472#[derive(Clone, Debug)]
473pub enum SelectMode {
474 Character,
475 Word(Range<Anchor>),
476 Line(Range<Anchor>),
477 All,
478}
479
480#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
481pub enum SizingBehavior {
482 /// The editor will layout itself using `size_full` and will include the vertical
483 /// scroll margin as requested by user settings.
484 #[default]
485 Default,
486 /// The editor will layout itself using `size_full`, but will not have any
487 /// vertical overscroll.
488 ExcludeOverscrollMargin,
489 /// The editor will request a vertical size according to its content and will be
490 /// layouted without a vertical scroll margin.
491 SizeByContent,
492}
493
494#[derive(Clone, PartialEq, Eq, Debug)]
495pub enum EditorMode {
496 SingleLine,
497 AutoHeight {
498 min_lines: usize,
499 max_lines: Option<usize>,
500 },
501 Full {
502 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
503 scale_ui_elements_with_buffer_font_size: bool,
504 /// When set to `true`, the editor will render a background for the active line.
505 show_active_line_background: bool,
506 /// Determines the sizing behavior for this editor
507 sizing_behavior: SizingBehavior,
508 },
509 Minimap {
510 parent: WeakEntity<Editor>,
511 },
512}
513
514impl EditorMode {
515 pub fn full() -> Self {
516 Self::Full {
517 scale_ui_elements_with_buffer_font_size: true,
518 show_active_line_background: true,
519 sizing_behavior: SizingBehavior::Default,
520 }
521 }
522
523 #[inline]
524 pub fn is_full(&self) -> bool {
525 matches!(self, Self::Full { .. })
526 }
527
528 #[inline]
529 pub fn is_single_line(&self) -> bool {
530 matches!(self, Self::SingleLine { .. })
531 }
532
533 #[inline]
534 fn is_minimap(&self) -> bool {
535 matches!(self, Self::Minimap { .. })
536 }
537}
538
539#[derive(Copy, Clone, Debug)]
540pub enum SoftWrap {
541 /// Prefer not to wrap at all.
542 ///
543 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
544 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
545 GitDiff,
546 /// Prefer a single line generally, unless an overly long line is encountered.
547 None,
548 /// Soft wrap lines that exceed the editor width.
549 EditorWidth,
550 /// Soft wrap lines at the preferred line length.
551 Column(u32),
552 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
553 Bounded(u32),
554}
555
556#[derive(Clone)]
557pub struct EditorStyle {
558 pub background: Hsla,
559 pub border: Hsla,
560 pub local_player: PlayerColor,
561 pub text: TextStyle,
562 pub scrollbar_width: Pixels,
563 pub syntax: Arc<SyntaxTheme>,
564 pub status: StatusColors,
565 pub inlay_hints_style: HighlightStyle,
566 pub edit_prediction_styles: EditPredictionStyles,
567 pub unnecessary_code_fade: f32,
568 pub show_underlines: bool,
569}
570
571impl Default for EditorStyle {
572 fn default() -> Self {
573 Self {
574 background: Hsla::default(),
575 border: Hsla::default(),
576 local_player: PlayerColor::default(),
577 text: TextStyle::default(),
578 scrollbar_width: Pixels::default(),
579 syntax: Default::default(),
580 // HACK: Status colors don't have a real default.
581 // We should look into removing the status colors from the editor
582 // style and retrieve them directly from the theme.
583 status: StatusColors::dark(),
584 inlay_hints_style: HighlightStyle::default(),
585 edit_prediction_styles: EditPredictionStyles {
586 insertion: HighlightStyle::default(),
587 whitespace: HighlightStyle::default(),
588 },
589 unnecessary_code_fade: Default::default(),
590 show_underlines: true,
591 }
592 }
593}
594
595pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
596 let show_background = AllLanguageSettings::get_global(cx)
597 .defaults
598 .inlay_hints
599 .show_background;
600
601 let mut style = cx
602 .theme()
603 .syntax()
604 .style_for_name("hint")
605 .unwrap_or_default();
606
607 if style.color.is_none() {
608 style.color = Some(cx.theme().status().hint);
609 }
610
611 if !show_background {
612 style.background_color = None;
613 return style;
614 }
615
616 if style.background_color.is_none() {
617 style.background_color = Some(cx.theme().status().hint_background);
618 }
619
620 style
621}
622
623pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
624 EditPredictionStyles {
625 insertion: HighlightStyle {
626 color: Some(cx.theme().status().predictive),
627 ..HighlightStyle::default()
628 },
629 whitespace: HighlightStyle {
630 background_color: Some(cx.theme().status().created_background),
631 ..HighlightStyle::default()
632 },
633 }
634}
635
636type CompletionId = usize;
637
638pub(crate) enum EditDisplayMode {
639 TabAccept,
640 DiffPopover,
641 Inline,
642}
643
644enum EditPrediction {
645 Edit {
646 // TODO could be a language::Anchor?
647 edits: Vec<(Range<Anchor>, Arc<str>)>,
648 /// Predicted cursor position as (anchor, offset_from_anchor).
649 /// The anchor is in multibuffer coordinates; after applying edits,
650 /// resolve the anchor and add the offset to get the final cursor position.
651 cursor_position: Option<(Anchor, usize)>,
652 edit_preview: Option<EditPreview>,
653 display_mode: EditDisplayMode,
654 snapshot: BufferSnapshot,
655 },
656 /// Move to a specific location in the active editor
657 MoveWithin {
658 target: Anchor,
659 snapshot: BufferSnapshot,
660 },
661 /// Move to a specific location in a different editor (not the active one)
662 MoveOutside {
663 target: language::Anchor,
664 snapshot: BufferSnapshot,
665 },
666}
667
668struct EditPredictionState {
669 inlay_ids: Vec<InlayId>,
670 completion: EditPrediction,
671 completion_id: Option<SharedString>,
672 invalidation_range: Option<Range<Anchor>>,
673}
674
675enum EditPredictionSettings {
676 Disabled,
677 Enabled {
678 show_in_menu: bool,
679 preview_requires_modifier: bool,
680 },
681}
682
683#[derive(Debug, Clone)]
684struct InlineDiagnostic {
685 message: SharedString,
686 group_id: usize,
687 is_primary: bool,
688 start: Point,
689 severity: lsp::DiagnosticSeverity,
690}
691
692pub enum MenuEditPredictionsPolicy {
693 Never,
694 ByProvider,
695}
696
697pub enum EditPredictionPreview {
698 /// Modifier is not pressed
699 Inactive { released_too_fast: bool },
700 /// Modifier pressed
701 Active {
702 since: Instant,
703 previous_scroll_position: Option<SharedScrollAnchor>,
704 },
705}
706
707#[derive(Copy, Clone, Eq, PartialEq)]
708enum EditPredictionKeybindSurface {
709 Inline,
710 CursorPopoverCompact,
711 CursorPopoverExpanded,
712}
713
714#[derive(Copy, Clone, Eq, PartialEq, Debug)]
715enum EditPredictionKeybindAction {
716 Accept,
717 Preview,
718}
719
720struct EditPredictionKeybindDisplay {
721 #[cfg(test)]
722 accept_keystroke: Option<gpui::KeybindingKeystroke>,
723 #[cfg(test)]
724 preview_keystroke: Option<gpui::KeybindingKeystroke>,
725 displayed_keystroke: Option<gpui::KeybindingKeystroke>,
726 action: EditPredictionKeybindAction,
727 missing_accept_keystroke: bool,
728 show_hold_label: bool,
729}
730
731impl EditPredictionPreview {
732 pub fn released_too_fast(&self) -> bool {
733 match self {
734 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
735 EditPredictionPreview::Active { .. } => false,
736 }
737 }
738
739 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
740 if let EditPredictionPreview::Active {
741 previous_scroll_position,
742 ..
743 } = self
744 {
745 *previous_scroll_position = scroll_position;
746 }
747 }
748}
749
750pub struct ContextMenuOptions {
751 pub min_entries_visible: usize,
752 pub max_entries_visible: usize,
753 pub placement: Option<ContextMenuPlacement>,
754}
755
756#[derive(Debug, Clone, PartialEq, Eq)]
757pub enum ContextMenuPlacement {
758 Above,
759 Below,
760}
761
762#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
763struct EditorActionId(usize);
764
765impl EditorActionId {
766 pub fn post_inc(&mut self) -> Self {
767 let answer = self.0;
768
769 *self = Self(answer + 1);
770
771 Self(answer)
772 }
773}
774
775// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
776// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
777
778type BackgroundHighlight = (
779 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
780 Arc<[Range<Anchor>]>,
781);
782type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
783
784#[derive(Default)]
785struct ScrollbarMarkerState {
786 scrollbar_size: Size<Pixels>,
787 dirty: bool,
788 markers: Arc<[PaintQuad]>,
789 pending_refresh: Option<Task<Result<()>>>,
790}
791
792impl ScrollbarMarkerState {
793 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
794 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
795 }
796}
797
798#[derive(Clone, Copy, PartialEq, Eq)]
799pub enum MinimapVisibility {
800 Disabled,
801 Enabled {
802 /// The configuration currently present in the users settings.
803 setting_configuration: bool,
804 /// Whether to override the currently set visibility from the users setting.
805 toggle_override: bool,
806 },
807}
808
809impl MinimapVisibility {
810 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
811 if mode.is_full() {
812 Self::Enabled {
813 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
814 toggle_override: false,
815 }
816 } else {
817 Self::Disabled
818 }
819 }
820
821 fn hidden(&self) -> Self {
822 match *self {
823 Self::Enabled {
824 setting_configuration,
825 ..
826 } => Self::Enabled {
827 setting_configuration,
828 toggle_override: setting_configuration,
829 },
830 Self::Disabled => Self::Disabled,
831 }
832 }
833
834 fn disabled(&self) -> bool {
835 matches!(*self, Self::Disabled)
836 }
837
838 fn settings_visibility(&self) -> bool {
839 match *self {
840 Self::Enabled {
841 setting_configuration,
842 ..
843 } => setting_configuration,
844 _ => false,
845 }
846 }
847
848 fn visible(&self) -> bool {
849 match *self {
850 Self::Enabled {
851 setting_configuration,
852 toggle_override,
853 } => setting_configuration ^ toggle_override,
854 _ => false,
855 }
856 }
857
858 fn toggle_visibility(&self) -> Self {
859 match *self {
860 Self::Enabled {
861 toggle_override,
862 setting_configuration,
863 } => Self::Enabled {
864 setting_configuration,
865 toggle_override: !toggle_override,
866 },
867 Self::Disabled => Self::Disabled,
868 }
869 }
870}
871
872#[derive(Debug, Clone, Copy, PartialEq, Eq)]
873pub enum BufferSerialization {
874 All,
875 NonDirtyBuffers,
876}
877
878impl BufferSerialization {
879 fn new(restore_unsaved_buffers: bool) -> Self {
880 if restore_unsaved_buffers {
881 Self::All
882 } else {
883 Self::NonDirtyBuffers
884 }
885 }
886}
887
888/// Addons allow storing per-editor state in other crates (e.g. Vim)
889pub trait Addon: 'static {
890 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
891
892 fn render_buffer_header_controls(
893 &self,
894 _: &ExcerptBoundaryInfo,
895 _: &language::BufferSnapshot,
896 _: &Window,
897 _: &App,
898 ) -> Option<AnyElement> {
899 None
900 }
901
902 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
903 None
904 }
905
906 fn to_any(&self) -> &dyn std::any::Any;
907
908 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
909 None
910 }
911}
912
913struct ChangeLocation {
914 current: Option<Vec<Anchor>>,
915 original: Vec<Anchor>,
916}
917impl ChangeLocation {
918 fn locations(&self) -> &[Anchor] {
919 self.current.as_ref().unwrap_or(&self.original)
920 }
921}
922
923/// A set of caret positions, registered when the editor was edited.
924pub struct ChangeList {
925 changes: Vec<ChangeLocation>,
926 /// Currently "selected" change.
927 position: Option<usize>,
928}
929
930impl ChangeList {
931 pub fn new() -> Self {
932 Self {
933 changes: Vec::new(),
934 position: None,
935 }
936 }
937
938 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
939 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
940 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
941 if self.changes.is_empty() {
942 return None;
943 }
944
945 let prev = self.position.unwrap_or(self.changes.len());
946 let next = if direction == Direction::Prev {
947 prev.saturating_sub(count)
948 } else {
949 (prev + count).min(self.changes.len() - 1)
950 };
951 self.position = Some(next);
952 self.changes.get(next).map(|change| change.locations())
953 }
954
955 /// Adds a new change to the list, resetting the change list position.
956 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
957 self.position.take();
958 if let Some(last) = self.changes.last_mut()
959 && group
960 {
961 last.current = Some(new_positions)
962 } else {
963 self.changes.push(ChangeLocation {
964 original: new_positions,
965 current: None,
966 });
967 }
968 }
969
970 pub fn last(&self) -> Option<&[Anchor]> {
971 self.changes.last().map(|change| change.locations())
972 }
973
974 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
975 self.changes.last().map(|change| change.original.as_slice())
976 }
977
978 pub fn invert_last_group(&mut self) {
979 if let Some(last) = self.changes.last_mut()
980 && let Some(current) = last.current.as_mut()
981 {
982 mem::swap(&mut last.original, current);
983 }
984 }
985}
986
987#[derive(Clone)]
988struct InlineBlamePopoverState {
989 scroll_handle: ScrollHandle,
990 commit_message: Option<ParsedCommitMessage>,
991 markdown: Entity<Markdown>,
992}
993
994struct InlineBlamePopover {
995 position: gpui::Point<Pixels>,
996 hide_task: Option<Task<()>>,
997 popover_bounds: Option<Bounds<Pixels>>,
998 popover_state: InlineBlamePopoverState,
999 keyboard_grace: bool,
1000}
1001
1002enum SelectionDragState {
1003 /// State when no drag related activity is detected.
1004 None,
1005 /// State when the mouse is down on a selection that is about to be dragged.
1006 ReadyToDrag {
1007 selection: Selection<Anchor>,
1008 click_position: gpui::Point<Pixels>,
1009 mouse_down_time: Instant,
1010 },
1011 /// State when the mouse is dragging the selection in the editor.
1012 Dragging {
1013 selection: Selection<Anchor>,
1014 drop_cursor: Selection<Anchor>,
1015 hide_drop_cursor: bool,
1016 },
1017}
1018
1019enum ColumnarSelectionState {
1020 FromMouse {
1021 selection_tail: Anchor,
1022 display_point: Option<DisplayPoint>,
1023 },
1024 FromSelection {
1025 selection_tail: Anchor,
1026 },
1027}
1028
1029/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1030/// a breakpoint on them.
1031#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1032struct PhantomBreakpointIndicator {
1033 display_row: DisplayRow,
1034 /// There's a small debounce between hovering over the line and showing the indicator.
1035 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1036 is_active: bool,
1037 collides_with_existing_breakpoint: bool,
1038}
1039
1040/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1041/// in diff view mode.
1042#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1043pub(crate) struct PhantomDiffReviewIndicator {
1044 /// The starting anchor of the selection (or the only row if not dragging).
1045 pub start: Anchor,
1046 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1047 pub end: Anchor,
1048 /// There's a small debounce between hovering over the line and showing the indicator.
1049 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1050 pub is_active: bool,
1051}
1052
1053#[derive(Clone, Debug)]
1054pub(crate) struct DiffReviewDragState {
1055 pub start_anchor: Anchor,
1056 pub current_anchor: Anchor,
1057}
1058
1059impl DiffReviewDragState {
1060 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1061 let start = self.start_anchor.to_display_point(snapshot).row();
1062 let current = self.current_anchor.to_display_point(snapshot).row();
1063
1064 (start..=current).sorted()
1065 }
1066}
1067
1068/// Identifies a specific hunk in the diff buffer.
1069/// Used as a key to group comments by their location.
1070#[derive(Clone, Debug)]
1071pub struct DiffHunkKey {
1072 /// The file path (relative to worktree) this hunk belongs to.
1073 pub file_path: Arc<util::rel_path::RelPath>,
1074 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1075 pub hunk_start_anchor: Anchor,
1076}
1077
1078/// A review comment stored locally before being sent to the Agent panel.
1079#[derive(Clone)]
1080pub struct StoredReviewComment {
1081 /// Unique identifier for this comment (for edit/delete operations).
1082 pub id: usize,
1083 /// The comment text entered by the user.
1084 pub comment: String,
1085 /// Anchors for the code range being reviewed.
1086 pub range: Range<Anchor>,
1087 /// Timestamp when the comment was created (for chronological ordering).
1088 pub created_at: Instant,
1089 /// Whether this comment is currently being edited inline.
1090 pub is_editing: bool,
1091}
1092
1093impl StoredReviewComment {
1094 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1095 Self {
1096 id,
1097 comment,
1098 range: anchor_range,
1099 created_at: Instant::now(),
1100 is_editing: false,
1101 }
1102 }
1103}
1104
1105/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1106pub(crate) struct DiffReviewOverlay {
1107 pub anchor_range: Range<Anchor>,
1108 /// The block ID for the overlay.
1109 pub block_id: CustomBlockId,
1110 /// The editor entity for the review input.
1111 pub prompt_editor: Entity<Editor>,
1112 /// The hunk key this overlay belongs to.
1113 pub hunk_key: DiffHunkKey,
1114 /// Whether the comments section is expanded.
1115 pub comments_expanded: bool,
1116 /// Editors for comments currently being edited inline.
1117 /// Key: comment ID, Value: Editor entity for inline editing.
1118 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1119 /// Subscriptions for inline edit editors' action handlers.
1120 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1121 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1122 /// The current user's avatar URI for display in comment rows.
1123 pub user_avatar_uri: Option<SharedUri>,
1124 /// Subscription to keep the action handler alive.
1125 _subscription: Subscription,
1126}
1127
1128/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1129///
1130/// See the [module level documentation](self) for more information.
1131pub struct Editor {
1132 focus_handle: FocusHandle,
1133 last_focused_descendant: Option<WeakFocusHandle>,
1134 /// The text buffer being edited
1135 buffer: Entity<MultiBuffer>,
1136 /// Map of how text in the buffer should be displayed.
1137 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1138 pub display_map: Entity<DisplayMap>,
1139 placeholder_display_map: Option<Entity<DisplayMap>>,
1140 pub selections: SelectionsCollection,
1141 pub scroll_manager: ScrollManager,
1142 /// When inline assist editors are linked, they all render cursors because
1143 /// typing enters text into each of them, even the ones that aren't focused.
1144 pub(crate) show_cursor_when_unfocused: bool,
1145 columnar_selection_state: Option<ColumnarSelectionState>,
1146 add_selections_state: Option<AddSelectionsState>,
1147 select_next_state: Option<SelectNextState>,
1148 select_prev_state: Option<SelectNextState>,
1149 selection_history: SelectionHistory,
1150 defer_selection_effects: bool,
1151 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1152 autoclose_regions: Vec<AutocloseRegion>,
1153 snippet_stack: InvalidationStack<SnippetState>,
1154 select_syntax_node_history: SelectSyntaxNodeHistory,
1155 ime_transaction: Option<TransactionId>,
1156 pub diagnostics_max_severity: DiagnosticSeverity,
1157 active_diagnostics: ActiveDiagnostic,
1158 show_inline_diagnostics: bool,
1159 inline_diagnostics_update: Task<()>,
1160 inline_diagnostics_enabled: bool,
1161 diagnostics_enabled: bool,
1162 word_completions_enabled: bool,
1163 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1164 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1165 hard_wrap: Option<usize>,
1166 project: Option<Entity<Project>>,
1167 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1168 completion_provider: Option<Rc<dyn CompletionProvider>>,
1169 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1170 blink_manager: Entity<BlinkManager>,
1171 show_cursor_names: bool,
1172 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1173 pub show_local_selections: bool,
1174 mode: EditorMode,
1175 show_breadcrumbs: bool,
1176 show_gutter: bool,
1177 show_scrollbars: ScrollbarAxes,
1178 minimap_visibility: MinimapVisibility,
1179 offset_content: bool,
1180 disable_expand_excerpt_buttons: bool,
1181 delegate_expand_excerpts: bool,
1182 delegate_stage_and_restore: bool,
1183 delegate_open_excerpts: bool,
1184 enable_lsp_data: bool,
1185 enable_runnables: bool,
1186 enable_mouse_wheel_zoom: bool,
1187 show_line_numbers: Option<bool>,
1188 use_relative_line_numbers: Option<bool>,
1189 show_git_diff_gutter: Option<bool>,
1190 show_code_actions: Option<bool>,
1191 show_runnables: Option<bool>,
1192 show_breakpoints: Option<bool>,
1193 show_diff_review_button: bool,
1194 show_wrap_guides: Option<bool>,
1195 show_indent_guides: Option<bool>,
1196 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1197 highlight_order: usize,
1198 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1199 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1200 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1201 scrollbar_marker_state: ScrollbarMarkerState,
1202 active_indent_guides_state: ActiveIndentGuidesState,
1203 nav_history: Option<ItemNavHistory>,
1204 context_menu: RefCell<Option<CodeContextMenu>>,
1205 context_menu_options: Option<ContextMenuOptions>,
1206 mouse_context_menu: Option<MouseContextMenu>,
1207 completion_tasks: Vec<(CompletionId, Task<()>)>,
1208 inline_blame_popover: Option<InlineBlamePopover>,
1209 inline_blame_popover_show_task: Option<Task<()>>,
1210 signature_help_state: SignatureHelpState,
1211 auto_signature_help: Option<bool>,
1212 find_all_references_task_sources: Vec<Anchor>,
1213 next_completion_id: CompletionId,
1214 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1215 code_actions_task: Option<Task<Result<()>>>,
1216 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1217 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1218 debounced_selection_highlight_complete: bool,
1219 document_highlights_task: Option<Task<()>>,
1220 linked_editing_range_task: Option<Task<Option<()>>>,
1221 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1222 pending_rename: Option<RenameState>,
1223 searchable: bool,
1224 cursor_shape: CursorShape,
1225 /// Whether the cursor is offset one character to the left when something is
1226 /// selected (needed for vim visual mode)
1227 cursor_offset_on_selection: bool,
1228 current_line_highlight: Option<CurrentLineHighlight>,
1229 /// Whether to collapse search match ranges to just their start position.
1230 /// When true, navigating to a match positions the cursor at the match
1231 /// without selecting the matched text.
1232 collapse_matches: bool,
1233 autoindent_mode: Option<AutoindentMode>,
1234 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1235 input_enabled: bool,
1236 expects_character_input: bool,
1237 use_modal_editing: bool,
1238 read_only: bool,
1239 leader_id: Option<CollaboratorId>,
1240 remote_id: Option<ViewId>,
1241 pub hover_state: HoverState,
1242 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1243 prev_pressure_stage: Option<PressureStage>,
1244 gutter_hovered: bool,
1245 hovered_link_state: Option<HoveredLinkState>,
1246 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1247 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1248 active_edit_prediction: Option<EditPredictionState>,
1249 /// Used to prevent flickering as the user types while the menu is open
1250 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1251 edit_prediction_settings: EditPredictionSettings,
1252 edit_predictions_hidden_for_vim_mode: bool,
1253 show_edit_predictions_override: Option<bool>,
1254 show_completions_on_input_override: Option<bool>,
1255 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1256 edit_prediction_preview: EditPredictionPreview,
1257 in_leading_whitespace: bool,
1258 next_inlay_id: usize,
1259 next_color_inlay_id: usize,
1260 _subscriptions: Vec<Subscription>,
1261 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1262 gutter_dimensions: GutterDimensions,
1263 style: Option<EditorStyle>,
1264 text_style_refinement: Option<TextStyleRefinement>,
1265 next_editor_action_id: EditorActionId,
1266 editor_actions: Rc<
1267 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1268 >,
1269 use_autoclose: bool,
1270 use_auto_surround: bool,
1271 use_selection_highlight: bool,
1272 auto_replace_emoji_shortcode: bool,
1273 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1274 show_git_blame_gutter: bool,
1275 show_git_blame_inline: bool,
1276 show_git_blame_inline_delay_task: Option<Task<()>>,
1277 git_blame_inline_enabled: bool,
1278 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1279 buffer_serialization: Option<BufferSerialization>,
1280 show_selection_menu: Option<bool>,
1281 blame: Option<Entity<GitBlame>>,
1282 blame_subscription: Option<Subscription>,
1283 custom_context_menu: Option<
1284 Box<
1285 dyn 'static
1286 + Fn(
1287 &mut Self,
1288 DisplayPoint,
1289 &mut Window,
1290 &mut Context<Self>,
1291 ) -> Option<Entity<ui::ContextMenu>>,
1292 >,
1293 >,
1294 last_bounds: Option<Bounds<Pixels>>,
1295 last_position_map: Option<Rc<PositionMap>>,
1296 expect_bounds_change: Option<Bounds<Pixels>>,
1297 runnables: RunnableData,
1298 breakpoint_store: Option<Entity<BreakpointStore>>,
1299 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1300 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1301 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1302 /// Active diff review overlays. Multiple overlays can be open simultaneously
1303 /// when hunks have comments stored.
1304 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1305 /// Stored review comments grouped by hunk.
1306 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1307 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1308 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1309 /// Counter for generating unique comment IDs.
1310 next_review_comment_id: usize,
1311 hovered_diff_hunk_row: Option<DisplayRow>,
1312 pull_diagnostics_task: Task<()>,
1313 in_project_search: bool,
1314 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1315 breadcrumb_header: Option<String>,
1316 focused_block: Option<FocusedBlock>,
1317 next_scroll_position: NextScrollCursorCenterTopBottom,
1318 addons: HashMap<TypeId, Box<dyn Addon>>,
1319 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1320 load_diff_task: Option<Shared<Task<()>>>,
1321 /// Whether we are temporarily displaying a diff other than git's
1322 temporary_diff_override: bool,
1323 selection_mark_mode: bool,
1324 toggle_fold_multiple_buffers: Task<()>,
1325 _scroll_cursor_center_top_bottom_task: Task<()>,
1326 serialize_selections: Task<()>,
1327 serialize_folds: Task<()>,
1328 mouse_cursor_hidden: bool,
1329 minimap: Option<Entity<Self>>,
1330 hide_mouse_mode: HideMouseMode,
1331 pub change_list: ChangeList,
1332 inline_value_cache: InlineValueCache,
1333 number_deleted_lines: bool,
1334
1335 selection_drag_state: SelectionDragState,
1336 colors: Option<LspColorData>,
1337 post_scroll_update: Task<()>,
1338 refresh_colors_task: Task<()>,
1339 use_document_folding_ranges: bool,
1340 refresh_folding_ranges_task: Task<()>,
1341 inlay_hints: Option<LspInlayHintData>,
1342 folding_newlines: Task<()>,
1343 select_next_is_case_sensitive: Option<bool>,
1344 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1345 on_local_selections_changed:
1346 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1347 suppress_selection_callback: bool,
1348 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1349 accent_data: Option<AccentData>,
1350 bracket_fetched_tree_sitter_chunks: HashMap<Range<text::Anchor>, HashSet<Range<BufferRow>>>,
1351 semantic_token_state: SemanticTokenState,
1352 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1353 refresh_document_symbols_task: Shared<Task<()>>,
1354 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1355 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1356 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1357 sticky_headers_task: Task<()>,
1358 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1359 pub(crate) colorize_brackets_task: Task<()>,
1360}
1361
1362#[derive(Debug, PartialEq)]
1363struct AccentData {
1364 colors: AccentColors,
1365 overrides: Vec<SharedString>,
1366}
1367
1368fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1369 if debounce_ms > 0 {
1370 Some(Duration::from_millis(debounce_ms))
1371 } else {
1372 None
1373 }
1374}
1375
1376#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1377enum NextScrollCursorCenterTopBottom {
1378 #[default]
1379 Center,
1380 Top,
1381 Bottom,
1382}
1383
1384impl NextScrollCursorCenterTopBottom {
1385 fn next(&self) -> Self {
1386 match self {
1387 Self::Center => Self::Top,
1388 Self::Top => Self::Bottom,
1389 Self::Bottom => Self::Center,
1390 }
1391 }
1392}
1393
1394#[derive(Clone)]
1395pub struct EditorSnapshot {
1396 pub mode: EditorMode,
1397 show_gutter: bool,
1398 offset_content: bool,
1399 show_line_numbers: Option<bool>,
1400 number_deleted_lines: bool,
1401 show_git_diff_gutter: Option<bool>,
1402 show_code_actions: Option<bool>,
1403 show_runnables: Option<bool>,
1404 show_breakpoints: Option<bool>,
1405 git_blame_gutter_max_author_length: Option<usize>,
1406 pub display_snapshot: DisplaySnapshot,
1407 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1408 is_focused: bool,
1409 scroll_anchor: SharedScrollAnchor,
1410 ongoing_scroll: OngoingScroll,
1411 current_line_highlight: CurrentLineHighlight,
1412 gutter_hovered: bool,
1413 semantic_tokens_enabled: bool,
1414}
1415
1416#[derive(Default, Debug, Clone, Copy)]
1417pub struct GutterDimensions {
1418 pub left_padding: Pixels,
1419 pub right_padding: Pixels,
1420 pub width: Pixels,
1421 pub margin: Pixels,
1422 pub git_blame_entries_width: Option<Pixels>,
1423}
1424
1425impl GutterDimensions {
1426 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1427 Self {
1428 margin: Self::default_gutter_margin(font_id, font_size, cx),
1429 ..Default::default()
1430 }
1431 }
1432
1433 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1434 -cx.text_system().descent(font_id, font_size)
1435 }
1436 /// The full width of the space taken up by the gutter.
1437 pub fn full_width(&self) -> Pixels {
1438 self.margin + self.width
1439 }
1440
1441 /// The width of the space reserved for the fold indicators,
1442 /// use alongside 'justify_end' and `gutter_width` to
1443 /// right align content with the line numbers
1444 pub fn fold_area_width(&self) -> Pixels {
1445 self.margin + self.right_padding
1446 }
1447}
1448
1449struct CharacterDimensions {
1450 em_width: Pixels,
1451 em_advance: Pixels,
1452 line_height: Pixels,
1453}
1454
1455#[derive(Debug)]
1456pub struct RemoteSelection {
1457 pub replica_id: ReplicaId,
1458 pub selection: Selection<Anchor>,
1459 pub cursor_shape: CursorShape,
1460 pub collaborator_id: CollaboratorId,
1461 pub line_mode: bool,
1462 pub user_name: Option<SharedString>,
1463 pub color: PlayerColor,
1464}
1465
1466#[derive(Clone, Debug)]
1467struct SelectionHistoryEntry {
1468 selections: Arc<[Selection<Anchor>]>,
1469 select_next_state: Option<SelectNextState>,
1470 select_prev_state: Option<SelectNextState>,
1471 add_selections_state: Option<AddSelectionsState>,
1472}
1473
1474#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1475enum SelectionHistoryMode {
1476 #[default]
1477 Normal,
1478 Undoing,
1479 Redoing,
1480 Skipping,
1481}
1482
1483#[derive(Clone, PartialEq, Eq, Hash)]
1484struct HoveredCursor {
1485 replica_id: ReplicaId,
1486 selection_id: usize,
1487}
1488
1489#[derive(Debug)]
1490/// SelectionEffects controls the side-effects of updating the selection.
1491///
1492/// The default behaviour does "what you mostly want":
1493/// - it pushes to the nav history if the cursor moved by >10 lines
1494/// - it re-triggers completion requests
1495/// - it scrolls to fit
1496///
1497/// You might want to modify these behaviours. For example when doing a "jump"
1498/// like go to definition, we always want to add to nav history; but when scrolling
1499/// in vim mode we never do.
1500///
1501/// Similarly, you might want to disable scrolling if you don't want the viewport to
1502/// move.
1503#[derive(Clone)]
1504pub struct SelectionEffects {
1505 nav_history: Option<bool>,
1506 completions: bool,
1507 scroll: Option<Autoscroll>,
1508}
1509
1510impl Default for SelectionEffects {
1511 fn default() -> Self {
1512 Self {
1513 nav_history: None,
1514 completions: true,
1515 scroll: Some(Autoscroll::fit()),
1516 }
1517 }
1518}
1519impl SelectionEffects {
1520 pub fn scroll(scroll: Autoscroll) -> Self {
1521 Self {
1522 scroll: Some(scroll),
1523 ..Default::default()
1524 }
1525 }
1526
1527 pub fn no_scroll() -> Self {
1528 Self {
1529 scroll: None,
1530 ..Default::default()
1531 }
1532 }
1533
1534 pub fn completions(self, completions: bool) -> Self {
1535 Self {
1536 completions,
1537 ..self
1538 }
1539 }
1540
1541 pub fn nav_history(self, nav_history: bool) -> Self {
1542 Self {
1543 nav_history: Some(nav_history),
1544 ..self
1545 }
1546 }
1547}
1548
1549struct DeferredSelectionEffectsState {
1550 changed: bool,
1551 effects: SelectionEffects,
1552 old_cursor_position: Anchor,
1553 history_entry: SelectionHistoryEntry,
1554}
1555
1556#[derive(Default)]
1557struct SelectionHistory {
1558 #[allow(clippy::type_complexity)]
1559 selections_by_transaction:
1560 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1561 mode: SelectionHistoryMode,
1562 undo_stack: VecDeque<SelectionHistoryEntry>,
1563 redo_stack: VecDeque<SelectionHistoryEntry>,
1564}
1565
1566impl SelectionHistory {
1567 #[track_caller]
1568 fn insert_transaction(
1569 &mut self,
1570 transaction_id: TransactionId,
1571 selections: Arc<[Selection<Anchor>]>,
1572 ) {
1573 if selections.is_empty() {
1574 log::error!(
1575 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1576 std::panic::Location::caller()
1577 );
1578 return;
1579 }
1580 self.selections_by_transaction
1581 .insert(transaction_id, (selections, None));
1582 }
1583
1584 #[allow(clippy::type_complexity)]
1585 fn transaction(
1586 &self,
1587 transaction_id: TransactionId,
1588 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1589 self.selections_by_transaction.get(&transaction_id)
1590 }
1591
1592 #[allow(clippy::type_complexity)]
1593 fn transaction_mut(
1594 &mut self,
1595 transaction_id: TransactionId,
1596 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1597 self.selections_by_transaction.get_mut(&transaction_id)
1598 }
1599
1600 fn push(&mut self, entry: SelectionHistoryEntry) {
1601 if !entry.selections.is_empty() {
1602 match self.mode {
1603 SelectionHistoryMode::Normal => {
1604 self.push_undo(entry);
1605 self.redo_stack.clear();
1606 }
1607 SelectionHistoryMode::Undoing => self.push_redo(entry),
1608 SelectionHistoryMode::Redoing => self.push_undo(entry),
1609 SelectionHistoryMode::Skipping => {}
1610 }
1611 }
1612 }
1613
1614 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1615 if self
1616 .undo_stack
1617 .back()
1618 .is_none_or(|e| e.selections != entry.selections)
1619 {
1620 self.undo_stack.push_back(entry);
1621 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1622 self.undo_stack.pop_front();
1623 }
1624 }
1625 }
1626
1627 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1628 if self
1629 .redo_stack
1630 .back()
1631 .is_none_or(|e| e.selections != entry.selections)
1632 {
1633 self.redo_stack.push_back(entry);
1634 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1635 self.redo_stack.pop_front();
1636 }
1637 }
1638 }
1639}
1640
1641#[derive(Clone, Copy)]
1642pub struct RowHighlightOptions {
1643 pub autoscroll: bool,
1644 pub include_gutter: bool,
1645}
1646
1647impl Default for RowHighlightOptions {
1648 fn default() -> Self {
1649 Self {
1650 autoscroll: Default::default(),
1651 include_gutter: true,
1652 }
1653 }
1654}
1655
1656struct RowHighlight {
1657 index: usize,
1658 range: Range<Anchor>,
1659 color: Hsla,
1660 options: RowHighlightOptions,
1661 type_id: TypeId,
1662}
1663
1664#[derive(Clone, Debug)]
1665struct AddSelectionsState {
1666 groups: Vec<AddSelectionsGroup>,
1667}
1668
1669#[derive(Clone, Debug)]
1670struct AddSelectionsGroup {
1671 above: bool,
1672 stack: Vec<usize>,
1673}
1674
1675#[derive(Clone)]
1676struct SelectNextState {
1677 query: AhoCorasick,
1678 wordwise: bool,
1679 done: bool,
1680}
1681
1682impl std::fmt::Debug for SelectNextState {
1683 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1684 f.debug_struct(std::any::type_name::<Self>())
1685 .field("wordwise", &self.wordwise)
1686 .field("done", &self.done)
1687 .finish()
1688 }
1689}
1690
1691#[derive(Debug)]
1692struct AutocloseRegion {
1693 selection_id: usize,
1694 range: Range<Anchor>,
1695 pair: BracketPair,
1696}
1697
1698#[derive(Debug)]
1699struct SnippetState {
1700 ranges: Vec<Vec<Range<Anchor>>>,
1701 active_index: usize,
1702 choices: Vec<Option<Vec<String>>>,
1703}
1704
1705#[doc(hidden)]
1706pub struct RenameState {
1707 pub range: Range<Anchor>,
1708 pub old_name: Arc<str>,
1709 pub editor: Entity<Editor>,
1710 block_id: CustomBlockId,
1711}
1712
1713struct InvalidationStack<T>(Vec<T>);
1714
1715struct RegisteredEditPredictionDelegate {
1716 provider: Arc<dyn EditPredictionDelegateHandle>,
1717 _subscription: Subscription,
1718}
1719
1720#[derive(Debug, PartialEq, Eq)]
1721pub struct ActiveDiagnosticGroup {
1722 pub active_range: Range<Anchor>,
1723 pub active_message: String,
1724 pub group_id: usize,
1725 pub blocks: HashSet<CustomBlockId>,
1726}
1727
1728#[derive(Debug, PartialEq, Eq)]
1729
1730pub(crate) enum ActiveDiagnostic {
1731 None,
1732 All,
1733 Group(ActiveDiagnosticGroup),
1734}
1735
1736#[derive(Serialize, Deserialize, Clone, Debug)]
1737pub struct ClipboardSelection {
1738 /// The number of bytes in this selection.
1739 pub len: usize,
1740 /// Whether this was a full-line selection.
1741 pub is_entire_line: bool,
1742 /// The indentation of the first line when this content was originally copied.
1743 pub first_line_indent: u32,
1744 #[serde(default)]
1745 pub file_path: Option<PathBuf>,
1746 #[serde(default)]
1747 pub line_range: Option<RangeInclusive<u32>>,
1748}
1749
1750impl ClipboardSelection {
1751 pub fn for_buffer(
1752 len: usize,
1753 is_entire_line: bool,
1754 range: Range<Point>,
1755 buffer: &MultiBufferSnapshot,
1756 project: Option<&Entity<Project>>,
1757 cx: &App,
1758 ) -> Self {
1759 let first_line_indent = buffer
1760 .indent_size_for_line(MultiBufferRow(range.start.row))
1761 .len;
1762
1763 let file_path = util::maybe!({
1764 let project = project?.read(cx);
1765 let file = buffer.file_at(range.start)?;
1766 let project_path = ProjectPath {
1767 worktree_id: file.worktree_id(cx),
1768 path: file.path().clone(),
1769 };
1770 project.absolute_path(&project_path, cx)
1771 });
1772
1773 let line_range = if file_path.is_some() {
1774 buffer
1775 .range_to_buffer_range(range)
1776 .map(|(_, buffer_range)| buffer_range.start.row..=buffer_range.end.row)
1777 } else {
1778 None
1779 };
1780
1781 Self {
1782 len,
1783 is_entire_line,
1784 first_line_indent,
1785 file_path,
1786 line_range,
1787 }
1788 }
1789}
1790
1791// selections, scroll behavior, was newest selection reversed
1792type SelectSyntaxNodeHistoryState = (
1793 Box<[Selection<Anchor>]>,
1794 SelectSyntaxNodeScrollBehavior,
1795 bool,
1796);
1797
1798#[derive(Default)]
1799struct SelectSyntaxNodeHistory {
1800 stack: Vec<SelectSyntaxNodeHistoryState>,
1801 // disable temporarily to allow changing selections without losing the stack
1802 pub disable_clearing: bool,
1803}
1804
1805impl SelectSyntaxNodeHistory {
1806 pub fn try_clear(&mut self) {
1807 if !self.disable_clearing {
1808 self.stack.clear();
1809 }
1810 }
1811
1812 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1813 self.stack.push(selection);
1814 }
1815
1816 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1817 self.stack.pop()
1818 }
1819}
1820
1821enum SelectSyntaxNodeScrollBehavior {
1822 CursorTop,
1823 FitSelection,
1824 CursorBottom,
1825}
1826
1827#[derive(Debug, Clone, Copy)]
1828pub(crate) struct NavigationData {
1829 cursor_anchor: Anchor,
1830 cursor_position: Point,
1831 scroll_anchor: ScrollAnchor,
1832 scroll_top_row: u32,
1833}
1834
1835#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1836pub enum GotoDefinitionKind {
1837 Symbol,
1838 Declaration,
1839 Type,
1840 Implementation,
1841}
1842
1843pub enum FormatTarget {
1844 Buffers(HashSet<Entity<Buffer>>),
1845 Ranges(Vec<Range<MultiBufferPoint>>),
1846}
1847
1848pub(crate) struct FocusedBlock {
1849 id: BlockId,
1850 focus_handle: WeakFocusHandle,
1851}
1852
1853#[derive(Clone, Debug)]
1854pub enum JumpData {
1855 MultiBufferRow {
1856 row: MultiBufferRow,
1857 line_offset_from_top: u32,
1858 },
1859 MultiBufferPoint {
1860 anchor: language::Anchor,
1861 position: Point,
1862 line_offset_from_top: u32,
1863 },
1864}
1865
1866pub enum MultibufferSelectionMode {
1867 First,
1868 All,
1869}
1870
1871#[derive(Clone, Copy, Debug, Default)]
1872pub struct RewrapOptions {
1873 pub override_language_settings: bool,
1874 pub preserve_existing_whitespace: bool,
1875 pub line_length: Option<usize>,
1876}
1877
1878impl Editor {
1879 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1880 let buffer = cx.new(|cx| Buffer::local("", cx));
1881 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1882 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1883 }
1884
1885 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1886 let buffer = cx.new(|cx| Buffer::local("", cx));
1887 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1888 Self::new(EditorMode::full(), buffer, None, window, cx)
1889 }
1890
1891 pub fn auto_height(
1892 min_lines: usize,
1893 max_lines: usize,
1894 window: &mut Window,
1895 cx: &mut Context<Self>,
1896 ) -> Self {
1897 let buffer = cx.new(|cx| Buffer::local("", cx));
1898 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1899 Self::new(
1900 EditorMode::AutoHeight {
1901 min_lines,
1902 max_lines: Some(max_lines),
1903 },
1904 buffer,
1905 None,
1906 window,
1907 cx,
1908 )
1909 }
1910
1911 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1912 /// The editor grows as tall as needed to fit its content.
1913 pub fn auto_height_unbounded(
1914 min_lines: usize,
1915 window: &mut Window,
1916 cx: &mut Context<Self>,
1917 ) -> Self {
1918 let buffer = cx.new(|cx| Buffer::local("", cx));
1919 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1920 Self::new(
1921 EditorMode::AutoHeight {
1922 min_lines,
1923 max_lines: None,
1924 },
1925 buffer,
1926 None,
1927 window,
1928 cx,
1929 )
1930 }
1931
1932 pub fn for_buffer(
1933 buffer: Entity<Buffer>,
1934 project: Option<Entity<Project>>,
1935 window: &mut Window,
1936 cx: &mut Context<Self>,
1937 ) -> Self {
1938 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1939 Self::new(EditorMode::full(), buffer, project, window, cx)
1940 }
1941
1942 pub fn for_multibuffer(
1943 buffer: Entity<MultiBuffer>,
1944 project: Option<Entity<Project>>,
1945 window: &mut Window,
1946 cx: &mut Context<Self>,
1947 ) -> Self {
1948 Self::new(EditorMode::full(), buffer, project, window, cx)
1949 }
1950
1951 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1952 let mut clone = Self::new(
1953 self.mode.clone(),
1954 self.buffer.clone(),
1955 self.project.clone(),
1956 window,
1957 cx,
1958 );
1959 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1960 let snapshot = display_map.snapshot(cx);
1961 clone.display_map.update(cx, |display_map, cx| {
1962 display_map.set_state(&snapshot, cx);
1963 });
1964 snapshot
1965 });
1966 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1967 clone.folds_did_change(cx);
1968 clone.selections.clone_state(&self.selections);
1969 clone
1970 .scroll_manager
1971 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1972 clone.searchable = self.searchable;
1973 clone.read_only = self.read_only;
1974 clone.buffers_with_disabled_indent_guides =
1975 self.buffers_with_disabled_indent_guides.clone();
1976 clone.enable_mouse_wheel_zoom = self.enable_mouse_wheel_zoom;
1977 clone.enable_lsp_data = self.enable_lsp_data;
1978 clone.enable_runnables = self.enable_runnables;
1979 clone
1980 }
1981
1982 pub fn new(
1983 mode: EditorMode,
1984 buffer: Entity<MultiBuffer>,
1985 project: Option<Entity<Project>>,
1986 window: &mut Window,
1987 cx: &mut Context<Self>,
1988 ) -> Self {
1989 Editor::new_internal(mode, buffer, project, None, window, cx)
1990 }
1991
1992 pub fn refresh_sticky_headers(
1993 &mut self,
1994 display_snapshot: &DisplaySnapshot,
1995 cx: &mut Context<Editor>,
1996 ) {
1997 if !self.mode.is_full() {
1998 return;
1999 }
2000 let multi_buffer = display_snapshot.buffer_snapshot().clone();
2001 let scroll_anchor = self
2002 .scroll_manager
2003 .native_anchor(display_snapshot, cx)
2004 .anchor;
2005 let Some(buffer_snapshot) = multi_buffer.as_singleton() else {
2006 return;
2007 };
2008
2009 let buffer = buffer_snapshot.clone();
2010 let Some((buffer_visible_start, _)) = multi_buffer.anchor_to_buffer_anchor(scroll_anchor)
2011 else {
2012 return;
2013 };
2014 let buffer_visible_start = buffer_visible_start.to_point(&buffer);
2015 let max_row = buffer.max_point().row;
2016 let start_row = buffer_visible_start.row.min(max_row);
2017 let end_row = (buffer_visible_start.row + 10).min(max_row);
2018
2019 let syntax = self.style(cx).syntax.clone();
2020 let background_task = cx.background_spawn(async move {
2021 buffer
2022 .outline_items_containing(
2023 Point::new(start_row, 0)..Point::new(end_row, 0),
2024 true,
2025 Some(syntax.as_ref()),
2026 )
2027 .into_iter()
2028 .filter_map(|outline_item| {
2029 Some(OutlineItem {
2030 depth: outline_item.depth,
2031 range: multi_buffer
2032 .buffer_anchor_range_to_anchor_range(outline_item.range)?,
2033 source_range_for_text: multi_buffer.buffer_anchor_range_to_anchor_range(
2034 outline_item.source_range_for_text,
2035 )?,
2036 text: outline_item.text,
2037 highlight_ranges: outline_item.highlight_ranges,
2038 name_ranges: outline_item.name_ranges,
2039 body_range: outline_item.body_range.and_then(|range| {
2040 multi_buffer.buffer_anchor_range_to_anchor_range(range)
2041 }),
2042 annotation_range: outline_item.annotation_range.and_then(|range| {
2043 multi_buffer.buffer_anchor_range_to_anchor_range(range)
2044 }),
2045 })
2046 })
2047 .collect()
2048 });
2049 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2050 let sticky_headers = background_task.await;
2051 this.update(cx, |this, cx| {
2052 this.sticky_headers = Some(sticky_headers);
2053 cx.notify();
2054 })
2055 .ok();
2056 });
2057 }
2058
2059 fn new_internal(
2060 mode: EditorMode,
2061 multi_buffer: Entity<MultiBuffer>,
2062 project: Option<Entity<Project>>,
2063 display_map: Option<Entity<DisplayMap>>,
2064 window: &mut Window,
2065 cx: &mut Context<Self>,
2066 ) -> Self {
2067 debug_assert!(
2068 display_map.is_none() || mode.is_minimap(),
2069 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2070 );
2071
2072 let full_mode = mode.is_full();
2073 let is_minimap = mode.is_minimap();
2074 let diagnostics_max_severity = if full_mode {
2075 EditorSettings::get_global(cx)
2076 .diagnostics_max_severity
2077 .unwrap_or(DiagnosticSeverity::Hint)
2078 } else {
2079 DiagnosticSeverity::Off
2080 };
2081 let style = window.text_style();
2082 let font_size = style.font_size.to_pixels(window.rem_size());
2083 let editor = cx.entity().downgrade();
2084 let fold_placeholder = FoldPlaceholder {
2085 constrain_width: false,
2086 render: Arc::new(move |fold_id, fold_range, cx| {
2087 let editor = editor.clone();
2088 FoldPlaceholder::fold_element(fold_id, cx)
2089 .cursor_pointer()
2090 .child("⋯")
2091 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2092 .on_click(move |_, _window, cx| {
2093 editor
2094 .update(cx, |editor, cx| {
2095 editor.unfold_ranges(
2096 &[fold_range.start..fold_range.end],
2097 true,
2098 false,
2099 cx,
2100 );
2101 cx.stop_propagation();
2102 })
2103 .ok();
2104 })
2105 .into_any()
2106 }),
2107 merge_adjacent: true,
2108 ..FoldPlaceholder::default()
2109 };
2110 let display_map = display_map.unwrap_or_else(|| {
2111 cx.new(|cx| {
2112 DisplayMap::new(
2113 multi_buffer.clone(),
2114 style.font(),
2115 font_size,
2116 None,
2117 FILE_HEADER_HEIGHT,
2118 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2119 fold_placeholder,
2120 diagnostics_max_severity,
2121 cx,
2122 )
2123 })
2124 });
2125
2126 let selections = SelectionsCollection::new();
2127
2128 let blink_manager = cx.new(|cx| {
2129 let mut blink_manager = BlinkManager::new(
2130 CURSOR_BLINK_INTERVAL,
2131 |cx| EditorSettings::get_global(cx).cursor_blink,
2132 cx,
2133 );
2134 if is_minimap {
2135 blink_manager.disable(cx);
2136 }
2137 blink_manager
2138 });
2139
2140 let soft_wrap_mode_override =
2141 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2142
2143 let mut project_subscriptions = Vec::new();
2144 if full_mode && let Some(project) = project.as_ref() {
2145 project_subscriptions.push(cx.subscribe_in(
2146 project,
2147 window,
2148 |editor, _, event, window, cx| match event {
2149 project::Event::RefreshCodeLens => {
2150 // we always query lens with actions, without storing them, always refreshing them
2151 }
2152 project::Event::RefreshInlayHints {
2153 server_id,
2154 request_id,
2155 } => {
2156 editor.refresh_inlay_hints(
2157 InlayHintRefreshReason::RefreshRequested {
2158 server_id: *server_id,
2159 request_id: *request_id,
2160 },
2161 cx,
2162 );
2163 }
2164 project::Event::RefreshSemanticTokens {
2165 server_id,
2166 request_id,
2167 } => {
2168 editor.refresh_semantic_tokens(
2169 None,
2170 Some(RefreshForServer {
2171 server_id: *server_id,
2172 request_id: *request_id,
2173 }),
2174 cx,
2175 );
2176 }
2177 project::Event::LanguageServerRemoved(_) => {
2178 editor.registered_buffers.clear();
2179 editor.register_visible_buffers(cx);
2180 editor.invalidate_semantic_tokens(None);
2181 editor.refresh_runnables(None, window, cx);
2182 editor.update_lsp_data(None, window, cx);
2183 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2184 }
2185 project::Event::SnippetEdit(id, snippet_edits) => {
2186 // todo(lw): Non singletons
2187 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2188 let snapshot = buffer.read(cx).snapshot();
2189 let focus_handle = editor.focus_handle(cx);
2190 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2191 for (range, snippet) in snippet_edits {
2192 let buffer_range =
2193 language::range_from_lsp(*range).to_offset(&snapshot);
2194 editor
2195 .insert_snippet(
2196 &[MultiBufferOffset(buffer_range.start)
2197 ..MultiBufferOffset(buffer_range.end)],
2198 snippet.clone(),
2199 window,
2200 cx,
2201 )
2202 .ok();
2203 }
2204 }
2205 }
2206 }
2207 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2208 let buffer_id = *buffer_id;
2209 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2210 editor.register_buffer(buffer_id, cx);
2211 editor.refresh_runnables(Some(buffer_id), window, cx);
2212 editor.update_lsp_data(Some(buffer_id), window, cx);
2213 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2214 refresh_linked_ranges(editor, window, cx);
2215 editor.refresh_code_actions(window, cx);
2216 editor.refresh_document_highlights(cx);
2217 }
2218 }
2219
2220 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2221 let Some(workspace) = editor.workspace() else {
2222 return;
2223 };
2224 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2225 else {
2226 return;
2227 };
2228
2229 if active_editor.entity_id() == cx.entity_id() {
2230 let entity_id = cx.entity_id();
2231 workspace.update(cx, |this, cx| {
2232 this.panes_mut()
2233 .iter_mut()
2234 .filter(|pane| pane.entity_id() != entity_id)
2235 .for_each(|p| {
2236 p.update(cx, |pane, _| {
2237 pane.nav_history_mut().rename_item(
2238 entity_id,
2239 project_path.clone(),
2240 abs_path.clone().into(),
2241 );
2242 })
2243 });
2244 });
2245
2246 Self::open_transaction_for_hidden_buffers(
2247 workspace,
2248 transaction.clone(),
2249 "Rename".to_string(),
2250 window,
2251 cx,
2252 );
2253 }
2254 }
2255
2256 project::Event::WorkspaceEditApplied(transaction) => {
2257 let Some(workspace) = editor.workspace() else {
2258 return;
2259 };
2260 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2261 else {
2262 return;
2263 };
2264
2265 if active_editor.entity_id() == cx.entity_id() {
2266 Self::open_transaction_for_hidden_buffers(
2267 workspace,
2268 transaction.clone(),
2269 "LSP Edit".to_string(),
2270 window,
2271 cx,
2272 );
2273 }
2274 }
2275
2276 _ => {}
2277 },
2278 ));
2279 if let Some(task_inventory) = project
2280 .read(cx)
2281 .task_store()
2282 .read(cx)
2283 .task_inventory()
2284 .cloned()
2285 {
2286 project_subscriptions.push(cx.observe_in(
2287 &task_inventory,
2288 window,
2289 |editor, _, window, cx| {
2290 editor.refresh_runnables(None, window, cx);
2291 },
2292 ));
2293 };
2294
2295 project_subscriptions.push(cx.subscribe_in(
2296 &project.read(cx).breakpoint_store(),
2297 window,
2298 |editor, _, event, window, cx| match event {
2299 BreakpointStoreEvent::ClearDebugLines => {
2300 editor.clear_row_highlights::<ActiveDebugLine>();
2301 editor.refresh_inline_values(cx);
2302 }
2303 BreakpointStoreEvent::SetDebugLine => {
2304 if editor.go_to_active_debug_line(window, cx) {
2305 cx.stop_propagation();
2306 }
2307
2308 editor.refresh_inline_values(cx);
2309 }
2310 _ => {}
2311 },
2312 ));
2313 let git_store = project.read(cx).git_store().clone();
2314 let project = project.clone();
2315 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2316 if let GitStoreEvent::RepositoryAdded = event {
2317 this.load_diff_task = Some(
2318 update_uncommitted_diff_for_buffer(
2319 cx.entity(),
2320 &project,
2321 this.buffer.read(cx).all_buffers(),
2322 this.buffer.clone(),
2323 cx,
2324 )
2325 .shared(),
2326 );
2327 }
2328 }));
2329 }
2330
2331 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2332
2333 let inlay_hint_settings =
2334 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2335 let focus_handle = cx.focus_handle();
2336 if !is_minimap {
2337 cx.on_focus(&focus_handle, window, Self::handle_focus)
2338 .detach();
2339 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2340 .detach();
2341 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2342 .detach();
2343 cx.on_blur(&focus_handle, window, Self::handle_blur)
2344 .detach();
2345 cx.observe_pending_input(window, Self::observe_pending_input)
2346 .detach();
2347 }
2348
2349 let show_indent_guides =
2350 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2351 Some(false)
2352 } else {
2353 None
2354 };
2355
2356 let breakpoint_store = match (&mode, project.as_ref()) {
2357 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2358 _ => None,
2359 };
2360
2361 let mut code_action_providers = Vec::new();
2362 let mut load_uncommitted_diff = None;
2363 if let Some(project) = project.clone() {
2364 load_uncommitted_diff = Some(
2365 update_uncommitted_diff_for_buffer(
2366 cx.entity(),
2367 &project,
2368 multi_buffer.read(cx).all_buffers(),
2369 multi_buffer.clone(),
2370 cx,
2371 )
2372 .shared(),
2373 );
2374 code_action_providers.push(Rc::new(project) as Rc<_>);
2375 }
2376
2377 let mut editor = Self {
2378 focus_handle,
2379 show_cursor_when_unfocused: false,
2380 last_focused_descendant: None,
2381 buffer: multi_buffer.clone(),
2382 display_map: display_map.clone(),
2383 placeholder_display_map: None,
2384 selections,
2385 scroll_manager: ScrollManager::new(cx),
2386 columnar_selection_state: None,
2387 add_selections_state: None,
2388 select_next_state: None,
2389 select_prev_state: None,
2390 selection_history: SelectionHistory::default(),
2391 defer_selection_effects: false,
2392 deferred_selection_effects_state: None,
2393 autoclose_regions: Vec::new(),
2394 snippet_stack: InvalidationStack::default(),
2395 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2396 ime_transaction: None,
2397 active_diagnostics: ActiveDiagnostic::None,
2398 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2399 inline_diagnostics_update: Task::ready(()),
2400 inline_diagnostics: Vec::new(),
2401 soft_wrap_mode_override,
2402 diagnostics_max_severity,
2403 hard_wrap: None,
2404 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2405 semantics_provider: project
2406 .as_ref()
2407 .map(|project| Rc::new(project.downgrade()) as _),
2408 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2409 project,
2410 blink_manager: blink_manager.clone(),
2411 show_local_selections: true,
2412 show_scrollbars: ScrollbarAxes {
2413 horizontal: full_mode,
2414 vertical: full_mode,
2415 },
2416 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2417 offset_content: !matches!(mode, EditorMode::SingleLine),
2418 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2419 show_gutter: full_mode,
2420 show_line_numbers: (!full_mode).then_some(false),
2421 use_relative_line_numbers: None,
2422 disable_expand_excerpt_buttons: !full_mode,
2423 delegate_expand_excerpts: false,
2424 delegate_stage_and_restore: false,
2425 delegate_open_excerpts: false,
2426 enable_lsp_data: full_mode,
2427 enable_runnables: full_mode,
2428 enable_mouse_wheel_zoom: full_mode,
2429 show_git_diff_gutter: None,
2430 show_code_actions: None,
2431 show_runnables: None,
2432 show_breakpoints: None,
2433 show_diff_review_button: false,
2434 show_wrap_guides: None,
2435 show_indent_guides,
2436 buffers_with_disabled_indent_guides: HashSet::default(),
2437 highlight_order: 0,
2438 highlighted_rows: HashMap::default(),
2439 background_highlights: HashMap::default(),
2440 gutter_highlights: HashMap::default(),
2441 scrollbar_marker_state: ScrollbarMarkerState::default(),
2442 active_indent_guides_state: ActiveIndentGuidesState::default(),
2443 nav_history: None,
2444 context_menu: RefCell::new(None),
2445 context_menu_options: None,
2446 mouse_context_menu: None,
2447 completion_tasks: Vec::new(),
2448 inline_blame_popover: None,
2449 inline_blame_popover_show_task: None,
2450 signature_help_state: SignatureHelpState::default(),
2451 auto_signature_help: None,
2452 find_all_references_task_sources: Vec::new(),
2453 next_completion_id: 0,
2454 next_inlay_id: 0,
2455 code_action_providers,
2456 available_code_actions: None,
2457 code_actions_task: None,
2458 quick_selection_highlight_task: None,
2459 debounced_selection_highlight_task: None,
2460 debounced_selection_highlight_complete: false,
2461 document_highlights_task: None,
2462 linked_editing_range_task: None,
2463 pending_rename: None,
2464 searchable: !is_minimap,
2465 cursor_shape: EditorSettings::get_global(cx)
2466 .cursor_shape
2467 .unwrap_or_default(),
2468 cursor_offset_on_selection: false,
2469 current_line_highlight: None,
2470 autoindent_mode: Some(AutoindentMode::EachLine),
2471 collapse_matches: false,
2472 workspace: None,
2473 input_enabled: !is_minimap,
2474 expects_character_input: !is_minimap,
2475 use_modal_editing: full_mode,
2476 read_only: is_minimap,
2477 use_autoclose: true,
2478 use_auto_surround: true,
2479 use_selection_highlight: true,
2480 auto_replace_emoji_shortcode: false,
2481 jsx_tag_auto_close_enabled_in_any_buffer: false,
2482 leader_id: None,
2483 remote_id: None,
2484 hover_state: HoverState::default(),
2485 pending_mouse_down: None,
2486 prev_pressure_stage: None,
2487 hovered_link_state: None,
2488 edit_prediction_provider: None,
2489 active_edit_prediction: None,
2490 stale_edit_prediction_in_menu: None,
2491 edit_prediction_preview: EditPredictionPreview::Inactive {
2492 released_too_fast: false,
2493 },
2494 inline_diagnostics_enabled: full_mode,
2495 diagnostics_enabled: full_mode,
2496 word_completions_enabled: full_mode,
2497 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2498 gutter_hovered: false,
2499 pixel_position_of_newest_cursor: None,
2500 last_bounds: None,
2501 last_position_map: None,
2502 expect_bounds_change: None,
2503 gutter_dimensions: GutterDimensions::default(),
2504 style: None,
2505 show_cursor_names: false,
2506 hovered_cursors: HashMap::default(),
2507 next_editor_action_id: EditorActionId::default(),
2508 editor_actions: Rc::default(),
2509 edit_predictions_hidden_for_vim_mode: false,
2510 show_edit_predictions_override: None,
2511 show_completions_on_input_override: None,
2512 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2513 edit_prediction_settings: EditPredictionSettings::Disabled,
2514 in_leading_whitespace: false,
2515 custom_context_menu: None,
2516 show_git_blame_gutter: false,
2517 show_git_blame_inline: false,
2518 show_selection_menu: None,
2519 show_git_blame_inline_delay_task: None,
2520 git_blame_inline_enabled: full_mode
2521 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2522 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2523 buffer_serialization: is_minimap.not().then(|| {
2524 BufferSerialization::new(
2525 ProjectSettings::get_global(cx)
2526 .session
2527 .restore_unsaved_buffers,
2528 )
2529 }),
2530 blame: None,
2531 blame_subscription: None,
2532
2533 breakpoint_store,
2534 gutter_breakpoint_indicator: (None, None),
2535 gutter_diff_review_indicator: (None, None),
2536 diff_review_drag_state: None,
2537 diff_review_overlays: Vec::new(),
2538 stored_review_comments: Vec::new(),
2539 next_review_comment_id: 0,
2540 hovered_diff_hunk_row: None,
2541 _subscriptions: (!is_minimap)
2542 .then(|| {
2543 vec![
2544 cx.observe(&multi_buffer, Self::on_buffer_changed),
2545 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2546 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2547 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2548 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2549 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2550 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2551 cx.observe_window_activation(window, |editor, window, cx| {
2552 let active = window.is_window_active();
2553 editor.blink_manager.update(cx, |blink_manager, cx| {
2554 if active {
2555 blink_manager.enable(cx);
2556 } else {
2557 blink_manager.disable(cx);
2558 }
2559 });
2560 if active {
2561 editor.show_mouse_cursor(cx);
2562 }
2563 }),
2564 ]
2565 })
2566 .unwrap_or_default(),
2567 runnables: RunnableData::new(),
2568 pull_diagnostics_task: Task::ready(()),
2569 colors: None,
2570 refresh_colors_task: Task::ready(()),
2571 use_document_folding_ranges: false,
2572 refresh_folding_ranges_task: Task::ready(()),
2573 inlay_hints: None,
2574 next_color_inlay_id: 0,
2575 post_scroll_update: Task::ready(()),
2576 linked_edit_ranges: Default::default(),
2577 in_project_search: false,
2578 previous_search_ranges: None,
2579 breadcrumb_header: None,
2580 focused_block: None,
2581 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2582 addons: HashMap::default(),
2583 registered_buffers: HashMap::default(),
2584 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2585 selection_mark_mode: false,
2586 toggle_fold_multiple_buffers: Task::ready(()),
2587 serialize_selections: Task::ready(()),
2588 serialize_folds: Task::ready(()),
2589 text_style_refinement: None,
2590 load_diff_task: load_uncommitted_diff,
2591 temporary_diff_override: false,
2592 mouse_cursor_hidden: false,
2593 minimap: None,
2594 hide_mouse_mode: EditorSettings::get_global(cx)
2595 .hide_mouse
2596 .unwrap_or_default(),
2597 change_list: ChangeList::new(),
2598 mode,
2599 selection_drag_state: SelectionDragState::None,
2600 folding_newlines: Task::ready(()),
2601 lookup_key: None,
2602 select_next_is_case_sensitive: None,
2603 on_local_selections_changed: None,
2604 suppress_selection_callback: false,
2605 applicable_language_settings: HashMap::default(),
2606 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2607 accent_data: None,
2608 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2609 number_deleted_lines: false,
2610 refresh_matching_bracket_highlights_task: Task::ready(()),
2611 refresh_document_symbols_task: Task::ready(()).shared(),
2612 lsp_document_symbols: HashMap::default(),
2613 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2614 outline_symbols_at_cursor: None,
2615 sticky_headers_task: Task::ready(()),
2616 sticky_headers: None,
2617 colorize_brackets_task: Task::ready(()),
2618 };
2619
2620 if is_minimap {
2621 return editor;
2622 }
2623
2624 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2625 editor.accent_data = editor.fetch_accent_data(cx);
2626
2627 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2628 editor
2629 ._subscriptions
2630 .push(cx.observe(breakpoints, |_, _, cx| {
2631 cx.notify();
2632 }));
2633 }
2634 editor._subscriptions.extend(project_subscriptions);
2635
2636 editor._subscriptions.push(cx.subscribe_in(
2637 &cx.entity(),
2638 window,
2639 |editor, _, e: &EditorEvent, window, cx| match e {
2640 EditorEvent::ScrollPositionChanged { local, .. } => {
2641 if *local {
2642 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2643 editor.inline_blame_popover.take();
2644 let snapshot = editor.snapshot(window, cx);
2645 let new_anchor = editor
2646 .scroll_manager
2647 .native_anchor(&snapshot.display_snapshot, cx);
2648 editor.update_restoration_data(cx, move |data| {
2649 data.scroll_position = (
2650 new_anchor.top_row(snapshot.buffer_snapshot()),
2651 new_anchor.offset,
2652 );
2653 });
2654
2655 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2656 cx.background_executor()
2657 .timer(Duration::from_millis(50))
2658 .await;
2659 editor
2660 .update_in(cx, |editor, window, cx| {
2661 editor.update_data_on_scroll(window, cx)
2662 })
2663 .ok();
2664 });
2665 }
2666 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2667 }
2668 EditorEvent::Edited { .. } => {
2669 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2670 .map(|vim_mode| vim_mode.0)
2671 .unwrap_or(false);
2672 if !vim_mode {
2673 let display_map = editor.display_snapshot(cx);
2674 let selections = editor.selections.all_adjusted_display(&display_map);
2675 let pop_state = editor
2676 .change_list
2677 .last()
2678 .map(|previous| {
2679 previous.len() == selections.len()
2680 && previous.iter().enumerate().all(|(ix, p)| {
2681 p.to_display_point(&display_map).row()
2682 == selections[ix].head().row()
2683 })
2684 })
2685 .unwrap_or(false);
2686 let new_positions = selections
2687 .into_iter()
2688 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2689 .collect();
2690 editor
2691 .change_list
2692 .push_to_change_list(pop_state, new_positions);
2693 }
2694 }
2695 _ => (),
2696 },
2697 ));
2698
2699 if let Some(dap_store) = editor
2700 .project
2701 .as_ref()
2702 .map(|project| project.read(cx).dap_store())
2703 {
2704 let weak_editor = cx.weak_entity();
2705
2706 editor
2707 ._subscriptions
2708 .push(
2709 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2710 let session_entity = cx.entity();
2711 weak_editor
2712 .update(cx, |editor, cx| {
2713 editor._subscriptions.push(
2714 cx.subscribe(&session_entity, Self::on_debug_session_event),
2715 );
2716 })
2717 .ok();
2718 }),
2719 );
2720
2721 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2722 editor
2723 ._subscriptions
2724 .push(cx.subscribe(&session, Self::on_debug_session_event));
2725 }
2726 }
2727
2728 // skip adding the initial selection to selection history
2729 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2730 editor.end_selection(window, cx);
2731 editor.selection_history.mode = SelectionHistoryMode::Normal;
2732
2733 editor.scroll_manager.show_scrollbars(window, cx);
2734 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2735
2736 if full_mode {
2737 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2738 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2739
2740 if editor.git_blame_inline_enabled {
2741 editor.start_git_blame_inline(false, window, cx);
2742 }
2743
2744 editor.go_to_active_debug_line(window, cx);
2745
2746 editor.minimap =
2747 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2748 editor.colors = Some(LspColorData::new(cx));
2749 editor.use_document_folding_ranges = true;
2750 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2751
2752 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2753 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2754 }
2755 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2756 }
2757
2758 editor
2759 }
2760
2761 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2762 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2763 }
2764
2765 pub fn deploy_mouse_context_menu(
2766 &mut self,
2767 position: gpui::Point<Pixels>,
2768 context_menu: Entity<ContextMenu>,
2769 window: &mut Window,
2770 cx: &mut Context<Self>,
2771 ) {
2772 self.mouse_context_menu = Some(MouseContextMenu::new(
2773 self,
2774 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2775 context_menu,
2776 window,
2777 cx,
2778 ));
2779 }
2780
2781 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2782 self.mouse_context_menu
2783 .as_ref()
2784 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2785 }
2786
2787 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2788 if self
2789 .selections
2790 .pending_anchor()
2791 .is_some_and(|pending_selection| {
2792 let snapshot = self.buffer().read(cx).snapshot(cx);
2793 pending_selection.range().includes(range, &snapshot)
2794 })
2795 {
2796 return true;
2797 }
2798
2799 self.selections
2800 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2801 .into_iter()
2802 .any(|selection| {
2803 // This is needed to cover a corner case, if we just check for an existing
2804 // selection in the fold range, having a cursor at the start of the fold
2805 // marks it as selected. Non-empty selections don't cause this.
2806 let length = selection.end - selection.start;
2807 length > 0
2808 })
2809 }
2810
2811 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2812 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2813 }
2814
2815 fn key_context_internal(
2816 &self,
2817 has_active_edit_prediction: bool,
2818 window: &mut Window,
2819 cx: &mut App,
2820 ) -> KeyContext {
2821 let mut key_context = KeyContext::new_with_defaults();
2822 key_context.add("Editor");
2823 let mode = match self.mode {
2824 EditorMode::SingleLine => "single_line",
2825 EditorMode::AutoHeight { .. } => "auto_height",
2826 EditorMode::Minimap { .. } => "minimap",
2827 EditorMode::Full { .. } => "full",
2828 };
2829
2830 if EditorSettings::jupyter_enabled(cx) {
2831 key_context.add("jupyter");
2832 }
2833
2834 key_context.set("mode", mode);
2835 if self.pending_rename.is_some() {
2836 key_context.add("renaming");
2837 }
2838
2839 if let Some(snippet_stack) = self.snippet_stack.last() {
2840 key_context.add("in_snippet");
2841
2842 if snippet_stack.active_index > 0 {
2843 key_context.add("has_previous_tabstop");
2844 }
2845
2846 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2847 key_context.add("has_next_tabstop");
2848 }
2849 }
2850
2851 match self.context_menu.borrow().as_ref() {
2852 Some(CodeContextMenu::Completions(menu)) => {
2853 if menu.visible() {
2854 key_context.add("menu");
2855 key_context.add("showing_completions");
2856 }
2857 }
2858 Some(CodeContextMenu::CodeActions(menu)) => {
2859 if menu.visible() {
2860 key_context.add("menu");
2861 key_context.add("showing_code_actions")
2862 }
2863 }
2864 None => {}
2865 }
2866
2867 if self.signature_help_state.has_multiple_signatures() {
2868 key_context.add("showing_signature_help");
2869 }
2870
2871 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2872 if !self.focus_handle(cx).contains_focused(window, cx)
2873 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2874 {
2875 for addon in self.addons.values() {
2876 addon.extend_key_context(&mut key_context, cx)
2877 }
2878 }
2879
2880 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2881 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2882 Some(
2883 file.full_path(cx)
2884 .extension()?
2885 .to_string_lossy()
2886 .to_lowercase(),
2887 )
2888 }) {
2889 key_context.set("extension", extension);
2890 }
2891 } else {
2892 key_context.add("multibuffer");
2893 }
2894
2895 if has_active_edit_prediction {
2896 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2897 key_context.add("copilot_suggestion");
2898 }
2899
2900 if self.in_leading_whitespace {
2901 key_context.add("in_leading_whitespace");
2902 }
2903 if self.edit_prediction_requires_modifier() {
2904 key_context.set("edit_prediction_mode", "subtle")
2905 } else {
2906 key_context.set("edit_prediction_mode", "eager");
2907 }
2908
2909 if self.selection_mark_mode {
2910 key_context.add("selection_mode");
2911 }
2912
2913 let disjoint = self.selections.disjoint_anchors();
2914 if matches!(
2915 &self.mode,
2916 EditorMode::SingleLine | EditorMode::AutoHeight { .. }
2917 ) && let [selection] = disjoint
2918 && selection.start == selection.end
2919 {
2920 let snapshot = self.snapshot(window, cx);
2921 let snapshot = snapshot.buffer_snapshot();
2922 let caret_offset = selection.end.to_offset(snapshot);
2923
2924 if caret_offset == MultiBufferOffset(0) {
2925 key_context.add("start_of_input");
2926 }
2927
2928 if caret_offset == snapshot.len() {
2929 key_context.add("end_of_input");
2930 }
2931 }
2932
2933 if self.has_any_expanded_diff_hunks(cx) {
2934 key_context.add("diffs_expanded");
2935 }
2936
2937 key_context
2938 }
2939
2940 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2941 self.last_bounds.as_ref()
2942 }
2943
2944 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2945 if self.mouse_cursor_hidden {
2946 self.mouse_cursor_hidden = false;
2947 cx.notify();
2948 }
2949 }
2950
2951 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2952 let hide_mouse_cursor = match origin {
2953 HideMouseCursorOrigin::TypingAction => {
2954 matches!(
2955 self.hide_mouse_mode,
2956 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2957 )
2958 }
2959 HideMouseCursorOrigin::MovementAction => {
2960 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2961 }
2962 };
2963 if self.mouse_cursor_hidden != hide_mouse_cursor {
2964 self.mouse_cursor_hidden = hide_mouse_cursor;
2965 cx.notify();
2966 }
2967 }
2968
2969 fn accept_edit_prediction_keystroke(
2970 &self,
2971 granularity: EditPredictionGranularity,
2972 window: &mut Window,
2973 cx: &mut App,
2974 ) -> Option<gpui::KeybindingKeystroke> {
2975 let key_context = self.key_context_internal(true, window, cx);
2976
2977 let bindings =
2978 match granularity {
2979 EditPredictionGranularity::Word => window
2980 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2981 EditPredictionGranularity::Line => window
2982 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2983 EditPredictionGranularity::Full => {
2984 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2985 }
2986 };
2987
2988 bindings
2989 .into_iter()
2990 .rev()
2991 .find_map(|binding| match binding.keystrokes() {
2992 [keystroke, ..] => Some(keystroke.clone()),
2993 _ => None,
2994 })
2995 }
2996
2997 fn preview_edit_prediction_keystroke(
2998 &self,
2999 window: &mut Window,
3000 cx: &mut App,
3001 ) -> Option<gpui::KeybindingKeystroke> {
3002 let key_context = self.key_context_internal(true, window, cx);
3003 let bindings = window.bindings_for_action_in_context(&AcceptEditPrediction, key_context);
3004 bindings
3005 .into_iter()
3006 .rev()
3007 .find_map(|binding| match binding.keystrokes() {
3008 [keystroke, ..] if keystroke.modifiers().modified() => Some(keystroke.clone()),
3009 _ => None,
3010 })
3011 }
3012
3013 fn edit_prediction_preview_modifiers_held(
3014 &self,
3015 modifiers: &Modifiers,
3016 window: &mut Window,
3017 cx: &mut App,
3018 ) -> bool {
3019 let key_context = self.key_context_internal(true, window, cx);
3020 let actions: [&dyn Action; 3] = [
3021 &AcceptEditPrediction,
3022 &AcceptNextWordEditPrediction,
3023 &AcceptNextLineEditPrediction,
3024 ];
3025
3026 actions.into_iter().any(|action| {
3027 window
3028 .bindings_for_action_in_context(action, key_context.clone())
3029 .into_iter()
3030 .rev()
3031 .any(|binding| {
3032 binding.keystrokes().first().is_some_and(|keystroke| {
3033 keystroke.modifiers().modified() && keystroke.modifiers() == modifiers
3034 })
3035 })
3036 })
3037 }
3038
3039 fn edit_prediction_cursor_popover_prefers_preview(
3040 &self,
3041 completion: &EditPredictionState,
3042 cx: &App,
3043 ) -> bool {
3044 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
3045
3046 match &completion.completion {
3047 EditPrediction::Edit {
3048 edits, snapshot, ..
3049 } => {
3050 let mut start_row: Option<u32> = None;
3051 let mut end_row: Option<u32> = None;
3052
3053 for (range, text) in edits {
3054 let Some((_, range)) =
3055 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(range.clone())
3056 else {
3057 continue;
3058 };
3059 let edit_start_row = range.start.to_point(snapshot).row;
3060 let old_end_row = range.end.to_point(snapshot).row;
3061 let inserted_newline_count = text
3062 .as_ref()
3063 .chars()
3064 .filter(|character| *character == '\n')
3065 .count() as u32;
3066 let deleted_newline_count = old_end_row - edit_start_row;
3067 let preview_end_row = edit_start_row + inserted_newline_count;
3068
3069 start_row =
3070 Some(start_row.map_or(edit_start_row, |row| row.min(edit_start_row)));
3071 end_row = Some(end_row.map_or(preview_end_row, |row| row.max(preview_end_row)));
3072
3073 if deleted_newline_count > 1 {
3074 end_row = Some(end_row.map_or(old_end_row, |row| row.max(old_end_row)));
3075 }
3076 }
3077
3078 start_row
3079 .zip(end_row)
3080 .is_some_and(|(start_row, end_row)| end_row > start_row)
3081 }
3082 EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => false,
3083 }
3084 }
3085
3086 fn edit_prediction_keybind_display(
3087 &self,
3088 surface: EditPredictionKeybindSurface,
3089 window: &mut Window,
3090 cx: &mut App,
3091 ) -> EditPredictionKeybindDisplay {
3092 let accept_keystroke =
3093 self.accept_edit_prediction_keystroke(EditPredictionGranularity::Full, window, cx);
3094 let preview_keystroke = self.preview_edit_prediction_keystroke(window, cx);
3095
3096 let action = match surface {
3097 EditPredictionKeybindSurface::Inline
3098 | EditPredictionKeybindSurface::CursorPopoverCompact => {
3099 if self.edit_prediction_requires_modifier() {
3100 EditPredictionKeybindAction::Preview
3101 } else {
3102 EditPredictionKeybindAction::Accept
3103 }
3104 }
3105 EditPredictionKeybindSurface::CursorPopoverExpanded => self
3106 .active_edit_prediction
3107 .as_ref()
3108 .filter(|completion| {
3109 self.edit_prediction_cursor_popover_prefers_preview(completion, cx)
3110 })
3111 .map_or(EditPredictionKeybindAction::Accept, |_| {
3112 EditPredictionKeybindAction::Preview
3113 }),
3114 };
3115 #[cfg(test)]
3116 let preview_copy = preview_keystroke.clone();
3117 #[cfg(test)]
3118 let accept_copy = accept_keystroke.clone();
3119
3120 let displayed_keystroke = match surface {
3121 EditPredictionKeybindSurface::Inline => match action {
3122 EditPredictionKeybindAction::Accept => accept_keystroke,
3123 EditPredictionKeybindAction::Preview => preview_keystroke,
3124 },
3125 EditPredictionKeybindSurface::CursorPopoverCompact
3126 | EditPredictionKeybindSurface::CursorPopoverExpanded => match action {
3127 EditPredictionKeybindAction::Accept => accept_keystroke,
3128 EditPredictionKeybindAction::Preview => {
3129 preview_keystroke.or_else(|| accept_keystroke.clone())
3130 }
3131 },
3132 };
3133
3134 let missing_accept_keystroke = displayed_keystroke.is_none();
3135
3136 EditPredictionKeybindDisplay {
3137 #[cfg(test)]
3138 accept_keystroke: accept_copy,
3139 #[cfg(test)]
3140 preview_keystroke: preview_copy,
3141 displayed_keystroke,
3142 action,
3143 missing_accept_keystroke,
3144 show_hold_label: matches!(surface, EditPredictionKeybindSurface::CursorPopoverCompact)
3145 && self.edit_prediction_preview.released_too_fast(),
3146 }
3147 }
3148
3149 pub fn new_file(
3150 workspace: &mut Workspace,
3151 _: &workspace::NewFile,
3152 window: &mut Window,
3153 cx: &mut Context<Workspace>,
3154 ) {
3155 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3156 "Failed to create buffer",
3157 window,
3158 cx,
3159 |e, _, _| match e.error_code() {
3160 ErrorCode::RemoteUpgradeRequired => Some(format!(
3161 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3162 e.error_tag("required").unwrap_or("the latest version")
3163 )),
3164 _ => None,
3165 },
3166 );
3167 }
3168
3169 pub fn new_in_workspace(
3170 workspace: &mut Workspace,
3171 window: &mut Window,
3172 cx: &mut Context<Workspace>,
3173 ) -> Task<Result<Entity<Editor>>> {
3174 let project = workspace.project().clone();
3175 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3176
3177 cx.spawn_in(window, async move |workspace, cx| {
3178 let buffer = create.await?;
3179 workspace.update_in(cx, |workspace, window, cx| {
3180 let editor =
3181 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3182 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3183 editor
3184 })
3185 })
3186 }
3187
3188 fn new_file_vertical(
3189 workspace: &mut Workspace,
3190 _: &workspace::NewFileSplitVertical,
3191 window: &mut Window,
3192 cx: &mut Context<Workspace>,
3193 ) {
3194 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3195 }
3196
3197 fn new_file_horizontal(
3198 workspace: &mut Workspace,
3199 _: &workspace::NewFileSplitHorizontal,
3200 window: &mut Window,
3201 cx: &mut Context<Workspace>,
3202 ) {
3203 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3204 }
3205
3206 fn new_file_split(
3207 workspace: &mut Workspace,
3208 action: &workspace::NewFileSplit,
3209 window: &mut Window,
3210 cx: &mut Context<Workspace>,
3211 ) {
3212 Self::new_file_in_direction(workspace, action.0, window, cx)
3213 }
3214
3215 fn new_file_in_direction(
3216 workspace: &mut Workspace,
3217 direction: SplitDirection,
3218 window: &mut Window,
3219 cx: &mut Context<Workspace>,
3220 ) {
3221 let project = workspace.project().clone();
3222 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3223
3224 cx.spawn_in(window, async move |workspace, cx| {
3225 let buffer = create.await?;
3226 workspace.update_in(cx, move |workspace, window, cx| {
3227 workspace.split_item(
3228 direction,
3229 Box::new(
3230 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3231 ),
3232 window,
3233 cx,
3234 )
3235 })?;
3236 anyhow::Ok(())
3237 })
3238 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3239 match e.error_code() {
3240 ErrorCode::RemoteUpgradeRequired => Some(format!(
3241 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3242 e.error_tag("required").unwrap_or("the latest version")
3243 )),
3244 _ => None,
3245 }
3246 });
3247 }
3248
3249 pub fn leader_id(&self) -> Option<CollaboratorId> {
3250 self.leader_id
3251 }
3252
3253 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3254 &self.buffer
3255 }
3256
3257 pub fn project(&self) -> Option<&Entity<Project>> {
3258 self.project.as_ref()
3259 }
3260
3261 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3262 self.workspace.as_ref()?.0.upgrade()
3263 }
3264
3265 /// Detaches a task and shows an error notification in the workspace if available,
3266 /// otherwise just logs the error.
3267 pub fn detach_and_notify_err<R, E>(
3268 &self,
3269 task: Task<Result<R, E>>,
3270 window: &mut Window,
3271 cx: &mut App,
3272 ) where
3273 E: std::fmt::Debug + std::fmt::Display + 'static,
3274 R: 'static,
3275 {
3276 if let Some(workspace) = self.workspace() {
3277 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3278 } else {
3279 task.detach_and_log_err(cx);
3280 }
3281 }
3282
3283 /// Returns the workspace serialization ID if this editor should be serialized.
3284 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3285 self.workspace
3286 .as_ref()
3287 .filter(|_| self.should_serialize_buffer())
3288 .and_then(|workspace| workspace.1)
3289 }
3290
3291 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3292 self.buffer().read(cx).title(cx)
3293 }
3294
3295 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3296 let git_blame_gutter_max_author_length = self
3297 .render_git_blame_gutter(cx)
3298 .then(|| {
3299 if let Some(blame) = self.blame.as_ref() {
3300 let max_author_length =
3301 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3302 Some(max_author_length)
3303 } else {
3304 None
3305 }
3306 })
3307 .flatten();
3308
3309 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3310
3311 EditorSnapshot {
3312 mode: self.mode.clone(),
3313 show_gutter: self.show_gutter,
3314 offset_content: self.offset_content,
3315 show_line_numbers: self.show_line_numbers,
3316 number_deleted_lines: self.number_deleted_lines,
3317 show_git_diff_gutter: self.show_git_diff_gutter,
3318 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3319 show_code_actions: self.show_code_actions,
3320 show_runnables: self.show_runnables,
3321 show_breakpoints: self.show_breakpoints,
3322 git_blame_gutter_max_author_length,
3323 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3324 display_snapshot,
3325 placeholder_display_snapshot: self
3326 .placeholder_display_map
3327 .as_ref()
3328 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3329 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3330 is_focused: self.focus_handle.is_focused(window),
3331 current_line_highlight: self
3332 .current_line_highlight
3333 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3334 gutter_hovered: self.gutter_hovered,
3335 }
3336 }
3337
3338 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3339 self.buffer.read(cx).language_at(point, cx)
3340 }
3341
3342 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3343 self.buffer.read(cx).read(cx).file_at(point).cloned()
3344 }
3345
3346 pub fn active_buffer(&self, cx: &App) -> Option<Entity<Buffer>> {
3347 let multibuffer = self.buffer.read(cx);
3348 let snapshot = multibuffer.snapshot(cx);
3349 let (anchor, _) =
3350 snapshot.anchor_to_buffer_anchor(self.selections.newest_anchor().head())?;
3351 multibuffer.buffer(anchor.buffer_id)
3352 }
3353
3354 pub fn mode(&self) -> &EditorMode {
3355 &self.mode
3356 }
3357
3358 pub fn set_mode(&mut self, mode: EditorMode) {
3359 self.mode = mode;
3360 }
3361
3362 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3363 self.collaboration_hub.as_deref()
3364 }
3365
3366 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3367 self.collaboration_hub = Some(hub);
3368 }
3369
3370 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3371 self.in_project_search = in_project_search;
3372 }
3373
3374 pub fn set_custom_context_menu(
3375 &mut self,
3376 f: impl 'static
3377 + Fn(
3378 &mut Self,
3379 DisplayPoint,
3380 &mut Window,
3381 &mut Context<Self>,
3382 ) -> Option<Entity<ui::ContextMenu>>,
3383 ) {
3384 self.custom_context_menu = Some(Box::new(f))
3385 }
3386
3387 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3388 self.completion_provider = provider;
3389 }
3390
3391 #[cfg(any(test, feature = "test-support"))]
3392 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3393 self.completion_provider.clone()
3394 }
3395
3396 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3397 self.semantics_provider.clone()
3398 }
3399
3400 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3401 self.semantics_provider = provider;
3402 }
3403
3404 pub fn set_edit_prediction_provider<T>(
3405 &mut self,
3406 provider: Option<Entity<T>>,
3407 window: &mut Window,
3408 cx: &mut Context<Self>,
3409 ) where
3410 T: EditPredictionDelegate,
3411 {
3412 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3413 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3414 if this.focus_handle.is_focused(window) {
3415 this.update_visible_edit_prediction(window, cx);
3416 }
3417 }),
3418 provider: Arc::new(provider),
3419 });
3420 self.update_edit_prediction_settings(cx);
3421 self.refresh_edit_prediction(false, false, window, cx);
3422 }
3423
3424 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3425 self.placeholder_display_map
3426 .as_ref()
3427 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3428 }
3429
3430 pub fn set_placeholder_text(
3431 &mut self,
3432 placeholder_text: &str,
3433 window: &mut Window,
3434 cx: &mut Context<Self>,
3435 ) {
3436 let multibuffer = cx
3437 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3438
3439 let style = window.text_style();
3440
3441 self.placeholder_display_map = Some(cx.new(|cx| {
3442 DisplayMap::new(
3443 multibuffer,
3444 style.font(),
3445 style.font_size.to_pixels(window.rem_size()),
3446 None,
3447 FILE_HEADER_HEIGHT,
3448 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3449 Default::default(),
3450 DiagnosticSeverity::Off,
3451 cx,
3452 )
3453 }));
3454 cx.notify();
3455 }
3456
3457 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3458 self.cursor_shape = cursor_shape;
3459
3460 // Disrupt blink for immediate user feedback that the cursor shape has changed
3461 self.blink_manager.update(cx, BlinkManager::show_cursor);
3462
3463 cx.notify();
3464 }
3465
3466 pub fn cursor_shape(&self) -> CursorShape {
3467 self.cursor_shape
3468 }
3469
3470 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3471 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3472 }
3473
3474 pub fn set_current_line_highlight(
3475 &mut self,
3476 current_line_highlight: Option<CurrentLineHighlight>,
3477 ) {
3478 self.current_line_highlight = current_line_highlight;
3479 }
3480
3481 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3482 self.collapse_matches = collapse_matches;
3483 }
3484
3485 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3486 if self.collapse_matches {
3487 return range.start..range.start;
3488 }
3489 range.clone()
3490 }
3491
3492 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3493 self.display_map.read(cx).clip_at_line_ends
3494 }
3495
3496 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3497 if self.display_map.read(cx).clip_at_line_ends != clip {
3498 self.display_map
3499 .update(cx, |map, _| map.clip_at_line_ends = clip);
3500 }
3501 }
3502
3503 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3504 self.input_enabled = input_enabled;
3505 }
3506
3507 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3508 self.expects_character_input = expects_character_input;
3509 }
3510
3511 pub fn set_edit_predictions_hidden_for_vim_mode(
3512 &mut self,
3513 hidden: bool,
3514 window: &mut Window,
3515 cx: &mut Context<Self>,
3516 ) {
3517 if hidden != self.edit_predictions_hidden_for_vim_mode {
3518 self.edit_predictions_hidden_for_vim_mode = hidden;
3519 if hidden {
3520 self.update_visible_edit_prediction(window, cx);
3521 } else {
3522 self.refresh_edit_prediction(true, false, window, cx);
3523 }
3524 }
3525 }
3526
3527 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3528 self.menu_edit_predictions_policy = value;
3529 }
3530
3531 pub fn set_autoindent(&mut self, autoindent: bool) {
3532 if autoindent {
3533 self.autoindent_mode = Some(AutoindentMode::EachLine);
3534 } else {
3535 self.autoindent_mode = None;
3536 }
3537 }
3538
3539 pub fn capability(&self, cx: &App) -> Capability {
3540 if self.read_only {
3541 Capability::ReadOnly
3542 } else {
3543 self.buffer.read(cx).capability()
3544 }
3545 }
3546
3547 pub fn read_only(&self, cx: &App) -> bool {
3548 self.read_only || self.buffer.read(cx).read_only()
3549 }
3550
3551 pub fn set_read_only(&mut self, read_only: bool) {
3552 self.read_only = read_only;
3553 }
3554
3555 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3556 self.use_autoclose = autoclose;
3557 }
3558
3559 pub fn set_use_selection_highlight(&mut self, highlight: bool) {
3560 self.use_selection_highlight = highlight;
3561 }
3562
3563 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3564 self.use_auto_surround = auto_surround;
3565 }
3566
3567 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3568 self.auto_replace_emoji_shortcode = auto_replace;
3569 }
3570
3571 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3572 self.buffer_serialization = should_serialize.then(|| {
3573 BufferSerialization::new(
3574 ProjectSettings::get_global(cx)
3575 .session
3576 .restore_unsaved_buffers,
3577 )
3578 })
3579 }
3580
3581 fn should_serialize_buffer(&self) -> bool {
3582 self.buffer_serialization.is_some()
3583 }
3584
3585 pub fn toggle_edit_predictions(
3586 &mut self,
3587 _: &ToggleEditPrediction,
3588 window: &mut Window,
3589 cx: &mut Context<Self>,
3590 ) {
3591 if self.show_edit_predictions_override.is_some() {
3592 self.set_show_edit_predictions(None, window, cx);
3593 } else {
3594 let show_edit_predictions = !self.edit_predictions_enabled();
3595 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3596 }
3597 }
3598
3599 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3600 self.show_completions_on_input_override = show_completions_on_input;
3601 }
3602
3603 pub fn set_show_edit_predictions(
3604 &mut self,
3605 show_edit_predictions: Option<bool>,
3606 window: &mut Window,
3607 cx: &mut Context<Self>,
3608 ) {
3609 self.show_edit_predictions_override = show_edit_predictions;
3610 self.update_edit_prediction_settings(cx);
3611
3612 if let Some(false) = show_edit_predictions {
3613 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3614 } else {
3615 self.refresh_edit_prediction(false, true, window, cx);
3616 }
3617 }
3618
3619 fn edit_predictions_disabled_in_scope(
3620 &self,
3621 buffer: &Entity<Buffer>,
3622 buffer_position: language::Anchor,
3623 cx: &App,
3624 ) -> bool {
3625 let snapshot = buffer.read(cx).snapshot();
3626 let settings = snapshot.settings_at(buffer_position, cx);
3627
3628 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3629 return false;
3630 };
3631
3632 scope.override_name().is_some_and(|scope_name| {
3633 settings
3634 .edit_predictions_disabled_in
3635 .iter()
3636 .any(|s| s == scope_name)
3637 })
3638 }
3639
3640 pub fn set_use_modal_editing(&mut self, to: bool) {
3641 self.use_modal_editing = to;
3642 }
3643
3644 pub fn use_modal_editing(&self) -> bool {
3645 self.use_modal_editing
3646 }
3647
3648 fn selections_did_change(
3649 &mut self,
3650 local: bool,
3651 old_cursor_position: &Anchor,
3652 effects: SelectionEffects,
3653 window: &mut Window,
3654 cx: &mut Context<Self>,
3655 ) {
3656 window.invalidate_character_coordinates();
3657
3658 // Copy selections to primary selection buffer
3659 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3660 if local {
3661 let selections = self
3662 .selections
3663 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3664 let buffer_handle = self.buffer.read(cx).read(cx);
3665
3666 let mut text = String::new();
3667 for (index, selection) in selections.iter().enumerate() {
3668 let text_for_selection = buffer_handle
3669 .text_for_range(selection.start..selection.end)
3670 .collect::<String>();
3671
3672 text.push_str(&text_for_selection);
3673 if index != selections.len() - 1 {
3674 text.push('\n');
3675 }
3676 }
3677
3678 if !text.is_empty() {
3679 cx.write_to_primary(ClipboardItem::new_string(text));
3680 }
3681 }
3682
3683 let selection_anchors = self.selections.disjoint_anchors_arc();
3684
3685 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3686 self.buffer.update(cx, |buffer, cx| {
3687 buffer.set_active_selections(
3688 &selection_anchors,
3689 self.selections.line_mode(),
3690 self.cursor_shape,
3691 cx,
3692 )
3693 });
3694 }
3695 let display_map = self
3696 .display_map
3697 .update(cx, |display_map, cx| display_map.snapshot(cx));
3698 let buffer = display_map.buffer_snapshot();
3699 if self.selections.count() == 1 {
3700 self.add_selections_state = None;
3701 }
3702 self.select_next_state = None;
3703 self.select_prev_state = None;
3704 self.select_syntax_node_history.try_clear();
3705 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3706 self.snippet_stack.invalidate(&selection_anchors, buffer);
3707 self.take_rename(false, window, cx);
3708
3709 let newest_selection = self.selections.newest_anchor();
3710 let new_cursor_position = newest_selection.head();
3711 let selection_start = newest_selection.start;
3712
3713 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3714 self.push_to_nav_history(
3715 *old_cursor_position,
3716 Some(new_cursor_position.to_point(buffer)),
3717 false,
3718 effects.nav_history == Some(true),
3719 cx,
3720 );
3721 }
3722
3723 if local {
3724 if let Some((anchor, _)) = buffer.anchor_to_buffer_anchor(new_cursor_position) {
3725 self.register_buffer(anchor.buffer_id, cx);
3726 }
3727
3728 let mut context_menu = self.context_menu.borrow_mut();
3729 let completion_menu = match context_menu.as_ref() {
3730 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3731 Some(CodeContextMenu::CodeActions(_)) => {
3732 *context_menu = None;
3733 None
3734 }
3735 None => None,
3736 };
3737 let completion_position = completion_menu.map(|menu| menu.initial_position);
3738 drop(context_menu);
3739
3740 if effects.completions
3741 && let Some(completion_position) = completion_position
3742 {
3743 let start_offset = selection_start.to_offset(buffer);
3744 let position_matches = start_offset == completion_position.to_offset(buffer);
3745 let continue_showing = if let Some((snap, ..)) =
3746 buffer.point_to_buffer_offset(completion_position)
3747 && !snap.capability.editable()
3748 {
3749 false
3750 } else if position_matches {
3751 if self.snippet_stack.is_empty() {
3752 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3753 == Some(CharKind::Word)
3754 } else {
3755 // Snippet choices can be shown even when the cursor is in whitespace.
3756 // Dismissing the menu with actions like backspace is handled by
3757 // invalidation regions.
3758 true
3759 }
3760 } else {
3761 false
3762 };
3763
3764 if continue_showing {
3765 self.open_or_update_completions_menu(None, None, false, window, cx);
3766 } else {
3767 self.hide_context_menu(window, cx);
3768 }
3769 }
3770
3771 hide_hover(self, cx);
3772
3773 if old_cursor_position.to_display_point(&display_map).row()
3774 != new_cursor_position.to_display_point(&display_map).row()
3775 {
3776 self.available_code_actions.take();
3777 }
3778 self.refresh_code_actions(window, cx);
3779 self.refresh_document_highlights(cx);
3780 refresh_linked_ranges(self, window, cx);
3781
3782 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3783 self.refresh_matching_bracket_highlights(&display_map, cx);
3784 self.refresh_outline_symbols_at_cursor(cx);
3785 self.update_visible_edit_prediction(window, cx);
3786 self.inline_blame_popover.take();
3787 if self.git_blame_inline_enabled {
3788 self.start_inline_blame_timer(window, cx);
3789 }
3790 }
3791
3792 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3793
3794 if local && !self.suppress_selection_callback {
3795 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3796 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3797 callback(cursor_position, window, cx);
3798 }
3799 }
3800
3801 cx.emit(EditorEvent::SelectionsChanged { local });
3802
3803 let selections = &self.selections.disjoint_anchors_arc();
3804 if selections.len() == 1 {
3805 cx.emit(SearchEvent::ActiveMatchChanged)
3806 }
3807 if local && let Some(buffer_snapshot) = buffer.as_singleton() {
3808 let inmemory_selections = selections
3809 .iter()
3810 .map(|s| {
3811 let start = s.range().start.text_anchor_in(buffer_snapshot);
3812 let end = s.range().end.text_anchor_in(buffer_snapshot);
3813 (start..end).to_point(buffer_snapshot)
3814 })
3815 .collect();
3816 self.update_restoration_data(cx, |data| {
3817 data.selections = inmemory_selections;
3818 });
3819
3820 if WorkspaceSettings::get(None, cx).restore_on_startup
3821 != RestoreOnStartupBehavior::EmptyTab
3822 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3823 {
3824 let snapshot = self.buffer().read(cx).snapshot(cx);
3825 let selections = selections.clone();
3826 let background_executor = cx.background_executor().clone();
3827 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3828 let db = EditorDb::global(cx);
3829 self.serialize_selections = cx.background_spawn(async move {
3830 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3831 let db_selections = selections
3832 .iter()
3833 .map(|selection| {
3834 (
3835 selection.start.to_offset(&snapshot).0,
3836 selection.end.to_offset(&snapshot).0,
3837 )
3838 })
3839 .collect();
3840
3841 db.save_editor_selections(editor_id, workspace_id, db_selections)
3842 .await
3843 .with_context(|| {
3844 format!(
3845 "persisting editor selections for editor {editor_id}, \
3846 workspace {workspace_id:?}"
3847 )
3848 })
3849 .log_err();
3850 });
3851 }
3852 }
3853
3854 cx.notify();
3855 }
3856
3857 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3858 use text::ToOffset as _;
3859
3860 if self.mode.is_minimap()
3861 || WorkspaceSettings::get(None, cx).restore_on_startup
3862 == RestoreOnStartupBehavior::EmptyTab
3863 {
3864 return;
3865 }
3866
3867 let display_snapshot = self
3868 .display_map
3869 .update(cx, |display_map, cx| display_map.snapshot(cx));
3870 let Some(buffer_snapshot) = display_snapshot.buffer_snapshot().as_singleton() else {
3871 return;
3872 };
3873 let inmemory_folds = display_snapshot
3874 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3875 .map(|fold| {
3876 let start = fold.range.start.text_anchor_in(buffer_snapshot);
3877 let end = fold.range.end.text_anchor_in(buffer_snapshot);
3878 (start..end).to_point(buffer_snapshot)
3879 })
3880 .collect();
3881 self.update_restoration_data(cx, |data| {
3882 data.folds = inmemory_folds;
3883 });
3884
3885 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3886 return;
3887 };
3888
3889 // Get file path for path-based fold storage (survives tab close)
3890 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3891 project::File::from_dyn(buffer.read(cx).file())
3892 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3893 }) else {
3894 return;
3895 };
3896
3897 let background_executor = cx.background_executor().clone();
3898 const FINGERPRINT_LEN: usize = 32;
3899 let db_folds = display_snapshot
3900 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3901 .map(|fold| {
3902 let start = fold
3903 .range
3904 .start
3905 .text_anchor_in(buffer_snapshot)
3906 .to_offset(buffer_snapshot);
3907 let end = fold
3908 .range
3909 .end
3910 .text_anchor_in(buffer_snapshot)
3911 .to_offset(buffer_snapshot);
3912
3913 // Extract fingerprints - content at fold boundaries for validation on restore
3914 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3915 // content that might change independently.
3916 // start_fp: first min(32, fold_len) bytes of fold content
3917 // end_fp: last min(32, fold_len) bytes of fold content
3918 // Clip to character boundaries to handle multibyte UTF-8 characters.
3919 let fold_len = end - start;
3920 let start_fp_end = buffer_snapshot
3921 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3922 let start_fp: String = buffer_snapshot
3923 .text_for_range(start..start_fp_end)
3924 .collect();
3925 let end_fp_start = buffer_snapshot
3926 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3927 let end_fp: String = buffer_snapshot.text_for_range(end_fp_start..end).collect();
3928
3929 (start, end, start_fp, end_fp)
3930 })
3931 .collect::<Vec<_>>();
3932 let db = EditorDb::global(cx);
3933 self.serialize_folds = cx.background_spawn(async move {
3934 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3935 if db_folds.is_empty() {
3936 // No folds - delete any persisted folds for this file
3937 db.delete_file_folds(workspace_id, file_path)
3938 .await
3939 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3940 .log_err();
3941 } else {
3942 db.save_file_folds(workspace_id, file_path, db_folds)
3943 .await
3944 .with_context(|| {
3945 format!("persisting file folds for workspace {workspace_id:?}")
3946 })
3947 .log_err();
3948 }
3949 });
3950 }
3951
3952 pub fn sync_selections(
3953 &mut self,
3954 other: Entity<Editor>,
3955 cx: &mut Context<Self>,
3956 ) -> gpui::Subscription {
3957 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3958 if !other_selections.is_empty() {
3959 self.selections
3960 .change_with(&self.display_snapshot(cx), |selections| {
3961 selections.select_anchors(other_selections);
3962 });
3963 }
3964
3965 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3966 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3967 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3968 if other_selections.is_empty() {
3969 return;
3970 }
3971 let snapshot = this.display_snapshot(cx);
3972 this.selections.change_with(&snapshot, |selections| {
3973 selections.select_anchors(other_selections);
3974 });
3975 }
3976 });
3977
3978 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3979 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3980 let these_selections = this.selections.disjoint_anchors().to_vec();
3981 if these_selections.is_empty() {
3982 return;
3983 }
3984 other.update(cx, |other_editor, cx| {
3985 let snapshot = other_editor.display_snapshot(cx);
3986 other_editor
3987 .selections
3988 .change_with(&snapshot, |selections| {
3989 selections.select_anchors(these_selections);
3990 })
3991 });
3992 }
3993 });
3994
3995 Subscription::join(other_subscription, this_subscription)
3996 }
3997
3998 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3999 if self.buffer().read(cx).is_singleton() {
4000 return;
4001 }
4002 let snapshot = self.buffer.read(cx).snapshot(cx);
4003 let buffer_ids: HashSet<BufferId> = self
4004 .selections
4005 .disjoint_anchor_ranges()
4006 .flat_map(|range| snapshot.buffer_ids_for_range(range))
4007 .collect();
4008 for buffer_id in buffer_ids {
4009 self.unfold_buffer(buffer_id, cx);
4010 }
4011 }
4012
4013 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
4014 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
4015 /// effects of selection change occur at the end of the transaction.
4016 pub fn change_selections<R>(
4017 &mut self,
4018 effects: SelectionEffects,
4019 window: &mut Window,
4020 cx: &mut Context<Self>,
4021 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
4022 ) -> R {
4023 let snapshot = self.display_snapshot(cx);
4024 if let Some(state) = &mut self.deferred_selection_effects_state {
4025 state.effects.scroll = effects.scroll.or(state.effects.scroll);
4026 state.effects.completions = effects.completions;
4027 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
4028 let (changed, result) = self.selections.change_with(&snapshot, change);
4029 state.changed |= changed;
4030 return result;
4031 }
4032 let mut state = DeferredSelectionEffectsState {
4033 changed: false,
4034 effects,
4035 old_cursor_position: self.selections.newest_anchor().head(),
4036 history_entry: SelectionHistoryEntry {
4037 selections: self.selections.disjoint_anchors_arc(),
4038 select_next_state: self.select_next_state.clone(),
4039 select_prev_state: self.select_prev_state.clone(),
4040 add_selections_state: self.add_selections_state.clone(),
4041 },
4042 };
4043 let (changed, result) = self.selections.change_with(&snapshot, change);
4044 state.changed = state.changed || changed;
4045 if self.defer_selection_effects {
4046 self.deferred_selection_effects_state = Some(state);
4047 } else {
4048 self.apply_selection_effects(state, window, cx);
4049 }
4050 result
4051 }
4052
4053 /// Defers the effects of selection change, so that the effects of multiple calls to
4054 /// `change_selections` are applied at the end. This way these intermediate states aren't added
4055 /// to selection history and the state of popovers based on selection position aren't
4056 /// erroneously updated.
4057 pub fn with_selection_effects_deferred<R>(
4058 &mut self,
4059 window: &mut Window,
4060 cx: &mut Context<Self>,
4061 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
4062 ) -> R {
4063 let already_deferred = self.defer_selection_effects;
4064 self.defer_selection_effects = true;
4065 let result = update(self, window, cx);
4066 if !already_deferred {
4067 self.defer_selection_effects = false;
4068 if let Some(state) = self.deferred_selection_effects_state.take() {
4069 self.apply_selection_effects(state, window, cx);
4070 }
4071 }
4072 result
4073 }
4074
4075 fn apply_selection_effects(
4076 &mut self,
4077 state: DeferredSelectionEffectsState,
4078 window: &mut Window,
4079 cx: &mut Context<Self>,
4080 ) {
4081 if state.changed {
4082 self.selection_history.push(state.history_entry);
4083
4084 if let Some(autoscroll) = state.effects.scroll {
4085 self.request_autoscroll(autoscroll, cx);
4086 }
4087
4088 let old_cursor_position = &state.old_cursor_position;
4089
4090 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
4091
4092 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
4093 self.show_signature_help_auto(window, cx);
4094 }
4095 }
4096 }
4097
4098 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4099 where
4100 I: IntoIterator<Item = (Range<S>, T)>,
4101 S: ToOffset,
4102 T: Into<Arc<str>>,
4103 {
4104 if self.read_only(cx) {
4105 return;
4106 }
4107
4108 self.buffer
4109 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
4110 }
4111
4112 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4113 where
4114 I: IntoIterator<Item = (Range<S>, T)>,
4115 S: ToOffset,
4116 T: Into<Arc<str>>,
4117 {
4118 if self.read_only(cx) {
4119 return;
4120 }
4121
4122 self.buffer.update(cx, |buffer, cx| {
4123 buffer.edit(edits, self.autoindent_mode.clone(), cx)
4124 });
4125 }
4126
4127 pub fn edit_with_block_indent<I, S, T>(
4128 &mut self,
4129 edits: I,
4130 original_indent_columns: Vec<Option<u32>>,
4131 cx: &mut Context<Self>,
4132 ) where
4133 I: IntoIterator<Item = (Range<S>, T)>,
4134 S: ToOffset,
4135 T: Into<Arc<str>>,
4136 {
4137 if self.read_only(cx) {
4138 return;
4139 }
4140
4141 self.buffer.update(cx, |buffer, cx| {
4142 buffer.edit(
4143 edits,
4144 Some(AutoindentMode::Block {
4145 original_indent_columns,
4146 }),
4147 cx,
4148 )
4149 });
4150 }
4151
4152 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
4153 self.hide_context_menu(window, cx);
4154
4155 match phase {
4156 SelectPhase::Begin {
4157 position,
4158 add,
4159 click_count,
4160 } => self.begin_selection(position, add, click_count, window, cx),
4161 SelectPhase::BeginColumnar {
4162 position,
4163 goal_column,
4164 reset,
4165 mode,
4166 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4167 SelectPhase::Extend {
4168 position,
4169 click_count,
4170 } => self.extend_selection(position, click_count, window, cx),
4171 SelectPhase::Update {
4172 position,
4173 goal_column,
4174 scroll_delta,
4175 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4176 SelectPhase::End => self.end_selection(window, cx),
4177 }
4178 }
4179
4180 fn extend_selection(
4181 &mut self,
4182 position: DisplayPoint,
4183 click_count: usize,
4184 window: &mut Window,
4185 cx: &mut Context<Self>,
4186 ) {
4187 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4188 let tail = self
4189 .selections
4190 .newest::<MultiBufferOffset>(&display_map)
4191 .tail();
4192 let click_count = click_count.max(match self.selections.select_mode() {
4193 SelectMode::Character => 1,
4194 SelectMode::Word(_) => 2,
4195 SelectMode::Line(_) => 3,
4196 SelectMode::All => 4,
4197 });
4198 self.begin_selection(position, false, click_count, window, cx);
4199
4200 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4201
4202 let current_selection = match self.selections.select_mode() {
4203 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4204 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4205 };
4206
4207 let mut pending_selection = self
4208 .selections
4209 .pending_anchor()
4210 .cloned()
4211 .expect("extend_selection not called with pending selection");
4212
4213 if pending_selection
4214 .start
4215 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4216 == Ordering::Greater
4217 {
4218 pending_selection.start = current_selection.start;
4219 }
4220 if pending_selection
4221 .end
4222 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4223 == Ordering::Less
4224 {
4225 pending_selection.end = current_selection.end;
4226 pending_selection.reversed = true;
4227 }
4228
4229 let mut pending_mode = self.selections.pending_mode().unwrap();
4230 match &mut pending_mode {
4231 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4232 _ => {}
4233 }
4234
4235 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4236 SelectionEffects::scroll(Autoscroll::fit())
4237 } else {
4238 SelectionEffects::no_scroll()
4239 };
4240
4241 self.change_selections(effects, window, cx, |s| {
4242 s.set_pending(pending_selection.clone(), pending_mode);
4243 s.set_is_extending(true);
4244 });
4245 }
4246
4247 fn begin_selection(
4248 &mut self,
4249 position: DisplayPoint,
4250 add: bool,
4251 click_count: usize,
4252 window: &mut Window,
4253 cx: &mut Context<Self>,
4254 ) {
4255 if !self.focus_handle.is_focused(window) {
4256 self.last_focused_descendant = None;
4257 window.focus(&self.focus_handle, cx);
4258 }
4259
4260 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4261 let buffer = display_map.buffer_snapshot();
4262 let position = display_map.clip_point(position, Bias::Left);
4263
4264 let start;
4265 let end;
4266 let mode;
4267 let mut auto_scroll;
4268 match click_count {
4269 1 => {
4270 start = buffer.anchor_before(position.to_point(&display_map));
4271 end = start;
4272 mode = SelectMode::Character;
4273 auto_scroll = true;
4274 }
4275 2 => {
4276 let position = display_map
4277 .clip_point(position, Bias::Left)
4278 .to_offset(&display_map, Bias::Left);
4279 let (range, _) = buffer.surrounding_word(position, None);
4280 start = buffer.anchor_before(range.start);
4281 end = buffer.anchor_before(range.end);
4282 mode = SelectMode::Word(start..end);
4283 auto_scroll = true;
4284 }
4285 3 => {
4286 let position = display_map
4287 .clip_point(position, Bias::Left)
4288 .to_point(&display_map);
4289 let line_start = display_map.prev_line_boundary(position).0;
4290 let next_line_start = buffer.clip_point(
4291 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4292 Bias::Left,
4293 );
4294 start = buffer.anchor_before(line_start);
4295 end = buffer.anchor_before(next_line_start);
4296 mode = SelectMode::Line(start..end);
4297 auto_scroll = true;
4298 }
4299 _ => {
4300 start = buffer.anchor_before(MultiBufferOffset(0));
4301 end = buffer.anchor_before(buffer.len());
4302 mode = SelectMode::All;
4303 auto_scroll = false;
4304 }
4305 }
4306 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4307
4308 let point_to_delete: Option<usize> = {
4309 let selected_points: Vec<Selection<Point>> =
4310 self.selections.disjoint_in_range(start..end, &display_map);
4311
4312 if !add || click_count > 1 {
4313 None
4314 } else if !selected_points.is_empty() {
4315 Some(selected_points[0].id)
4316 } else {
4317 let clicked_point_already_selected =
4318 self.selections.disjoint_anchors().iter().find(|selection| {
4319 selection.start.to_point(buffer) == start.to_point(buffer)
4320 || selection.end.to_point(buffer) == end.to_point(buffer)
4321 });
4322
4323 clicked_point_already_selected.map(|selection| selection.id)
4324 }
4325 };
4326
4327 let selections_count = self.selections.count();
4328 let effects = if auto_scroll {
4329 SelectionEffects::default()
4330 } else {
4331 SelectionEffects::no_scroll()
4332 };
4333
4334 self.change_selections(effects, window, cx, |s| {
4335 if let Some(point_to_delete) = point_to_delete {
4336 s.delete(point_to_delete);
4337
4338 if selections_count == 1 {
4339 s.set_pending_anchor_range(start..end, mode);
4340 }
4341 } else {
4342 if !add {
4343 s.clear_disjoint();
4344 }
4345
4346 s.set_pending_anchor_range(start..end, mode);
4347 }
4348 });
4349 }
4350
4351 fn begin_columnar_selection(
4352 &mut self,
4353 position: DisplayPoint,
4354 goal_column: u32,
4355 reset: bool,
4356 mode: ColumnarMode,
4357 window: &mut Window,
4358 cx: &mut Context<Self>,
4359 ) {
4360 if !self.focus_handle.is_focused(window) {
4361 self.last_focused_descendant = None;
4362 window.focus(&self.focus_handle, cx);
4363 }
4364
4365 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4366
4367 if reset {
4368 let pointer_position = display_map
4369 .buffer_snapshot()
4370 .anchor_before(position.to_point(&display_map));
4371
4372 self.change_selections(
4373 SelectionEffects::scroll(Autoscroll::newest()),
4374 window,
4375 cx,
4376 |s| {
4377 s.clear_disjoint();
4378 s.set_pending_anchor_range(
4379 pointer_position..pointer_position,
4380 SelectMode::Character,
4381 );
4382 },
4383 );
4384 };
4385
4386 let tail = self.selections.newest::<Point>(&display_map).tail();
4387 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4388 self.columnar_selection_state = match mode {
4389 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4390 selection_tail: selection_anchor,
4391 display_point: if reset {
4392 if position.column() != goal_column {
4393 Some(DisplayPoint::new(position.row(), goal_column))
4394 } else {
4395 None
4396 }
4397 } else {
4398 None
4399 },
4400 }),
4401 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4402 selection_tail: selection_anchor,
4403 }),
4404 };
4405
4406 if !reset {
4407 self.select_columns(position, goal_column, &display_map, window, cx);
4408 }
4409 }
4410
4411 fn update_selection(
4412 &mut self,
4413 position: DisplayPoint,
4414 goal_column: u32,
4415 scroll_delta: gpui::Point<f32>,
4416 window: &mut Window,
4417 cx: &mut Context<Self>,
4418 ) {
4419 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4420
4421 if self.columnar_selection_state.is_some() {
4422 self.select_columns(position, goal_column, &display_map, window, cx);
4423 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4424 let buffer = display_map.buffer_snapshot();
4425 let head;
4426 let tail;
4427 let mode = self.selections.pending_mode().unwrap();
4428 match &mode {
4429 SelectMode::Character => {
4430 head = position.to_point(&display_map);
4431 tail = pending.tail().to_point(buffer);
4432 }
4433 SelectMode::Word(original_range) => {
4434 let offset = display_map
4435 .clip_point(position, Bias::Left)
4436 .to_offset(&display_map, Bias::Left);
4437 let original_range = original_range.to_offset(buffer);
4438
4439 let head_offset = if buffer.is_inside_word(offset, None)
4440 || original_range.contains(&offset)
4441 {
4442 let (word_range, _) = buffer.surrounding_word(offset, None);
4443 if word_range.start < original_range.start {
4444 word_range.start
4445 } else {
4446 word_range.end
4447 }
4448 } else {
4449 offset
4450 };
4451
4452 head = head_offset.to_point(buffer);
4453 if head_offset <= original_range.start {
4454 tail = original_range.end.to_point(buffer);
4455 } else {
4456 tail = original_range.start.to_point(buffer);
4457 }
4458 }
4459 SelectMode::Line(original_range) => {
4460 let original_range = original_range.to_point(display_map.buffer_snapshot());
4461
4462 let position = display_map
4463 .clip_point(position, Bias::Left)
4464 .to_point(&display_map);
4465 let line_start = display_map.prev_line_boundary(position).0;
4466 let next_line_start = buffer.clip_point(
4467 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4468 Bias::Left,
4469 );
4470
4471 if line_start < original_range.start {
4472 head = line_start
4473 } else {
4474 head = next_line_start
4475 }
4476
4477 if head <= original_range.start {
4478 tail = original_range.end;
4479 } else {
4480 tail = original_range.start;
4481 }
4482 }
4483 SelectMode::All => {
4484 return;
4485 }
4486 };
4487
4488 if head < tail {
4489 pending.start = buffer.anchor_before(head);
4490 pending.end = buffer.anchor_before(tail);
4491 pending.reversed = true;
4492 } else {
4493 pending.start = buffer.anchor_before(tail);
4494 pending.end = buffer.anchor_before(head);
4495 pending.reversed = false;
4496 }
4497
4498 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4499 s.set_pending(pending.clone(), mode);
4500 });
4501 } else {
4502 log::error!("update_selection dispatched with no pending selection");
4503 return;
4504 }
4505
4506 self.apply_scroll_delta(scroll_delta, window, cx);
4507 cx.notify();
4508 }
4509
4510 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4511 self.columnar_selection_state.take();
4512 if let Some(pending_mode) = self.selections.pending_mode() {
4513 let selections = self
4514 .selections
4515 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4516 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4517 s.select(selections);
4518 s.clear_pending();
4519 if s.is_extending() {
4520 s.set_is_extending(false);
4521 } else {
4522 s.set_select_mode(pending_mode);
4523 }
4524 });
4525 }
4526 }
4527
4528 fn select_columns(
4529 &mut self,
4530 head: DisplayPoint,
4531 goal_column: u32,
4532 display_map: &DisplaySnapshot,
4533 window: &mut Window,
4534 cx: &mut Context<Self>,
4535 ) {
4536 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4537 return;
4538 };
4539
4540 let tail = match columnar_state {
4541 ColumnarSelectionState::FromMouse {
4542 selection_tail,
4543 display_point,
4544 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4545 ColumnarSelectionState::FromSelection { selection_tail } => {
4546 selection_tail.to_display_point(display_map)
4547 }
4548 };
4549
4550 let start_row = cmp::min(tail.row(), head.row());
4551 let end_row = cmp::max(tail.row(), head.row());
4552 let start_column = cmp::min(tail.column(), goal_column);
4553 let end_column = cmp::max(tail.column(), goal_column);
4554 let reversed = start_column < tail.column();
4555
4556 let selection_ranges = (start_row.0..=end_row.0)
4557 .map(DisplayRow)
4558 .filter_map(|row| {
4559 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4560 || start_column <= display_map.line_len(row))
4561 && !display_map.is_block_line(row)
4562 {
4563 let start = display_map
4564 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4565 .to_point(display_map);
4566 let end = display_map
4567 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4568 .to_point(display_map);
4569 if reversed {
4570 Some(end..start)
4571 } else {
4572 Some(start..end)
4573 }
4574 } else {
4575 None
4576 }
4577 })
4578 .collect::<Vec<_>>();
4579 if selection_ranges.is_empty() {
4580 return;
4581 }
4582
4583 let ranges = match columnar_state {
4584 ColumnarSelectionState::FromMouse { .. } => {
4585 let mut non_empty_ranges = selection_ranges
4586 .iter()
4587 .filter(|selection_range| selection_range.start != selection_range.end)
4588 .peekable();
4589 if non_empty_ranges.peek().is_some() {
4590 non_empty_ranges.cloned().collect()
4591 } else {
4592 selection_ranges
4593 }
4594 }
4595 _ => selection_ranges,
4596 };
4597
4598 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4599 s.select_ranges(ranges);
4600 });
4601 cx.notify();
4602 }
4603
4604 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4605 self.selections
4606 .all_adjusted(snapshot)
4607 .iter()
4608 .any(|selection| !selection.is_empty())
4609 }
4610
4611 pub fn has_pending_nonempty_selection(&self) -> bool {
4612 let pending_nonempty_selection = match self.selections.pending_anchor() {
4613 Some(Selection { start, end, .. }) => start != end,
4614 None => false,
4615 };
4616
4617 pending_nonempty_selection
4618 || (self.columnar_selection_state.is_some()
4619 && self.selections.disjoint_anchors().len() > 1)
4620 }
4621
4622 pub fn has_pending_selection(&self) -> bool {
4623 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4624 }
4625
4626 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4627 self.selection_mark_mode = false;
4628 self.selection_drag_state = SelectionDragState::None;
4629
4630 if self.dismiss_menus_and_popups(true, window, cx) {
4631 cx.notify();
4632 return;
4633 }
4634 if self.clear_expanded_diff_hunks(cx) {
4635 cx.notify();
4636 return;
4637 }
4638 if self.show_git_blame_gutter {
4639 self.show_git_blame_gutter = false;
4640 cx.notify();
4641 return;
4642 }
4643
4644 if self.mode.is_full()
4645 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4646 {
4647 cx.notify();
4648 return;
4649 }
4650
4651 cx.propagate();
4652 }
4653
4654 pub fn dismiss_menus_and_popups(
4655 &mut self,
4656 is_user_requested: bool,
4657 window: &mut Window,
4658 cx: &mut Context<Self>,
4659 ) -> bool {
4660 let mut dismissed = false;
4661
4662 dismissed |= self.take_rename(false, window, cx).is_some();
4663 dismissed |= self.hide_blame_popover(true, cx);
4664 dismissed |= hide_hover(self, cx);
4665 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4666 dismissed |= self.hide_context_menu(window, cx).is_some();
4667 dismissed |= self.mouse_context_menu.take().is_some();
4668 dismissed |= is_user_requested
4669 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4670 dismissed |= self.snippet_stack.pop().is_some();
4671 if self.diff_review_drag_state.is_some() {
4672 self.cancel_diff_review_drag(cx);
4673 dismissed = true;
4674 }
4675 if !self.diff_review_overlays.is_empty() {
4676 self.dismiss_all_diff_review_overlays(cx);
4677 dismissed = true;
4678 }
4679
4680 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4681 self.dismiss_diagnostics(cx);
4682 dismissed = true;
4683 }
4684
4685 dismissed
4686 }
4687
4688 fn linked_editing_ranges_for(
4689 &self,
4690 query_range: Range<text::Anchor>,
4691 cx: &App,
4692 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4693 use text::ToOffset as TO;
4694
4695 if self.linked_edit_ranges.is_empty() {
4696 return None;
4697 }
4698 if query_range.start.buffer_id != query_range.end.buffer_id {
4699 return None;
4700 };
4701 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
4702 let buffer = self.buffer.read(cx).buffer(query_range.end.buffer_id)?;
4703 let buffer_snapshot = buffer.read(cx).snapshot();
4704 let (base_range, linked_ranges) = self.linked_edit_ranges.get(
4705 buffer_snapshot.remote_id(),
4706 query_range.clone(),
4707 &buffer_snapshot,
4708 )?;
4709 // find offset from the start of current range to current cursor position
4710 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4711
4712 let start_offset = TO::to_offset(&query_range.start, &buffer_snapshot);
4713 let start_difference = start_offset - start_byte_offset;
4714 let end_offset = TO::to_offset(&query_range.end, &buffer_snapshot);
4715 let end_difference = end_offset - start_byte_offset;
4716
4717 // Current range has associated linked ranges.
4718 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4719 for range in linked_ranges.iter() {
4720 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4721 let end_offset = start_offset + end_difference;
4722 let start_offset = start_offset + start_difference;
4723 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4724 continue;
4725 }
4726 if self.selections.disjoint_anchor_ranges().any(|s| {
4727 let Some((selection_start, _)) =
4728 multibuffer_snapshot.anchor_to_buffer_anchor(s.start)
4729 else {
4730 return false;
4731 };
4732 let Some((selection_end, _)) = multibuffer_snapshot.anchor_to_buffer_anchor(s.end)
4733 else {
4734 return false;
4735 };
4736 if selection_start.buffer_id != query_range.start.buffer_id
4737 || selection_end.buffer_id != query_range.end.buffer_id
4738 {
4739 return false;
4740 }
4741 TO::to_offset(&selection_start, &buffer_snapshot) <= end_offset
4742 && TO::to_offset(&selection_end, &buffer_snapshot) >= start_offset
4743 }) {
4744 continue;
4745 }
4746 let start = buffer_snapshot.anchor_after(start_offset);
4747 let end = buffer_snapshot.anchor_after(end_offset);
4748 linked_edits
4749 .entry(buffer.clone())
4750 .or_default()
4751 .push(start..end);
4752 }
4753 Some(linked_edits)
4754 }
4755
4756 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4757 let text: Arc<str> = text.into();
4758
4759 if self.read_only(cx) {
4760 return;
4761 }
4762
4763 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4764
4765 self.unfold_buffers_with_selections(cx);
4766
4767 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4768 let mut bracket_inserted = false;
4769 let mut edits = Vec::new();
4770 let mut linked_edits = LinkedEdits::new();
4771 let mut new_selections = Vec::with_capacity(selections.len());
4772 let mut new_autoclose_regions = Vec::new();
4773 let snapshot = self.buffer.read(cx).read(cx);
4774 let mut clear_linked_edit_ranges = false;
4775 let mut all_selections_read_only = true;
4776 let mut has_adjacent_edits = false;
4777 let mut in_adjacent_group = false;
4778
4779 let mut regions = self
4780 .selections_with_autoclose_regions(selections, &snapshot)
4781 .peekable();
4782
4783 while let Some((selection, autoclose_region)) = regions.next() {
4784 if snapshot
4785 .point_to_buffer_point(selection.head())
4786 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4787 {
4788 continue;
4789 }
4790 if snapshot
4791 .point_to_buffer_point(selection.tail())
4792 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4793 {
4794 // note, ideally we'd clip the tail to the closest writeable region towards the head
4795 continue;
4796 }
4797 all_selections_read_only = false;
4798
4799 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4800 // Determine if the inserted text matches the opening or closing
4801 // bracket of any of this language's bracket pairs.
4802 let mut bracket_pair = None;
4803 let mut is_bracket_pair_start = false;
4804 let mut is_bracket_pair_end = false;
4805 if !text.is_empty() {
4806 let mut bracket_pair_matching_end = None;
4807 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4808 // and they are removing the character that triggered IME popup.
4809 for (pair, enabled) in scope.brackets() {
4810 if !pair.close && !pair.surround {
4811 continue;
4812 }
4813
4814 if enabled && pair.start.ends_with(text.as_ref()) {
4815 let prefix_len = pair.start.len() - text.len();
4816 let preceding_text_matches_prefix = prefix_len == 0
4817 || (selection.start.column >= (prefix_len as u32)
4818 && snapshot.contains_str_at(
4819 Point::new(
4820 selection.start.row,
4821 selection.start.column - (prefix_len as u32),
4822 ),
4823 &pair.start[..prefix_len],
4824 ));
4825 if preceding_text_matches_prefix {
4826 bracket_pair = Some(pair.clone());
4827 is_bracket_pair_start = true;
4828 break;
4829 }
4830 }
4831 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4832 {
4833 // take first bracket pair matching end, but don't break in case a later bracket
4834 // pair matches start
4835 bracket_pair_matching_end = Some(pair.clone());
4836 }
4837 }
4838 if let Some(end) = bracket_pair_matching_end
4839 && bracket_pair.is_none()
4840 {
4841 bracket_pair = Some(end);
4842 is_bracket_pair_end = true;
4843 }
4844 }
4845
4846 if let Some(bracket_pair) = bracket_pair {
4847 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4848 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4849 let auto_surround =
4850 self.use_auto_surround && snapshot_settings.use_auto_surround;
4851 if selection.is_empty() {
4852 if is_bracket_pair_start {
4853 // If the inserted text is a suffix of an opening bracket and the
4854 // selection is preceded by the rest of the opening bracket, then
4855 // insert the closing bracket.
4856 let following_text_allows_autoclose = snapshot
4857 .chars_at(selection.start)
4858 .next()
4859 .is_none_or(|c| scope.should_autoclose_before(c));
4860
4861 let preceding_text_allows_autoclose = selection.start.column == 0
4862 || snapshot
4863 .reversed_chars_at(selection.start)
4864 .next()
4865 .is_none_or(|c| {
4866 bracket_pair.start != bracket_pair.end
4867 || !snapshot
4868 .char_classifier_at(selection.start)
4869 .is_word(c)
4870 });
4871
4872 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4873 && bracket_pair.start.len() == 1
4874 {
4875 let target = bracket_pair.start.chars().next().unwrap();
4876 let mut byte_offset = 0u32;
4877 let current_line_count = snapshot
4878 .reversed_chars_at(selection.start)
4879 .take_while(|&c| c != '\n')
4880 .filter(|c| {
4881 byte_offset += c.len_utf8() as u32;
4882 if *c != target {
4883 return false;
4884 }
4885
4886 let point = Point::new(
4887 selection.start.row,
4888 selection.start.column.saturating_sub(byte_offset),
4889 );
4890
4891 let is_enabled = snapshot
4892 .language_scope_at(point)
4893 .and_then(|scope| {
4894 scope
4895 .brackets()
4896 .find(|(pair, _)| {
4897 pair.start == bracket_pair.start
4898 })
4899 .map(|(_, enabled)| enabled)
4900 })
4901 .unwrap_or(true);
4902
4903 let is_delimiter = snapshot
4904 .language_scope_at(Point::new(
4905 point.row,
4906 point.column + 1,
4907 ))
4908 .and_then(|scope| {
4909 scope
4910 .brackets()
4911 .find(|(pair, _)| {
4912 pair.start == bracket_pair.start
4913 })
4914 .map(|(_, enabled)| !enabled)
4915 })
4916 .unwrap_or(false);
4917
4918 is_enabled && !is_delimiter
4919 })
4920 .count();
4921 current_line_count % 2 == 1
4922 } else {
4923 false
4924 };
4925
4926 if autoclose
4927 && bracket_pair.close
4928 && following_text_allows_autoclose
4929 && preceding_text_allows_autoclose
4930 && !is_closing_quote
4931 {
4932 let anchor = snapshot.anchor_before(selection.end);
4933 new_selections.push((selection.map(|_| anchor), text.len()));
4934 new_autoclose_regions.push((
4935 anchor,
4936 text.len(),
4937 selection.id,
4938 bracket_pair.clone(),
4939 ));
4940 edits.push((
4941 selection.range(),
4942 format!("{}{}", text, bracket_pair.end).into(),
4943 ));
4944 bracket_inserted = true;
4945 continue;
4946 }
4947 }
4948
4949 if let Some(region) = autoclose_region {
4950 // If the selection is followed by an auto-inserted closing bracket,
4951 // then don't insert that closing bracket again; just move the selection
4952 // past the closing bracket.
4953 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4954 && text.as_ref() == region.pair.end.as_str()
4955 && snapshot.contains_str_at(region.range.end, text.as_ref());
4956 if should_skip {
4957 let anchor = snapshot.anchor_after(selection.end);
4958 new_selections
4959 .push((selection.map(|_| anchor), region.pair.end.len()));
4960 continue;
4961 }
4962 }
4963
4964 let always_treat_brackets_as_autoclosed = snapshot
4965 .language_settings_at(selection.start, cx)
4966 .always_treat_brackets_as_autoclosed;
4967 if always_treat_brackets_as_autoclosed
4968 && is_bracket_pair_end
4969 && snapshot.contains_str_at(selection.end, text.as_ref())
4970 {
4971 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4972 // and the inserted text is a closing bracket and the selection is followed
4973 // by the closing bracket then move the selection past the closing bracket.
4974 let anchor = snapshot.anchor_after(selection.end);
4975 new_selections.push((selection.map(|_| anchor), text.len()));
4976 continue;
4977 }
4978 }
4979 // If an opening bracket is 1 character long and is typed while
4980 // text is selected, then surround that text with the bracket pair.
4981 else if auto_surround
4982 && bracket_pair.surround
4983 && is_bracket_pair_start
4984 && bracket_pair.start.chars().count() == 1
4985 {
4986 edits.push((selection.start..selection.start, text.clone()));
4987 edits.push((
4988 selection.end..selection.end,
4989 bracket_pair.end.as_str().into(),
4990 ));
4991 bracket_inserted = true;
4992 new_selections.push((
4993 Selection {
4994 id: selection.id,
4995 start: snapshot.anchor_after(selection.start),
4996 end: snapshot.anchor_before(selection.end),
4997 reversed: selection.reversed,
4998 goal: selection.goal,
4999 },
5000 0,
5001 ));
5002 continue;
5003 }
5004 }
5005 }
5006
5007 if self.auto_replace_emoji_shortcode
5008 && selection.is_empty()
5009 && text.as_ref().ends_with(':')
5010 && let Some(possible_emoji_short_code) =
5011 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
5012 && !possible_emoji_short_code.is_empty()
5013 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
5014 {
5015 let emoji_shortcode_start = Point::new(
5016 selection.start.row,
5017 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
5018 );
5019
5020 // Remove shortcode from buffer
5021 edits.push((
5022 emoji_shortcode_start..selection.start,
5023 "".to_string().into(),
5024 ));
5025 new_selections.push((
5026 Selection {
5027 id: selection.id,
5028 start: snapshot.anchor_after(emoji_shortcode_start),
5029 end: snapshot.anchor_before(selection.start),
5030 reversed: selection.reversed,
5031 goal: selection.goal,
5032 },
5033 0,
5034 ));
5035
5036 // Insert emoji
5037 let selection_start_anchor = snapshot.anchor_after(selection.start);
5038 new_selections.push((selection.map(|_| selection_start_anchor), 0));
5039 edits.push((selection.start..selection.end, emoji.to_string().into()));
5040
5041 continue;
5042 }
5043
5044 let next_is_adjacent = regions
5045 .peek()
5046 .is_some_and(|(next, _)| selection.end == next.start);
5047
5048 // If not handling any auto-close operation, then just replace the selected
5049 // text with the given input and move the selection to the end of the
5050 // newly inserted text.
5051 let anchor = if in_adjacent_group || next_is_adjacent {
5052 // After edits the right bias would shift those anchor to the next visible fragment
5053 // but we want to resolve to the previous one
5054 snapshot.anchor_before(selection.end)
5055 } else {
5056 snapshot.anchor_after(selection.end)
5057 };
5058
5059 if !self.linked_edit_ranges.is_empty() {
5060 let start_anchor = snapshot.anchor_before(selection.start);
5061 let classifier = snapshot
5062 .char_classifier_at(start_anchor)
5063 .scope_context(Some(CharScopeContext::LinkedEdit));
5064
5065 if let Some((_, anchor_range)) =
5066 snapshot.anchor_range_to_buffer_anchor_range(start_anchor..anchor)
5067 {
5068 let is_word_char = text
5069 .chars()
5070 .next()
5071 .is_none_or(|char| classifier.is_word(char));
5072
5073 let is_dot = text.as_ref() == ".";
5074 let should_apply_linked_edit = is_word_char || is_dot;
5075
5076 if should_apply_linked_edit {
5077 linked_edits.push(&self, anchor_range, text.clone(), cx);
5078 } else {
5079 clear_linked_edit_ranges = true;
5080 }
5081 }
5082 }
5083
5084 new_selections.push((selection.map(|_| anchor), 0));
5085 edits.push((selection.start..selection.end, text.clone()));
5086
5087 has_adjacent_edits |= next_is_adjacent;
5088 in_adjacent_group = next_is_adjacent;
5089 }
5090
5091 if all_selections_read_only {
5092 return;
5093 }
5094
5095 drop(regions);
5096 drop(snapshot);
5097
5098 self.transact(window, cx, |this, window, cx| {
5099 if clear_linked_edit_ranges {
5100 this.linked_edit_ranges.clear();
5101 }
5102 let initial_buffer_versions =
5103 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
5104
5105 this.buffer.update(cx, |buffer, cx| {
5106 if has_adjacent_edits {
5107 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
5108 } else {
5109 buffer.edit(edits, this.autoindent_mode.clone(), cx);
5110 }
5111 });
5112 linked_edits.apply(cx);
5113 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
5114 let new_selection_deltas = new_selections.iter().map(|e| e.1);
5115 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5116 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
5117 new_anchor_selections,
5118 &map,
5119 )
5120 .zip(new_selection_deltas)
5121 .map(|(selection, delta)| Selection {
5122 id: selection.id,
5123 start: selection.start + delta,
5124 end: selection.end + delta,
5125 reversed: selection.reversed,
5126 goal: SelectionGoal::None,
5127 })
5128 .collect::<Vec<_>>();
5129
5130 let mut i = 0;
5131 for (position, delta, selection_id, pair) in new_autoclose_regions {
5132 let position = position.to_offset(map.buffer_snapshot()) + delta;
5133 let start = map.buffer_snapshot().anchor_before(position);
5134 let end = map.buffer_snapshot().anchor_after(position);
5135 while let Some(existing_state) = this.autoclose_regions.get(i) {
5136 match existing_state
5137 .range
5138 .start
5139 .cmp(&start, map.buffer_snapshot())
5140 {
5141 Ordering::Less => i += 1,
5142 Ordering::Greater => break,
5143 Ordering::Equal => {
5144 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
5145 Ordering::Less => i += 1,
5146 Ordering::Equal => break,
5147 Ordering::Greater => break,
5148 }
5149 }
5150 }
5151 }
5152 this.autoclose_regions.insert(
5153 i,
5154 AutocloseRegion {
5155 selection_id,
5156 range: start..end,
5157 pair,
5158 },
5159 );
5160 }
5161
5162 let had_active_edit_prediction = this.has_active_edit_prediction();
5163 this.change_selections(
5164 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
5165 window,
5166 cx,
5167 |s| s.select(new_selections),
5168 );
5169
5170 if !bracket_inserted
5171 && let Some(on_type_format_task) =
5172 this.trigger_on_type_formatting(text.to_string(), window, cx)
5173 {
5174 on_type_format_task.detach_and_log_err(cx);
5175 }
5176
5177 let editor_settings = EditorSettings::get_global(cx);
5178 if bracket_inserted
5179 && (editor_settings.auto_signature_help
5180 || editor_settings.show_signature_help_after_edits)
5181 {
5182 this.show_signature_help(&ShowSignatureHelp, window, cx);
5183 }
5184
5185 let trigger_in_words =
5186 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5187 if this.hard_wrap.is_some() {
5188 let latest: Range<Point> = this.selections.newest(&map).range();
5189 if latest.is_empty()
5190 && this
5191 .buffer()
5192 .read(cx)
5193 .snapshot(cx)
5194 .line_len(MultiBufferRow(latest.start.row))
5195 == latest.start.column
5196 {
5197 this.rewrap_impl(
5198 RewrapOptions {
5199 override_language_settings: true,
5200 preserve_existing_whitespace: true,
5201 line_length: None,
5202 },
5203 cx,
5204 )
5205 }
5206 }
5207 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5208 refresh_linked_ranges(this, window, cx);
5209 this.refresh_edit_prediction(true, false, window, cx);
5210 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5211 });
5212 }
5213
5214 fn find_possible_emoji_shortcode_at_position(
5215 snapshot: &MultiBufferSnapshot,
5216 position: Point,
5217 ) -> Option<String> {
5218 let mut chars = Vec::new();
5219 let mut found_colon = false;
5220 for char in snapshot.reversed_chars_at(position).take(100) {
5221 // Found a possible emoji shortcode in the middle of the buffer
5222 if found_colon {
5223 if char.is_whitespace() {
5224 chars.reverse();
5225 return Some(chars.iter().collect());
5226 }
5227 // If the previous character is not a whitespace, we are in the middle of a word
5228 // and we only want to complete the shortcode if the word is made up of other emojis
5229 let mut containing_word = String::new();
5230 for ch in snapshot
5231 .reversed_chars_at(position)
5232 .skip(chars.len() + 1)
5233 .take(100)
5234 {
5235 if ch.is_whitespace() {
5236 break;
5237 }
5238 containing_word.push(ch);
5239 }
5240 let containing_word = containing_word.chars().rev().collect::<String>();
5241 if util::word_consists_of_emojis(containing_word.as_str()) {
5242 chars.reverse();
5243 return Some(chars.iter().collect());
5244 }
5245 }
5246
5247 if char.is_whitespace() || !char.is_ascii() {
5248 return None;
5249 }
5250 if char == ':' {
5251 found_colon = true;
5252 } else {
5253 chars.push(char);
5254 }
5255 }
5256 // Found a possible emoji shortcode at the beginning of the buffer
5257 chars.reverse();
5258 Some(chars.iter().collect())
5259 }
5260
5261 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5262 if self.read_only(cx) {
5263 return;
5264 }
5265
5266 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5267 self.transact(window, cx, |this, window, cx| {
5268 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5269 let selections = this
5270 .selections
5271 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5272 let multi_buffer = this.buffer.read(cx);
5273 let buffer = multi_buffer.snapshot(cx);
5274 selections
5275 .iter()
5276 .map(|selection| {
5277 let start_point = selection.start.to_point(&buffer);
5278 let mut existing_indent =
5279 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5280 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5281 let start = selection.start;
5282 let end = selection.end;
5283 let selection_is_empty = start == end;
5284 let language_scope = buffer.language_scope_at(start);
5285 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5286 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5287 &buffer,
5288 start..end,
5289 language,
5290 )
5291 || NewlineConfig::insert_extra_newline_tree_sitter(
5292 &buffer,
5293 start..end,
5294 );
5295
5296 let mut newline_config = NewlineConfig::Newline {
5297 additional_indent: IndentSize::spaces(0),
5298 extra_line_additional_indent: if needs_extra_newline {
5299 Some(IndentSize::spaces(0))
5300 } else {
5301 None
5302 },
5303 prevent_auto_indent: false,
5304 };
5305
5306 let comment_delimiter = maybe!({
5307 if !selection_is_empty {
5308 return None;
5309 }
5310
5311 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5312 return None;
5313 }
5314
5315 return comment_delimiter_for_newline(
5316 &start_point,
5317 &buffer,
5318 language,
5319 );
5320 });
5321
5322 let doc_delimiter = maybe!({
5323 if !selection_is_empty {
5324 return None;
5325 }
5326
5327 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5328 return None;
5329 }
5330
5331 return documentation_delimiter_for_newline(
5332 &start_point,
5333 &buffer,
5334 language,
5335 &mut newline_config,
5336 );
5337 });
5338
5339 let list_delimiter = maybe!({
5340 if !selection_is_empty {
5341 return None;
5342 }
5343
5344 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5345 return None;
5346 }
5347
5348 return list_delimiter_for_newline(
5349 &start_point,
5350 &buffer,
5351 language,
5352 &mut newline_config,
5353 );
5354 });
5355
5356 (
5357 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5358 newline_config,
5359 )
5360 } else {
5361 (
5362 None,
5363 NewlineConfig::Newline {
5364 additional_indent: IndentSize::spaces(0),
5365 extra_line_additional_indent: None,
5366 prevent_auto_indent: false,
5367 },
5368 )
5369 };
5370
5371 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5372 NewlineConfig::ClearCurrentLine => {
5373 let row_start =
5374 buffer.point_to_offset(Point::new(start_point.row, 0));
5375 (row_start, String::new(), false)
5376 }
5377 NewlineConfig::UnindentCurrentLine { continuation } => {
5378 let row_start =
5379 buffer.point_to_offset(Point::new(start_point.row, 0));
5380 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5381 let tab_size_indent = IndentSize::spaces(tab_size.get());
5382 let reduced_indent =
5383 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5384 let mut new_text = String::new();
5385 new_text.extend(reduced_indent.chars());
5386 new_text.push_str(continuation);
5387 (row_start, new_text, true)
5388 }
5389 NewlineConfig::Newline {
5390 additional_indent,
5391 extra_line_additional_indent,
5392 prevent_auto_indent,
5393 } => {
5394 let auto_indent_mode =
5395 buffer.language_settings_at(start, cx).auto_indent;
5396 let preserve_indent =
5397 auto_indent_mode != language::AutoIndentMode::None;
5398 let apply_syntax_indent =
5399 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5400 let capacity_for_delimiter =
5401 delimiter.as_deref().map(str::len).unwrap_or_default();
5402 let existing_indent_len = if preserve_indent {
5403 existing_indent.len as usize
5404 } else {
5405 0
5406 };
5407 let extra_line_len = extra_line_additional_indent
5408 .map(|i| 1 + existing_indent_len + i.len as usize)
5409 .unwrap_or(0);
5410 let mut new_text = String::with_capacity(
5411 1 + capacity_for_delimiter
5412 + existing_indent_len
5413 + additional_indent.len as usize
5414 + extra_line_len,
5415 );
5416 new_text.push('\n');
5417 if preserve_indent {
5418 new_text.extend(existing_indent.chars());
5419 }
5420 new_text.extend(additional_indent.chars());
5421 if let Some(delimiter) = &delimiter {
5422 new_text.push_str(delimiter);
5423 }
5424 if let Some(extra_indent) = extra_line_additional_indent {
5425 new_text.push('\n');
5426 if preserve_indent {
5427 new_text.extend(existing_indent.chars());
5428 }
5429 new_text.extend(extra_indent.chars());
5430 }
5431 (
5432 start,
5433 new_text,
5434 *prevent_auto_indent || !apply_syntax_indent,
5435 )
5436 }
5437 };
5438
5439 let anchor = buffer.anchor_after(end);
5440 let new_selection = selection.map(|_| anchor);
5441 (
5442 ((edit_start..end, new_text), prevent_auto_indent),
5443 (newline_config.has_extra_line(), new_selection),
5444 )
5445 })
5446 .unzip()
5447 };
5448
5449 let mut auto_indent_edits = Vec::new();
5450 let mut edits = Vec::new();
5451 for (edit, prevent_auto_indent) in edits_with_flags {
5452 if prevent_auto_indent {
5453 edits.push(edit);
5454 } else {
5455 auto_indent_edits.push(edit);
5456 }
5457 }
5458 if !edits.is_empty() {
5459 this.edit(edits, cx);
5460 }
5461 if !auto_indent_edits.is_empty() {
5462 this.edit_with_autoindent(auto_indent_edits, cx);
5463 }
5464
5465 let buffer = this.buffer.read(cx).snapshot(cx);
5466 let new_selections = selection_info
5467 .into_iter()
5468 .map(|(extra_newline_inserted, new_selection)| {
5469 let mut cursor = new_selection.end.to_point(&buffer);
5470 if extra_newline_inserted {
5471 cursor.row -= 1;
5472 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5473 }
5474 new_selection.map(|_| cursor)
5475 })
5476 .collect();
5477
5478 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5479 this.refresh_edit_prediction(true, false, window, cx);
5480 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5481 task.detach_and_log_err(cx);
5482 }
5483 });
5484 }
5485
5486 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5487 if self.read_only(cx) {
5488 return;
5489 }
5490
5491 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5492
5493 let buffer = self.buffer.read(cx);
5494 let snapshot = buffer.snapshot(cx);
5495
5496 let mut edits = Vec::new();
5497 let mut rows = Vec::new();
5498
5499 for (rows_inserted, selection) in self
5500 .selections
5501 .all_adjusted(&self.display_snapshot(cx))
5502 .into_iter()
5503 .enumerate()
5504 {
5505 let cursor = selection.head();
5506 let row = cursor.row;
5507
5508 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5509
5510 let newline = "\n".to_string();
5511 edits.push((start_of_line..start_of_line, newline));
5512
5513 rows.push(row + rows_inserted as u32);
5514 }
5515
5516 self.transact(window, cx, |editor, window, cx| {
5517 editor.edit(edits, cx);
5518
5519 editor.change_selections(Default::default(), window, cx, |s| {
5520 let mut index = 0;
5521 s.move_cursors_with(&mut |map, _, _| {
5522 let row = rows[index];
5523 index += 1;
5524
5525 let point = Point::new(row, 0);
5526 let boundary = map.next_line_boundary(point).1;
5527 let clipped = map.clip_point(boundary, Bias::Left);
5528
5529 (clipped, SelectionGoal::None)
5530 });
5531 });
5532
5533 let mut indent_edits = Vec::new();
5534 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5535 for row in rows {
5536 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5537 for (row, indent) in indents {
5538 if indent.len == 0 {
5539 continue;
5540 }
5541
5542 let text = match indent.kind {
5543 IndentKind::Space => " ".repeat(indent.len as usize),
5544 IndentKind::Tab => "\t".repeat(indent.len as usize),
5545 };
5546 let point = Point::new(row.0, 0);
5547 indent_edits.push((point..point, text));
5548 }
5549 }
5550 editor.edit(indent_edits, cx);
5551 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5552 format.detach_and_log_err(cx);
5553 }
5554 });
5555 }
5556
5557 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5558 if self.read_only(cx) {
5559 return;
5560 }
5561
5562 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5563
5564 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5565 let mut rows = Vec::new();
5566 let mut rows_inserted = 0;
5567
5568 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5569 let cursor = selection.head();
5570 let row = cursor.row;
5571
5572 let point = Point::new(row, 0);
5573 let Some((buffer_handle, buffer_point)) =
5574 self.buffer.read(cx).point_to_buffer_point(point, cx)
5575 else {
5576 continue;
5577 };
5578
5579 buffer_edits
5580 .entry(buffer_handle.entity_id())
5581 .or_insert_with(|| (buffer_handle, Vec::new()))
5582 .1
5583 .push(buffer_point);
5584
5585 rows_inserted += 1;
5586 rows.push(row + rows_inserted);
5587 }
5588
5589 self.transact(window, cx, |editor, window, cx| {
5590 for (_, (buffer_handle, points)) in &buffer_edits {
5591 buffer_handle.update(cx, |buffer, cx| {
5592 let edits: Vec<_> = points
5593 .iter()
5594 .map(|point| {
5595 let target = Point::new(point.row + 1, 0);
5596 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5597 (start_of_line..start_of_line, "\n")
5598 })
5599 .collect();
5600 buffer.edit(edits, None, cx);
5601 });
5602 }
5603
5604 editor.change_selections(Default::default(), window, cx, |s| {
5605 let mut index = 0;
5606 s.move_cursors_with(&mut |map, _, _| {
5607 let row = rows[index];
5608 index += 1;
5609
5610 let point = Point::new(row, 0);
5611 let boundary = map.next_line_boundary(point).1;
5612 let clipped = map.clip_point(boundary, Bias::Left);
5613
5614 (clipped, SelectionGoal::None)
5615 });
5616 });
5617
5618 let mut indent_edits = Vec::new();
5619 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5620 for row in rows {
5621 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5622 for (row, indent) in indents {
5623 if indent.len == 0 {
5624 continue;
5625 }
5626
5627 let text = match indent.kind {
5628 IndentKind::Space => " ".repeat(indent.len as usize),
5629 IndentKind::Tab => "\t".repeat(indent.len as usize),
5630 };
5631 let point = Point::new(row.0, 0);
5632 indent_edits.push((point..point, text));
5633 }
5634 }
5635 editor.edit(indent_edits, cx);
5636 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5637 format.detach_and_log_err(cx);
5638 }
5639 });
5640 }
5641
5642 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5643 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5644 original_indent_columns: Vec::new(),
5645 });
5646 self.replace_selections(text, autoindent, window, cx, false);
5647 }
5648
5649 /// Replaces the editor's selections with the provided `text`, applying the
5650 /// given `autoindent_mode` (`None` will skip autoindentation).
5651 ///
5652 /// Early returns if the editor is in read-only mode, without applying any
5653 /// edits.
5654 fn replace_selections(
5655 &mut self,
5656 text: &str,
5657 autoindent_mode: Option<AutoindentMode>,
5658 window: &mut Window,
5659 cx: &mut Context<Self>,
5660 apply_linked_edits: bool,
5661 ) {
5662 if self.read_only(cx) {
5663 return;
5664 }
5665
5666 let text: Arc<str> = text.into();
5667 self.transact(window, cx, |this, window, cx| {
5668 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5669 let linked_edits = if apply_linked_edits {
5670 this.linked_edits_for_selections(text.clone(), cx)
5671 } else {
5672 LinkedEdits::new()
5673 };
5674
5675 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5676 let anchors = {
5677 let snapshot = buffer.read(cx);
5678 old_selections
5679 .iter()
5680 .map(|s| {
5681 let anchor = snapshot.anchor_after(s.head());
5682 s.map(|_| anchor)
5683 })
5684 .collect::<Vec<_>>()
5685 };
5686 buffer.edit(
5687 old_selections
5688 .iter()
5689 .map(|s| (s.start..s.end, text.clone())),
5690 autoindent_mode,
5691 cx,
5692 );
5693 anchors
5694 });
5695
5696 linked_edits.apply(cx);
5697
5698 this.change_selections(Default::default(), window, cx, |s| {
5699 s.select_anchors(selection_anchors);
5700 });
5701
5702 if apply_linked_edits {
5703 refresh_linked_ranges(this, window, cx);
5704 }
5705
5706 cx.notify();
5707 });
5708 }
5709
5710 /// Collects linked edits for the current selections, pairing each linked
5711 /// range with `text`.
5712 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5713 let multibuffer_snapshot = self.buffer().read(cx).snapshot(cx);
5714 let mut linked_edits = LinkedEdits::new();
5715 if !self.linked_edit_ranges.is_empty() {
5716 for selection in self.selections.disjoint_anchors() {
5717 let Some((_, range)) =
5718 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(selection.range())
5719 else {
5720 continue;
5721 };
5722 linked_edits.push(self, range, text.clone(), cx);
5723 }
5724 }
5725 linked_edits
5726 }
5727
5728 /// Deletes the content covered by the current selections and applies
5729 /// linked edits.
5730 pub fn delete_selections_with_linked_edits(
5731 &mut self,
5732 window: &mut Window,
5733 cx: &mut Context<Self>,
5734 ) {
5735 self.replace_selections("", None, window, cx, true);
5736 }
5737
5738 #[cfg(any(test, feature = "test-support"))]
5739 pub fn set_linked_edit_ranges_for_testing(
5740 &mut self,
5741 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5742 cx: &mut Context<Self>,
5743 ) -> Option<()> {
5744 let Some((buffer, _)) = self
5745 .buffer
5746 .read(cx)
5747 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5748 else {
5749 return None;
5750 };
5751 let buffer = buffer.read(cx);
5752 let buffer_id = buffer.remote_id();
5753 let mut linked_ranges = Vec::with_capacity(ranges.len());
5754 for (base_range, linked_ranges_points) in ranges {
5755 let base_anchor =
5756 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5757 let linked_anchors = linked_ranges_points
5758 .into_iter()
5759 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5760 .collect();
5761 linked_ranges.push((base_anchor, linked_anchors));
5762 }
5763 let mut map = HashMap::default();
5764 map.insert(buffer_id, linked_ranges);
5765 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5766 Some(())
5767 }
5768
5769 fn trigger_completion_on_input(
5770 &mut self,
5771 text: &str,
5772 trigger_in_words: bool,
5773 window: &mut Window,
5774 cx: &mut Context<Self>,
5775 ) {
5776 let completions_source = self
5777 .context_menu
5778 .borrow()
5779 .as_ref()
5780 .and_then(|menu| match menu {
5781 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5782 CodeContextMenu::CodeActions(_) => None,
5783 });
5784
5785 match completions_source {
5786 Some(CompletionsMenuSource::Words { .. }) => {
5787 self.open_or_update_completions_menu(
5788 Some(CompletionsMenuSource::Words {
5789 ignore_threshold: false,
5790 }),
5791 None,
5792 trigger_in_words,
5793 window,
5794 cx,
5795 );
5796 }
5797 _ => self.open_or_update_completions_menu(
5798 None,
5799 Some(text.to_owned()).filter(|x| !x.is_empty()),
5800 true,
5801 window,
5802 cx,
5803 ),
5804 }
5805 }
5806
5807 /// If any empty selections is touching the start of its innermost containing autoclose
5808 /// region, expand it to select the brackets.
5809 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5810 let selections = self
5811 .selections
5812 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5813 let buffer = self.buffer.read(cx).read(cx);
5814 let new_selections = self
5815 .selections_with_autoclose_regions(selections, &buffer)
5816 .map(|(mut selection, region)| {
5817 if !selection.is_empty() {
5818 return selection;
5819 }
5820
5821 if let Some(region) = region {
5822 let mut range = region.range.to_offset(&buffer);
5823 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5824 range.start -= region.pair.start.len();
5825 if buffer.contains_str_at(range.start, ®ion.pair.start)
5826 && buffer.contains_str_at(range.end, ®ion.pair.end)
5827 {
5828 range.end += region.pair.end.len();
5829 selection.start = range.start;
5830 selection.end = range.end;
5831
5832 return selection;
5833 }
5834 }
5835 }
5836
5837 let always_treat_brackets_as_autoclosed = buffer
5838 .language_settings_at(selection.start, cx)
5839 .always_treat_brackets_as_autoclosed;
5840
5841 if !always_treat_brackets_as_autoclosed {
5842 return selection;
5843 }
5844
5845 if let Some(scope) = buffer.language_scope_at(selection.start) {
5846 for (pair, enabled) in scope.brackets() {
5847 if !enabled || !pair.close {
5848 continue;
5849 }
5850
5851 if buffer.contains_str_at(selection.start, &pair.end) {
5852 let pair_start_len = pair.start.len();
5853 if buffer.contains_str_at(
5854 selection.start.saturating_sub_usize(pair_start_len),
5855 &pair.start,
5856 ) {
5857 selection.start -= pair_start_len;
5858 selection.end += pair.end.len();
5859
5860 return selection;
5861 }
5862 }
5863 }
5864 }
5865
5866 selection
5867 })
5868 .collect();
5869
5870 drop(buffer);
5871 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5872 selections.select(new_selections)
5873 });
5874 }
5875
5876 /// Iterate the given selections, and for each one, find the smallest surrounding
5877 /// autoclose region. This uses the ordering of the selections and the autoclose
5878 /// regions to avoid repeated comparisons.
5879 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5880 &'a self,
5881 selections: impl IntoIterator<Item = Selection<D>>,
5882 buffer: &'a MultiBufferSnapshot,
5883 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5884 let mut i = 0;
5885 let mut regions = self.autoclose_regions.as_slice();
5886 selections.into_iter().map(move |selection| {
5887 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5888
5889 let mut enclosing = None;
5890 while let Some(pair_state) = regions.get(i) {
5891 if pair_state.range.end.to_offset(buffer) < range.start {
5892 regions = ®ions[i + 1..];
5893 i = 0;
5894 } else if pair_state.range.start.to_offset(buffer) > range.end {
5895 break;
5896 } else {
5897 if pair_state.selection_id == selection.id {
5898 enclosing = Some(pair_state);
5899 }
5900 i += 1;
5901 }
5902 }
5903
5904 (selection, enclosing)
5905 })
5906 }
5907
5908 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5909 fn invalidate_autoclose_regions(
5910 &mut self,
5911 mut selections: &[Selection<Anchor>],
5912 buffer: &MultiBufferSnapshot,
5913 ) {
5914 self.autoclose_regions.retain(|state| {
5915 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5916 return false;
5917 }
5918
5919 let mut i = 0;
5920 while let Some(selection) = selections.get(i) {
5921 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5922 selections = &selections[1..];
5923 continue;
5924 }
5925 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5926 break;
5927 }
5928 if selection.id == state.selection_id {
5929 return true;
5930 } else {
5931 i += 1;
5932 }
5933 }
5934 false
5935 });
5936 }
5937
5938 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5939 let offset = position.to_offset(buffer);
5940 let (word_range, kind) =
5941 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5942 if offset > word_range.start && kind == Some(CharKind::Word) {
5943 Some(
5944 buffer
5945 .text_for_range(word_range.start..offset)
5946 .collect::<String>(),
5947 )
5948 } else {
5949 None
5950 }
5951 }
5952
5953 pub fn is_lsp_relevant(&self, file: Option<&Arc<dyn language::File>>, cx: &App) -> bool {
5954 let Some(project) = self.project() else {
5955 return false;
5956 };
5957 let Some(buffer_file) = project::File::from_dyn(file) else {
5958 return false;
5959 };
5960 let Some(entry_id) = buffer_file.project_entry_id() else {
5961 return false;
5962 };
5963 let project = project.read(cx);
5964 let Some(buffer_worktree) = project.worktree_for_id(buffer_file.worktree_id(cx), cx) else {
5965 return false;
5966 };
5967 let Some(worktree_entry) = buffer_worktree.read(cx).entry_for_id(entry_id) else {
5968 return false;
5969 };
5970 !worktree_entry.is_ignored
5971 }
5972
5973 pub fn visible_buffers(&self, cx: &mut Context<Editor>) -> Vec<Entity<Buffer>> {
5974 let display_snapshot = self.display_snapshot(cx);
5975 let visible_range = self.multi_buffer_visible_range(&display_snapshot, cx);
5976 let multi_buffer = self.buffer().read(cx);
5977 display_snapshot
5978 .buffer_snapshot()
5979 .range_to_buffer_ranges(visible_range)
5980 .into_iter()
5981 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5982 .filter_map(|(buffer_snapshot, _, _)| multi_buffer.buffer(buffer_snapshot.remote_id()))
5983 .collect()
5984 }
5985
5986 pub fn visible_buffer_ranges(
5987 &self,
5988 cx: &mut Context<Editor>,
5989 ) -> Vec<(
5990 BufferSnapshot,
5991 Range<BufferOffset>,
5992 ExcerptRange<text::Anchor>,
5993 )> {
5994 let display_snapshot = self.display_snapshot(cx);
5995 let visible_range = self.multi_buffer_visible_range(&display_snapshot, cx);
5996 display_snapshot
5997 .buffer_snapshot()
5998 .range_to_buffer_ranges(visible_range)
5999 .into_iter()
6000 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
6001 .collect()
6002 }
6003
6004 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
6005 TextLayoutDetails {
6006 text_system: window.text_system().clone(),
6007 editor_style: self.style.clone().unwrap(),
6008 rem_size: window.rem_size(),
6009 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
6010 visible_rows: self.visible_line_count(),
6011 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
6012 }
6013 }
6014
6015 fn trigger_on_type_formatting(
6016 &self,
6017 input: String,
6018 window: &mut Window,
6019 cx: &mut Context<Self>,
6020 ) -> Option<Task<Result<()>>> {
6021 if input.chars().count() != 1 {
6022 return None;
6023 }
6024
6025 let project = self.project()?;
6026 let position = self.selections.newest_anchor().head();
6027 let (buffer, buffer_position) = self
6028 .buffer
6029 .read(cx)
6030 .text_anchor_for_position(position, cx)?;
6031
6032 let settings = LanguageSettings::for_buffer_at(&buffer.read(cx), buffer_position, cx);
6033 if !settings.use_on_type_format {
6034 return None;
6035 }
6036
6037 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
6038 // hence we do LSP request & edit on host side only — add formats to host's history.
6039 let push_to_lsp_host_history = true;
6040 // If this is not the host, append its history with new edits.
6041 let push_to_client_history = project.read(cx).is_via_collab();
6042
6043 let on_type_formatting = project.update(cx, |project, cx| {
6044 project.on_type_format(
6045 buffer.clone(),
6046 buffer_position,
6047 input,
6048 push_to_lsp_host_history,
6049 cx,
6050 )
6051 });
6052 Some(cx.spawn_in(window, async move |editor, cx| {
6053 if let Some(transaction) = on_type_formatting.await? {
6054 if push_to_client_history {
6055 buffer.update(cx, |buffer, _| {
6056 buffer.push_transaction(transaction, Instant::now());
6057 buffer.finalize_last_transaction();
6058 });
6059 }
6060 editor.update(cx, |editor, cx| {
6061 editor.refresh_document_highlights(cx);
6062 })?;
6063 }
6064 Ok(())
6065 }))
6066 }
6067
6068 pub fn show_word_completions(
6069 &mut self,
6070 _: &ShowWordCompletions,
6071 window: &mut Window,
6072 cx: &mut Context<Self>,
6073 ) {
6074 self.open_or_update_completions_menu(
6075 Some(CompletionsMenuSource::Words {
6076 ignore_threshold: true,
6077 }),
6078 None,
6079 false,
6080 window,
6081 cx,
6082 );
6083 }
6084
6085 pub fn show_completions(
6086 &mut self,
6087 _: &ShowCompletions,
6088 window: &mut Window,
6089 cx: &mut Context<Self>,
6090 ) {
6091 self.open_or_update_completions_menu(None, None, false, window, cx);
6092 }
6093
6094 fn open_or_update_completions_menu(
6095 &mut self,
6096 requested_source: Option<CompletionsMenuSource>,
6097 trigger: Option<String>,
6098 trigger_in_words: bool,
6099 window: &mut Window,
6100 cx: &mut Context<Self>,
6101 ) {
6102 if self.pending_rename.is_some() {
6103 return;
6104 }
6105
6106 let completions_source = self
6107 .context_menu
6108 .borrow()
6109 .as_ref()
6110 .and_then(|menu| match menu {
6111 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
6112 CodeContextMenu::CodeActions(_) => None,
6113 });
6114
6115 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
6116
6117 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
6118 // inserted and selected. To handle that case, the start of the selection is used so that
6119 // the menu starts with all choices.
6120 let position = self
6121 .selections
6122 .newest_anchor()
6123 .start
6124 .bias_right(&multibuffer_snapshot);
6125
6126 if position.diff_base_anchor().is_some() {
6127 return;
6128 }
6129 let multibuffer_position = multibuffer_snapshot.anchor_before(position);
6130 let Some((buffer_position, _)) =
6131 multibuffer_snapshot.anchor_to_buffer_anchor(multibuffer_position)
6132 else {
6133 return;
6134 };
6135 let Some(buffer) = self.buffer.read(cx).buffer(buffer_position.buffer_id) else {
6136 return;
6137 };
6138 let buffer_snapshot = buffer.read(cx).snapshot();
6139
6140 let menu_is_open = matches!(
6141 self.context_menu.borrow().as_ref(),
6142 Some(CodeContextMenu::Completions(_))
6143 );
6144
6145 let language = buffer_snapshot
6146 .language_at(buffer_position)
6147 .map(|language| language.name());
6148 let language_settings = multibuffer_snapshot.language_settings_at(multibuffer_position, cx);
6149 let completion_settings = language_settings.completions.clone();
6150
6151 let show_completions_on_input = self
6152 .show_completions_on_input_override
6153 .unwrap_or(language_settings.show_completions_on_input);
6154 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
6155 return;
6156 }
6157
6158 let query: Option<Arc<String>> =
6159 Self::completion_query(&multibuffer_snapshot, multibuffer_position)
6160 .map(|query| query.into());
6161
6162 drop(multibuffer_snapshot);
6163
6164 // Hide the current completions menu when query is empty. Without this, cached
6165 // completions from before the trigger char may be reused (#32774).
6166 if query.is_none() && menu_is_open {
6167 self.hide_context_menu(window, cx);
6168 }
6169
6170 let mut ignore_word_threshold = false;
6171 let provider = match requested_source {
6172 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
6173 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
6174 ignore_word_threshold = ignore_threshold;
6175 None
6176 }
6177 Some(CompletionsMenuSource::SnippetChoices)
6178 | Some(CompletionsMenuSource::SnippetsOnly) => {
6179 log::error!("bug: SnippetChoices requested_source is not handled");
6180 None
6181 }
6182 };
6183
6184 let sort_completions = provider
6185 .as_ref()
6186 .is_some_and(|provider| provider.sort_completions());
6187
6188 let filter_completions = provider
6189 .as_ref()
6190 .is_none_or(|provider| provider.filter_completions());
6191
6192 let was_snippets_only = matches!(
6193 completions_source,
6194 Some(CompletionsMenuSource::SnippetsOnly)
6195 );
6196
6197 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
6198 if filter_completions {
6199 menu.filter(
6200 query.clone().unwrap_or_default(),
6201 buffer_position,
6202 &buffer,
6203 provider.clone(),
6204 window,
6205 cx,
6206 );
6207 }
6208 // When `is_incomplete` is false, no need to re-query completions when the current query
6209 // is a suffix of the initial query.
6210 let was_complete = !menu.is_incomplete;
6211 if was_complete && !was_snippets_only {
6212 // If the new query is a suffix of the old query (typing more characters) and
6213 // the previous result was complete, the existing completions can be filtered.
6214 //
6215 // Note that snippet completions are always complete.
6216 let query_matches = match (&menu.initial_query, &query) {
6217 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6218 (None, _) => true,
6219 _ => false,
6220 };
6221 if query_matches {
6222 let position_matches = if menu.initial_position == position {
6223 true
6224 } else {
6225 let snapshot = self.buffer.read(cx).read(cx);
6226 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6227 };
6228 if position_matches {
6229 return;
6230 }
6231 }
6232 }
6233 };
6234
6235 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6236 buffer_snapshot.surrounding_word(buffer_position, None)
6237 {
6238 let word_to_exclude = buffer_snapshot
6239 .text_for_range(word_range.clone())
6240 .collect::<String>();
6241 (
6242 buffer_snapshot.anchor_before(word_range.start)
6243 ..buffer_snapshot.anchor_after(buffer_position),
6244 Some(word_to_exclude),
6245 )
6246 } else {
6247 (buffer_position..buffer_position, None)
6248 };
6249
6250 let show_completion_documentation = buffer_snapshot
6251 .settings_at(buffer_position, cx)
6252 .show_completion_documentation;
6253
6254 // The document can be large, so stay in reasonable bounds when searching for words,
6255 // otherwise completion pop-up might be slow to appear.
6256 const WORD_LOOKUP_ROWS: u32 = 5_000;
6257 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6258 let min_word_search = buffer_snapshot.clip_point(
6259 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6260 Bias::Left,
6261 );
6262 let max_word_search = buffer_snapshot.clip_point(
6263 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6264 Bias::Right,
6265 );
6266 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6267 ..buffer_snapshot.point_to_offset(max_word_search);
6268
6269 let skip_digits = query
6270 .as_ref()
6271 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6272
6273 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6274 trigger.as_ref().is_none_or(|trigger| {
6275 provider.is_completion_trigger(
6276 &buffer,
6277 buffer_position,
6278 trigger,
6279 trigger_in_words,
6280 cx,
6281 )
6282 })
6283 });
6284
6285 let provider_responses = if let Some(provider) = &provider
6286 && load_provider_completions
6287 {
6288 let trigger_character =
6289 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6290 let completion_context = CompletionContext {
6291 trigger_kind: match &trigger_character {
6292 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6293 None => CompletionTriggerKind::INVOKED,
6294 },
6295 trigger_character,
6296 };
6297
6298 provider.completions(&buffer, buffer_position, completion_context, window, cx)
6299 } else {
6300 Task::ready(Ok(Vec::new()))
6301 };
6302
6303 let load_word_completions = if !self.word_completions_enabled {
6304 false
6305 } else if requested_source
6306 == Some(CompletionsMenuSource::Words {
6307 ignore_threshold: true,
6308 })
6309 {
6310 true
6311 } else {
6312 load_provider_completions
6313 && completion_settings.words != WordsCompletionMode::Disabled
6314 && (ignore_word_threshold || {
6315 let words_min_length = completion_settings.words_min_length;
6316 // check whether word has at least `words_min_length` characters
6317 let query_chars = query.iter().flat_map(|q| q.chars());
6318 query_chars.take(words_min_length).count() == words_min_length
6319 })
6320 };
6321
6322 let mut words = if load_word_completions {
6323 cx.background_spawn({
6324 let buffer_snapshot = buffer_snapshot.clone();
6325 async move {
6326 buffer_snapshot.words_in_range(WordsQuery {
6327 fuzzy_contents: None,
6328 range: word_search_range,
6329 skip_digits,
6330 })
6331 }
6332 })
6333 } else {
6334 Task::ready(BTreeMap::default())
6335 };
6336
6337 let snippets = if let Some(provider) = &provider
6338 && provider.show_snippets()
6339 && let Some(project) = self.project()
6340 {
6341 let char_classifier = buffer_snapshot
6342 .char_classifier_at(buffer_position)
6343 .scope_context(Some(CharScopeContext::Completion));
6344 project.update(cx, |project, cx| {
6345 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6346 })
6347 } else {
6348 Task::ready(Ok(CompletionResponse {
6349 completions: Vec::new(),
6350 display_options: Default::default(),
6351 is_incomplete: false,
6352 }))
6353 };
6354
6355 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6356
6357 let id = post_inc(&mut self.next_completion_id);
6358 let task = cx.spawn_in(window, async move |editor, cx| {
6359 let Ok(()) = editor.update(cx, |this, _| {
6360 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6361 }) else {
6362 return;
6363 };
6364
6365 // TODO: Ideally completions from different sources would be selectively re-queried, so
6366 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6367 let mut completions = Vec::new();
6368 let mut is_incomplete = false;
6369 let mut display_options: Option<CompletionDisplayOptions> = None;
6370 if let Some(provider_responses) = provider_responses.await.log_err()
6371 && !provider_responses.is_empty()
6372 {
6373 for response in provider_responses {
6374 completions.extend(response.completions);
6375 is_incomplete = is_incomplete || response.is_incomplete;
6376 match display_options.as_mut() {
6377 None => {
6378 display_options = Some(response.display_options);
6379 }
6380 Some(options) => options.merge(&response.display_options),
6381 }
6382 }
6383 if completion_settings.words == WordsCompletionMode::Fallback {
6384 words = Task::ready(BTreeMap::default());
6385 }
6386 }
6387 let display_options = display_options.unwrap_or_default();
6388
6389 let mut words = words.await;
6390 if let Some(word_to_exclude) = &word_to_exclude {
6391 words.remove(word_to_exclude);
6392 }
6393 for lsp_completion in &completions {
6394 words.remove(&lsp_completion.new_text);
6395 }
6396 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6397 replace_range: word_replace_range.clone(),
6398 new_text: word.clone(),
6399 label: CodeLabel::plain(word, None),
6400 match_start: None,
6401 snippet_deduplication_key: None,
6402 icon_path: None,
6403 documentation: None,
6404 source: CompletionSource::BufferWord {
6405 word_range,
6406 resolved: false,
6407 },
6408 insert_text_mode: Some(InsertTextMode::AS_IS),
6409 confirm: None,
6410 }));
6411
6412 completions.extend(
6413 snippets
6414 .await
6415 .into_iter()
6416 .flat_map(|response| response.completions),
6417 );
6418
6419 let menu = if completions.is_empty() {
6420 None
6421 } else {
6422 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6423 let languages = editor
6424 .workspace
6425 .as_ref()
6426 .and_then(|(workspace, _)| workspace.upgrade())
6427 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6428 let menu = CompletionsMenu::new(
6429 id,
6430 requested_source.unwrap_or(if load_provider_completions {
6431 CompletionsMenuSource::Normal
6432 } else {
6433 CompletionsMenuSource::SnippetsOnly
6434 }),
6435 sort_completions,
6436 show_completion_documentation,
6437 position,
6438 query.clone(),
6439 is_incomplete,
6440 buffer.clone(),
6441 completions.into(),
6442 editor
6443 .context_menu()
6444 .borrow_mut()
6445 .as_ref()
6446 .map(|menu| menu.primary_scroll_handle()),
6447 display_options,
6448 snippet_sort_order,
6449 languages,
6450 language,
6451 cx,
6452 );
6453
6454 let query = if filter_completions { query } else { None };
6455 let matches_task = menu.do_async_filtering(
6456 query.unwrap_or_default(),
6457 buffer_position,
6458 &buffer,
6459 cx,
6460 );
6461 (menu, matches_task)
6462 }) else {
6463 return;
6464 };
6465
6466 let matches = matches_task.await;
6467
6468 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6469 // Newer menu already set, so exit.
6470 if let Some(CodeContextMenu::Completions(prev_menu)) =
6471 editor.context_menu.borrow().as_ref()
6472 && prev_menu.id > id
6473 {
6474 return;
6475 };
6476
6477 // Only valid to take prev_menu because either the new menu is immediately set
6478 // below, or the menu is hidden.
6479 if let Some(CodeContextMenu::Completions(prev_menu)) =
6480 editor.context_menu.borrow_mut().take()
6481 {
6482 let position_matches =
6483 if prev_menu.initial_position == menu.initial_position {
6484 true
6485 } else {
6486 let snapshot = editor.buffer.read(cx).read(cx);
6487 prev_menu.initial_position.to_offset(&snapshot)
6488 == menu.initial_position.to_offset(&snapshot)
6489 };
6490 if position_matches {
6491 // Preserve markdown cache before `set_filter_results` because it will
6492 // try to populate the documentation cache.
6493 menu.preserve_markdown_cache(prev_menu);
6494 }
6495 };
6496
6497 menu.set_filter_results(matches, provider, window, cx);
6498 }) else {
6499 return;
6500 };
6501
6502 menu.visible().then_some(menu)
6503 };
6504
6505 editor
6506 .update_in(cx, |editor, window, cx| {
6507 if editor.focus_handle.is_focused(window)
6508 && let Some(menu) = menu
6509 {
6510 *editor.context_menu.borrow_mut() =
6511 Some(CodeContextMenu::Completions(menu));
6512
6513 crate::hover_popover::hide_hover(editor, cx);
6514 if editor.show_edit_predictions_in_menu() {
6515 editor.update_visible_edit_prediction(window, cx);
6516 } else {
6517 editor
6518 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6519 }
6520
6521 cx.notify();
6522 return;
6523 }
6524
6525 if editor.completion_tasks.len() <= 1 {
6526 // If there are no more completion tasks and the last menu was empty, we should hide it.
6527 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6528 // If it was already hidden and we don't show edit predictions in the menu,
6529 // we should also show the edit prediction when available.
6530 if was_hidden && editor.show_edit_predictions_in_menu() {
6531 editor.update_visible_edit_prediction(window, cx);
6532 }
6533 }
6534 })
6535 .ok();
6536 });
6537
6538 self.completion_tasks.push((id, task));
6539 }
6540
6541 #[cfg(any(test, feature = "test-support"))]
6542 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6543 let menu = self.context_menu.borrow();
6544 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6545 let completions = menu.completions.borrow();
6546 Some(completions.to_vec())
6547 } else {
6548 None
6549 }
6550 }
6551
6552 pub fn with_completions_menu_matching_id<R>(
6553 &self,
6554 id: CompletionId,
6555 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6556 ) -> R {
6557 let mut context_menu = self.context_menu.borrow_mut();
6558 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6559 return f(None);
6560 };
6561 if completions_menu.id != id {
6562 return f(None);
6563 }
6564 f(Some(completions_menu))
6565 }
6566
6567 pub fn confirm_completion(
6568 &mut self,
6569 action: &ConfirmCompletion,
6570 window: &mut Window,
6571 cx: &mut Context<Self>,
6572 ) -> Option<Task<Result<()>>> {
6573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6574 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6575 }
6576
6577 pub fn confirm_completion_insert(
6578 &mut self,
6579 _: &ConfirmCompletionInsert,
6580 window: &mut Window,
6581 cx: &mut Context<Self>,
6582 ) -> Option<Task<Result<()>>> {
6583 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6584 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6585 }
6586
6587 pub fn confirm_completion_replace(
6588 &mut self,
6589 _: &ConfirmCompletionReplace,
6590 window: &mut Window,
6591 cx: &mut Context<Self>,
6592 ) -> Option<Task<Result<()>>> {
6593 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6594 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6595 }
6596
6597 pub fn compose_completion(
6598 &mut self,
6599 action: &ComposeCompletion,
6600 window: &mut Window,
6601 cx: &mut Context<Self>,
6602 ) -> Option<Task<Result<()>>> {
6603 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6604 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6605 }
6606
6607 fn do_completion(
6608 &mut self,
6609 item_ix: Option<usize>,
6610 intent: CompletionIntent,
6611 window: &mut Window,
6612 cx: &mut Context<Editor>,
6613 ) -> Option<Task<Result<()>>> {
6614 use language::ToOffset as _;
6615
6616 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6617 else {
6618 return None;
6619 };
6620
6621 let candidate_id = {
6622 let entries = completions_menu.entries.borrow();
6623 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6624 if self.show_edit_predictions_in_menu() {
6625 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6626 }
6627 mat.candidate_id
6628 };
6629
6630 let completion = completions_menu
6631 .completions
6632 .borrow()
6633 .get(candidate_id)?
6634 .clone();
6635 cx.stop_propagation();
6636
6637 let buffer_handle = completions_menu.buffer.clone();
6638 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
6639 let (initial_position, _) =
6640 multibuffer_snapshot.anchor_to_buffer_anchor(completions_menu.initial_position)?;
6641
6642 let CompletionEdit {
6643 new_text,
6644 snippet,
6645 replace_range,
6646 } = process_completion_for_edit(&completion, intent, &buffer_handle, &initial_position, cx);
6647
6648 let buffer = buffer_handle.read(cx).snapshot();
6649 let newest_selection = self.selections.newest_anchor();
6650
6651 let Some(replace_range_multibuffer) =
6652 multibuffer_snapshot.buffer_anchor_range_to_anchor_range(replace_range.clone())
6653 else {
6654 return None;
6655 };
6656
6657 let Some((buffer_snapshot, newest_range_buffer)) =
6658 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(newest_selection.range())
6659 else {
6660 return None;
6661 };
6662
6663 let old_text = buffer
6664 .text_for_range(replace_range.clone())
6665 .collect::<String>();
6666 let lookbehind = newest_range_buffer
6667 .start
6668 .to_offset(buffer_snapshot)
6669 .saturating_sub(replace_range.start.to_offset(&buffer_snapshot));
6670 let lookahead = replace_range
6671 .end
6672 .to_offset(&buffer_snapshot)
6673 .saturating_sub(newest_range_buffer.end.to_offset(&buffer));
6674 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6675 let suffix = &old_text[lookbehind.min(old_text.len())..];
6676
6677 let selections = self
6678 .selections
6679 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6680 let mut ranges = Vec::new();
6681 let mut all_commit_ranges = Vec::new();
6682 let mut linked_edits = LinkedEdits::new();
6683
6684 let text: Arc<str> = new_text.clone().into();
6685 for selection in &selections {
6686 let range = if selection.id == newest_selection.id {
6687 replace_range_multibuffer.clone()
6688 } else {
6689 let mut range = selection.range();
6690
6691 // if prefix is present, don't duplicate it
6692 if multibuffer_snapshot
6693 .contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix)
6694 {
6695 range.start = range.start.saturating_sub_usize(lookbehind);
6696
6697 // if suffix is also present, mimic the newest cursor and replace it
6698 if selection.id != newest_selection.id
6699 && multibuffer_snapshot.contains_str_at(range.end, suffix)
6700 {
6701 range.end += lookahead;
6702 }
6703 }
6704 range.to_anchors(&multibuffer_snapshot)
6705 };
6706
6707 ranges.push(range.clone());
6708
6709 let start_anchor = multibuffer_snapshot.anchor_before(range.start);
6710 let end_anchor = multibuffer_snapshot.anchor_after(range.end);
6711
6712 if let Some((buffer_snapshot_2, anchor_range)) =
6713 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(start_anchor..end_anchor)
6714 && buffer_snapshot_2.remote_id() == buffer_snapshot.remote_id()
6715 {
6716 all_commit_ranges.push(anchor_range.clone());
6717 if !self.linked_edit_ranges.is_empty() {
6718 linked_edits.push(&self, anchor_range, text.clone(), cx);
6719 }
6720 }
6721 }
6722
6723 let common_prefix_len = old_text
6724 .chars()
6725 .zip(new_text.chars())
6726 .take_while(|(a, b)| a == b)
6727 .map(|(a, _)| a.len_utf8())
6728 .sum::<usize>();
6729
6730 cx.emit(EditorEvent::InputHandled {
6731 utf16_range_to_replace: None,
6732 text: new_text[common_prefix_len..].into(),
6733 });
6734
6735 let tx_id = self.transact(window, cx, |editor, window, cx| {
6736 if let Some(mut snippet) = snippet {
6737 snippet.text = new_text.to_string();
6738 let offset_ranges = ranges
6739 .iter()
6740 .map(|range| range.to_offset(&multibuffer_snapshot))
6741 .collect::<Vec<_>>();
6742 editor
6743 .insert_snippet(&offset_ranges, snippet, window, cx)
6744 .log_err();
6745 } else {
6746 editor.buffer.update(cx, |multi_buffer, cx| {
6747 let auto_indent = match completion.insert_text_mode {
6748 Some(InsertTextMode::AS_IS) => None,
6749 _ => editor.autoindent_mode.clone(),
6750 };
6751 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6752 multi_buffer.edit(edits, auto_indent, cx);
6753 });
6754 }
6755 linked_edits.apply(cx);
6756 editor.refresh_edit_prediction(true, false, window, cx);
6757 });
6758 self.invalidate_autoclose_regions(
6759 &self.selections.disjoint_anchors_arc(),
6760 &multibuffer_snapshot,
6761 );
6762
6763 let show_new_completions_on_confirm = completion
6764 .confirm
6765 .as_ref()
6766 .is_some_and(|confirm| confirm(intent, window, cx));
6767 if show_new_completions_on_confirm {
6768 self.open_or_update_completions_menu(None, None, false, window, cx);
6769 }
6770
6771 let provider = self.completion_provider.as_ref()?;
6772
6773 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6774 let command = lsp_store.as_ref().and_then(|lsp_store| {
6775 let CompletionSource::Lsp {
6776 lsp_completion,
6777 server_id,
6778 ..
6779 } = &completion.source
6780 else {
6781 return None;
6782 };
6783 let lsp_command = lsp_completion.command.as_ref()?;
6784 let available_commands = lsp_store
6785 .read(cx)
6786 .lsp_server_capabilities
6787 .get(server_id)
6788 .and_then(|server_capabilities| {
6789 server_capabilities
6790 .execute_command_provider
6791 .as_ref()
6792 .map(|options| options.commands.as_slice())
6793 })?;
6794 if available_commands.contains(&lsp_command.command) {
6795 Some(CodeAction {
6796 server_id: *server_id,
6797 range: language::Anchor::min_min_range_for_buffer(buffer.remote_id()),
6798 lsp_action: LspAction::Command(lsp_command.clone()),
6799 resolved: false,
6800 })
6801 } else {
6802 None
6803 }
6804 });
6805
6806 drop(completion);
6807 let apply_edits = provider.apply_additional_edits_for_completion(
6808 buffer_handle.clone(),
6809 completions_menu.completions.clone(),
6810 candidate_id,
6811 true,
6812 all_commit_ranges,
6813 cx,
6814 );
6815
6816 let editor_settings = EditorSettings::get_global(cx);
6817 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6818 // After the code completion is finished, users often want to know what signatures are needed.
6819 // so we should automatically call signature_help
6820 self.show_signature_help(&ShowSignatureHelp, window, cx);
6821 }
6822
6823 Some(cx.spawn_in(window, async move |editor, cx| {
6824 let additional_edits_tx = apply_edits.await?;
6825
6826 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6827 let title = command.lsp_action.title().to_owned();
6828 let project_transaction = lsp_store
6829 .update(cx, |lsp_store, cx| {
6830 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6831 })
6832 .await
6833 .context("applying post-completion command")?;
6834 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6835 Self::open_project_transaction(
6836 &editor,
6837 workspace.downgrade(),
6838 project_transaction,
6839 title,
6840 cx,
6841 )
6842 .await?;
6843 }
6844 }
6845
6846 if let Some(tx_id) = tx_id
6847 && let Some(additional_edits_tx) = additional_edits_tx
6848 {
6849 editor
6850 .update(cx, |editor, cx| {
6851 editor.buffer.update(cx, |buffer, cx| {
6852 buffer.merge_transactions(additional_edits_tx.id, tx_id, cx)
6853 });
6854 })
6855 .context("merge transactions")?;
6856 }
6857
6858 Ok(())
6859 }))
6860 }
6861
6862 pub fn toggle_code_actions(
6863 &mut self,
6864 action: &ToggleCodeActions,
6865 window: &mut Window,
6866 cx: &mut Context<Self>,
6867 ) {
6868 let quick_launch = action.quick_launch;
6869 let mut context_menu = self.context_menu.borrow_mut();
6870 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6871 if code_actions.deployed_from == action.deployed_from {
6872 // Toggle if we're selecting the same one
6873 *context_menu = None;
6874 cx.notify();
6875 return;
6876 } else {
6877 // Otherwise, clear it and start a new one
6878 *context_menu = None;
6879 cx.notify();
6880 }
6881 }
6882 drop(context_menu);
6883 let snapshot = self.snapshot(window, cx);
6884 let deployed_from = action.deployed_from.clone();
6885 let action = action.clone();
6886 self.completion_tasks.clear();
6887 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6888
6889 let multibuffer_point = match &action.deployed_from {
6890 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6891 DisplayPoint::new(*row, 0).to_point(&snapshot)
6892 }
6893 _ => self
6894 .selections
6895 .newest::<Point>(&snapshot.display_snapshot)
6896 .head(),
6897 };
6898 let Some((buffer, buffer_row)) = snapshot
6899 .buffer_snapshot()
6900 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6901 .and_then(|(buffer_snapshot, range)| {
6902 self.buffer()
6903 .read(cx)
6904 .buffer(buffer_snapshot.remote_id())
6905 .map(|buffer| (buffer, range.start.row))
6906 })
6907 else {
6908 return;
6909 };
6910 let buffer_id = buffer.read(cx).remote_id();
6911 let tasks = self
6912 .runnables
6913 .runnables((buffer_id, buffer_row))
6914 .map(|t| Arc::new(t.to_owned()));
6915
6916 if !self.focus_handle.is_focused(window) {
6917 return;
6918 }
6919 let project = self.project.clone();
6920
6921 let code_actions_task = match deployed_from {
6922 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6923 _ => self.code_actions(buffer_row, window, cx),
6924 };
6925
6926 let runnable_task = match deployed_from {
6927 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6928 _ => {
6929 let mut task_context_task = Task::ready(None);
6930 if let Some(tasks) = &tasks
6931 && let Some(project) = project
6932 {
6933 task_context_task =
6934 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6935 }
6936
6937 cx.spawn_in(window, {
6938 let buffer = buffer.clone();
6939 async move |editor, cx| {
6940 let task_context = task_context_task.await;
6941
6942 let resolved_tasks =
6943 tasks
6944 .zip(task_context.clone())
6945 .map(|(tasks, task_context)| ResolvedTasks {
6946 templates: tasks.resolve(&task_context).collect(),
6947 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6948 multibuffer_point.row,
6949 tasks.column,
6950 )),
6951 });
6952 let debug_scenarios = editor
6953 .update(cx, |editor, cx| {
6954 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6955 })?
6956 .await;
6957 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6958 }
6959 })
6960 }
6961 };
6962
6963 cx.spawn_in(window, async move |editor, cx| {
6964 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6965 let code_actions = code_actions_task.await;
6966 let spawn_straight_away = quick_launch
6967 && resolved_tasks
6968 .as_ref()
6969 .is_some_and(|tasks| tasks.templates.len() == 1)
6970 && code_actions
6971 .as_ref()
6972 .is_none_or(|actions| actions.is_empty())
6973 && debug_scenarios.is_empty();
6974
6975 editor.update_in(cx, |editor, window, cx| {
6976 crate::hover_popover::hide_hover(editor, cx);
6977 let actions = CodeActionContents::new(
6978 resolved_tasks,
6979 code_actions,
6980 debug_scenarios,
6981 task_context.unwrap_or_default(),
6982 );
6983
6984 // Don't show the menu if there are no actions available
6985 if actions.is_empty() {
6986 cx.notify();
6987 return Task::ready(Ok(()));
6988 }
6989
6990 *editor.context_menu.borrow_mut() =
6991 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6992 buffer,
6993 actions,
6994 selected_item: Default::default(),
6995 scroll_handle: UniformListScrollHandle::default(),
6996 deployed_from,
6997 }));
6998 cx.notify();
6999 if spawn_straight_away
7000 && let Some(task) = editor.confirm_code_action(
7001 &ConfirmCodeAction { item_ix: Some(0) },
7002 window,
7003 cx,
7004 )
7005 {
7006 return task;
7007 }
7008
7009 Task::ready(Ok(()))
7010 })
7011 })
7012 .detach_and_log_err(cx);
7013 }
7014
7015 fn debug_scenarios(
7016 &mut self,
7017 resolved_tasks: &Option<ResolvedTasks>,
7018 buffer: &Entity<Buffer>,
7019 cx: &mut App,
7020 ) -> Task<Vec<task::DebugScenario>> {
7021 maybe!({
7022 let project = self.project()?;
7023 let dap_store = project.read(cx).dap_store();
7024 let mut scenarios = vec![];
7025 let resolved_tasks = resolved_tasks.as_ref()?;
7026 let buffer = buffer.read(cx);
7027 let language = buffer.language()?;
7028 let debug_adapter = LanguageSettings::for_buffer(&buffer, cx)
7029 .debuggers
7030 .first()
7031 .map(SharedString::from)
7032 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
7033
7034 dap_store.update(cx, |dap_store, cx| {
7035 for (_, task) in &resolved_tasks.templates {
7036 let maybe_scenario = dap_store.debug_scenario_for_build_task(
7037 task.original_task().clone(),
7038 debug_adapter.clone().into(),
7039 task.display_label().to_owned().into(),
7040 cx,
7041 );
7042 scenarios.push(maybe_scenario);
7043 }
7044 });
7045 Some(cx.background_spawn(async move {
7046 futures::future::join_all(scenarios)
7047 .await
7048 .into_iter()
7049 .flatten()
7050 .collect::<Vec<_>>()
7051 }))
7052 })
7053 .unwrap_or_else(|| Task::ready(vec![]))
7054 }
7055
7056 fn code_actions(
7057 &mut self,
7058 buffer_row: u32,
7059 window: &mut Window,
7060 cx: &mut Context<Self>,
7061 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
7062 let mut task = self.code_actions_task.take();
7063 cx.spawn_in(window, async move |editor, cx| {
7064 while let Some(prev_task) = task {
7065 prev_task.await.log_err();
7066 task = editor
7067 .update(cx, |this, _| this.code_actions_task.take())
7068 .ok()?;
7069 }
7070
7071 editor
7072 .update(cx, |editor, cx| {
7073 editor
7074 .available_code_actions
7075 .clone()
7076 .and_then(|(location, code_actions)| {
7077 let snapshot = location.buffer.read(cx).snapshot();
7078 let point_range = location.range.to_point(&snapshot);
7079 let point_range = point_range.start.row..=point_range.end.row;
7080 if point_range.contains(&buffer_row) {
7081 Some(code_actions)
7082 } else {
7083 None
7084 }
7085 })
7086 })
7087 .ok()
7088 .flatten()
7089 })
7090 }
7091
7092 pub fn confirm_code_action(
7093 &mut self,
7094 action: &ConfirmCodeAction,
7095 window: &mut Window,
7096 cx: &mut Context<Self>,
7097 ) -> Option<Task<Result<()>>> {
7098 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
7099
7100 let actions_menu =
7101 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
7102 menu
7103 } else {
7104 return None;
7105 };
7106
7107 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
7108 let action = actions_menu.actions.get(action_ix)?;
7109 let title = action.label();
7110 let buffer = actions_menu.buffer;
7111 let workspace = self.workspace()?;
7112
7113 match action {
7114 CodeActionsItem::Task(task_source_kind, resolved_task) => {
7115 workspace.update(cx, |workspace, cx| {
7116 workspace.schedule_resolved_task(
7117 task_source_kind,
7118 resolved_task,
7119 false,
7120 window,
7121 cx,
7122 );
7123
7124 Some(Task::ready(Ok(())))
7125 })
7126 }
7127 CodeActionsItem::CodeAction { action, provider } => {
7128 let apply_code_action =
7129 provider.apply_code_action(buffer, action, true, window, cx);
7130 let workspace = workspace.downgrade();
7131 Some(cx.spawn_in(window, async move |editor, cx| {
7132 let project_transaction = apply_code_action.await?;
7133 Self::open_project_transaction(
7134 &editor,
7135 workspace,
7136 project_transaction,
7137 title,
7138 cx,
7139 )
7140 .await
7141 }))
7142 }
7143 CodeActionsItem::DebugScenario(scenario) => {
7144 let context = actions_menu.actions.context.into();
7145
7146 workspace.update(cx, |workspace, cx| {
7147 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
7148 workspace.start_debug_session(
7149 scenario,
7150 context,
7151 Some(buffer),
7152 None,
7153 window,
7154 cx,
7155 );
7156 });
7157 Some(Task::ready(Ok(())))
7158 }
7159 }
7160 }
7161
7162 fn open_transaction_for_hidden_buffers(
7163 workspace: Entity<Workspace>,
7164 transaction: ProjectTransaction,
7165 title: String,
7166 window: &mut Window,
7167 cx: &mut Context<Self>,
7168 ) {
7169 if transaction.0.is_empty() {
7170 return;
7171 }
7172
7173 let edited_buffers_already_open = {
7174 let other_editors: Vec<Entity<Editor>> = workspace
7175 .read(cx)
7176 .panes()
7177 .iter()
7178 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
7179 .filter(|editor| editor.entity_id() != cx.entity_id())
7180 .collect();
7181
7182 transaction.0.keys().all(|buffer| {
7183 other_editors.iter().any(|editor| {
7184 let multi_buffer = editor.read(cx).buffer();
7185 multi_buffer.read(cx).is_singleton()
7186 && multi_buffer
7187 .read(cx)
7188 .as_singleton()
7189 .map_or(false, |singleton| {
7190 singleton.entity_id() == buffer.entity_id()
7191 })
7192 })
7193 })
7194 };
7195 if !edited_buffers_already_open {
7196 let workspace = workspace.downgrade();
7197 cx.defer_in(window, move |_, window, cx| {
7198 cx.spawn_in(window, async move |editor, cx| {
7199 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
7200 .await
7201 .ok()
7202 })
7203 .detach();
7204 });
7205 }
7206 }
7207
7208 pub async fn open_project_transaction(
7209 editor: &WeakEntity<Editor>,
7210 workspace: WeakEntity<Workspace>,
7211 transaction: ProjectTransaction,
7212 title: String,
7213 cx: &mut AsyncWindowContext,
7214 ) -> Result<()> {
7215 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7216 cx.update(|_, cx| {
7217 entries.sort_unstable_by_key(|(buffer, _)| {
7218 buffer.read(cx).file().map(|f| f.path().clone())
7219 });
7220 })?;
7221 if entries.is_empty() {
7222 return Ok(());
7223 }
7224
7225 // If the project transaction's edits are all contained within this editor, then
7226 // avoid opening a new editor to display them.
7227
7228 if let [(buffer, transaction)] = &*entries {
7229 let cursor_excerpt = editor.update(cx, |editor, cx| {
7230 let snapshot = editor.buffer().read(cx).snapshot(cx);
7231 let head = editor.selections.newest_anchor().head();
7232 let (buffer_snapshot, excerpt_range) = snapshot.excerpt_containing(head..head)?;
7233 if buffer_snapshot.remote_id() != buffer.read(cx).remote_id() {
7234 return None;
7235 }
7236 Some(excerpt_range)
7237 })?;
7238
7239 if let Some(excerpt_range) = cursor_excerpt {
7240 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7241 let excerpt_range = excerpt_range.context.to_offset(buffer);
7242 buffer
7243 .edited_ranges_for_transaction::<usize>(transaction)
7244 .all(|range| {
7245 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7246 })
7247 });
7248
7249 if all_edits_within_excerpt {
7250 return Ok(());
7251 }
7252 }
7253 }
7254
7255 let mut ranges_to_highlight = Vec::new();
7256 let excerpt_buffer = cx.new(|cx| {
7257 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7258 for (buffer_handle, transaction) in &entries {
7259 let edited_ranges = buffer_handle
7260 .read(cx)
7261 .edited_ranges_for_transaction::<Point>(transaction)
7262 .collect::<Vec<_>>();
7263 multibuffer.set_excerpts_for_path(
7264 PathKey::for_buffer(buffer_handle, cx),
7265 buffer_handle.clone(),
7266 edited_ranges.clone(),
7267 multibuffer_context_lines(cx),
7268 cx,
7269 );
7270 let snapshot = multibuffer.snapshot(cx);
7271 let buffer_snapshot = buffer_handle.read(cx).snapshot();
7272 ranges_to_highlight.extend(edited_ranges.into_iter().filter_map(|range| {
7273 let text_range = buffer_snapshot.anchor_range_inside(range);
7274 let start = snapshot.anchor_in_buffer(text_range.start)?;
7275 let end = snapshot.anchor_in_buffer(text_range.end)?;
7276 Some(start..end)
7277 }));
7278 }
7279 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7280 multibuffer
7281 });
7282
7283 workspace.update_in(cx, |workspace, window, cx| {
7284 let project = workspace.project().clone();
7285 let editor =
7286 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7287 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7288 editor.update(cx, |editor, cx| {
7289 editor.highlight_background(
7290 HighlightKey::Editor,
7291 &ranges_to_highlight,
7292 |_, theme| theme.colors().editor_highlighted_line_background,
7293 cx,
7294 );
7295 });
7296 })?;
7297
7298 Ok(())
7299 }
7300
7301 pub fn clear_code_action_providers(&mut self) {
7302 self.code_action_providers.clear();
7303 self.available_code_actions.take();
7304 }
7305
7306 pub fn add_code_action_provider(
7307 &mut self,
7308 provider: Rc<dyn CodeActionProvider>,
7309 window: &mut Window,
7310 cx: &mut Context<Self>,
7311 ) {
7312 if self
7313 .code_action_providers
7314 .iter()
7315 .any(|existing_provider| existing_provider.id() == provider.id())
7316 {
7317 return;
7318 }
7319
7320 self.code_action_providers.push(provider);
7321 self.refresh_code_actions(window, cx);
7322 }
7323
7324 pub fn remove_code_action_provider(
7325 &mut self,
7326 id: Arc<str>,
7327 window: &mut Window,
7328 cx: &mut Context<Self>,
7329 ) {
7330 self.code_action_providers
7331 .retain(|provider| provider.id() != id);
7332 self.refresh_code_actions(window, cx);
7333 }
7334
7335 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7336 !self.code_action_providers.is_empty()
7337 && EditorSettings::get_global(cx).toolbar.code_actions
7338 }
7339
7340 pub fn has_available_code_actions(&self) -> bool {
7341 self.available_code_actions
7342 .as_ref()
7343 .is_some_and(|(_, actions)| !actions.is_empty())
7344 }
7345
7346 fn render_inline_code_actions(
7347 &self,
7348 icon_size: ui::IconSize,
7349 display_row: DisplayRow,
7350 is_active: bool,
7351 cx: &mut Context<Self>,
7352 ) -> AnyElement {
7353 let show_tooltip = !self.context_menu_visible();
7354 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7355 .icon_size(icon_size)
7356 .shape(ui::IconButtonShape::Square)
7357 .icon_color(ui::Color::Hidden)
7358 .toggle_state(is_active)
7359 .when(show_tooltip, |this| {
7360 this.tooltip({
7361 let focus_handle = self.focus_handle.clone();
7362 move |_window, cx| {
7363 Tooltip::for_action_in(
7364 "Toggle Code Actions",
7365 &ToggleCodeActions {
7366 deployed_from: None,
7367 quick_launch: false,
7368 },
7369 &focus_handle,
7370 cx,
7371 )
7372 }
7373 })
7374 })
7375 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7376 window.focus(&editor.focus_handle(cx), cx);
7377 editor.toggle_code_actions(
7378 &crate::actions::ToggleCodeActions {
7379 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7380 display_row,
7381 )),
7382 quick_launch: false,
7383 },
7384 window,
7385 cx,
7386 );
7387 }))
7388 .into_any_element()
7389 }
7390
7391 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7392 &self.context_menu
7393 }
7394
7395 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7396 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7397 cx.background_executor()
7398 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7399 .await;
7400
7401 let (start_buffer, start, _, end, _newest_selection) = this
7402 .update(cx, |this, cx| {
7403 let newest_selection = this.selections.newest_anchor().clone();
7404 if newest_selection.head().diff_base_anchor().is_some() {
7405 return None;
7406 }
7407 let display_snapshot = this.display_snapshot(cx);
7408 let newest_selection_adjusted =
7409 this.selections.newest_adjusted(&display_snapshot);
7410 let buffer = this.buffer.read(cx);
7411
7412 let (start_buffer, start) =
7413 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7414 let (end_buffer, end) =
7415 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7416
7417 Some((start_buffer, start, end_buffer, end, newest_selection))
7418 })?
7419 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7420 .context(
7421 "Expected selection to lie in a single buffer when refreshing code actions",
7422 )?;
7423 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7424 let providers = this.code_action_providers.clone();
7425 let tasks = this
7426 .code_action_providers
7427 .iter()
7428 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7429 .collect::<Vec<_>>();
7430 (providers, tasks)
7431 })?;
7432
7433 let mut actions = Vec::new();
7434 for (provider, provider_actions) in
7435 providers.into_iter().zip(future::join_all(tasks).await)
7436 {
7437 if let Some(provider_actions) = provider_actions.log_err() {
7438 actions.extend(provider_actions.into_iter().map(|action| {
7439 AvailableCodeAction {
7440 action,
7441 provider: provider.clone(),
7442 }
7443 }));
7444 }
7445 }
7446
7447 this.update(cx, |this, cx| {
7448 this.available_code_actions = if actions.is_empty() {
7449 None
7450 } else {
7451 Some((
7452 Location {
7453 buffer: start_buffer,
7454 range: start..end,
7455 },
7456 actions.into(),
7457 ))
7458 };
7459 cx.notify();
7460 })
7461 }));
7462 }
7463
7464 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7465 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7466 self.show_git_blame_inline = false;
7467
7468 self.show_git_blame_inline_delay_task =
7469 Some(cx.spawn_in(window, async move |this, cx| {
7470 cx.background_executor().timer(delay).await;
7471
7472 this.update(cx, |this, cx| {
7473 this.show_git_blame_inline = true;
7474 cx.notify();
7475 })
7476 .log_err();
7477 }));
7478 }
7479 }
7480
7481 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7482 let snapshot = self.snapshot(window, cx);
7483 let cursor = self
7484 .selections
7485 .newest::<Point>(&snapshot.display_snapshot)
7486 .head();
7487 let Some((buffer, point)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor) else {
7488 return;
7489 };
7490
7491 if self.blame.is_none() {
7492 self.start_git_blame(true, window, cx);
7493 }
7494 let Some(blame) = self.blame.as_ref() else {
7495 return;
7496 };
7497
7498 let row_info = RowInfo {
7499 buffer_id: Some(buffer.remote_id()),
7500 buffer_row: Some(point.row),
7501 ..Default::default()
7502 };
7503 let Some((buffer, blame_entry)) = blame
7504 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7505 .flatten()
7506 else {
7507 return;
7508 };
7509
7510 let anchor = self.selections.newest_anchor().head();
7511 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7512 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7513 self.show_blame_popover(
7514 buffer,
7515 &blame_entry,
7516 position + last_bounds.origin,
7517 true,
7518 cx,
7519 );
7520 };
7521 }
7522
7523 fn show_blame_popover(
7524 &mut self,
7525 buffer: BufferId,
7526 blame_entry: &BlameEntry,
7527 position: gpui::Point<Pixels>,
7528 ignore_timeout: bool,
7529 cx: &mut Context<Self>,
7530 ) {
7531 if let Some(state) = &mut self.inline_blame_popover {
7532 state.hide_task.take();
7533 } else {
7534 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7535 let blame_entry = blame_entry.clone();
7536 let show_task = cx.spawn(async move |editor, cx| {
7537 if !ignore_timeout {
7538 cx.background_executor()
7539 .timer(std::time::Duration::from_millis(blame_popover_delay))
7540 .await;
7541 }
7542 editor
7543 .update(cx, |editor, cx| {
7544 editor.inline_blame_popover_show_task.take();
7545 let Some(blame) = editor.blame.as_ref() else {
7546 return;
7547 };
7548 let blame = blame.read(cx);
7549 let details = blame.details_for_entry(buffer, &blame_entry);
7550 let markdown = cx.new(|cx| {
7551 Markdown::new(
7552 details
7553 .as_ref()
7554 .map(|message| message.message.clone())
7555 .unwrap_or_default(),
7556 None,
7557 None,
7558 cx,
7559 )
7560 });
7561 editor.inline_blame_popover = Some(InlineBlamePopover {
7562 position,
7563 hide_task: None,
7564 popover_bounds: None,
7565 popover_state: InlineBlamePopoverState {
7566 scroll_handle: ScrollHandle::new(),
7567 commit_message: details,
7568 markdown,
7569 },
7570 keyboard_grace: ignore_timeout,
7571 });
7572 cx.notify();
7573 })
7574 .ok();
7575 });
7576 self.inline_blame_popover_show_task = Some(show_task);
7577 }
7578 }
7579
7580 pub fn has_mouse_context_menu(&self) -> bool {
7581 self.mouse_context_menu.is_some()
7582 }
7583
7584 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7585 self.inline_blame_popover_show_task.take();
7586 if let Some(state) = &mut self.inline_blame_popover {
7587 let hide_task = cx.spawn(async move |editor, cx| {
7588 if !ignore_timeout {
7589 cx.background_executor()
7590 .timer(std::time::Duration::from_millis(100))
7591 .await;
7592 }
7593 editor
7594 .update(cx, |editor, cx| {
7595 editor.inline_blame_popover.take();
7596 cx.notify();
7597 })
7598 .ok();
7599 });
7600 state.hide_task = Some(hide_task);
7601 true
7602 } else {
7603 false
7604 }
7605 }
7606
7607 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7608 if self.pending_rename.is_some() {
7609 return None;
7610 }
7611
7612 let provider = self.semantics_provider.clone()?;
7613 let buffer = self.buffer.read(cx);
7614 let newest_selection = self.selections.newest_anchor().clone();
7615 let cursor_position = newest_selection.head();
7616 let (cursor_buffer, cursor_buffer_position) =
7617 buffer.text_anchor_for_position(cursor_position, cx)?;
7618 let (tail_buffer, tail_buffer_position) =
7619 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7620 if cursor_buffer != tail_buffer {
7621 return None;
7622 }
7623
7624 let snapshot = cursor_buffer.read(cx).snapshot();
7625 let word_ranges = cx.background_spawn(async move {
7626 // this might look odd to put on the background thread, but
7627 // `surrounding_word` can be quite expensive as it calls into
7628 // tree-sitter language scopes
7629 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7630 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7631 (start_word_range, end_word_range)
7632 });
7633
7634 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7635 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7636 let (start_word_range, end_word_range) = word_ranges.await;
7637 if start_word_range != end_word_range {
7638 this.update(cx, |this, cx| {
7639 this.document_highlights_task.take();
7640 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7641 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7642 })
7643 .ok();
7644 return;
7645 }
7646 cx.background_executor()
7647 .timer(Duration::from_millis(debounce))
7648 .await;
7649
7650 let highlights = if let Some(highlights) = cx.update(|cx| {
7651 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7652 }) {
7653 highlights.await.log_err()
7654 } else {
7655 None
7656 };
7657
7658 if let Some(highlights) = highlights {
7659 this.update(cx, |this, cx| {
7660 if this.pending_rename.is_some() {
7661 return;
7662 }
7663
7664 let buffer = this.buffer.read(cx);
7665 if buffer
7666 .text_anchor_for_position(cursor_position, cx)
7667 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7668 {
7669 return;
7670 }
7671
7672 let mut write_ranges = Vec::new();
7673 let mut read_ranges = Vec::new();
7674 let multibuffer_snapshot = buffer.snapshot(cx);
7675 for highlight in highlights {
7676 for range in
7677 multibuffer_snapshot.buffer_range_to_excerpt_ranges(highlight.range)
7678 {
7679 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7680 write_ranges.push(range);
7681 } else {
7682 read_ranges.push(range);
7683 }
7684 }
7685 }
7686
7687 this.highlight_background(
7688 HighlightKey::DocumentHighlightRead,
7689 &read_ranges,
7690 |_, theme| theme.colors().editor_document_highlight_read_background,
7691 cx,
7692 );
7693 this.highlight_background(
7694 HighlightKey::DocumentHighlightWrite,
7695 &write_ranges,
7696 |_, theme| theme.colors().editor_document_highlight_write_background,
7697 cx,
7698 );
7699 cx.notify();
7700 })
7701 .log_err();
7702 }
7703 }));
7704 None
7705 }
7706
7707 fn prepare_highlight_query_from_selection(
7708 &mut self,
7709 snapshot: &DisplaySnapshot,
7710 cx: &mut Context<Editor>,
7711 ) -> Option<(String, Range<Anchor>)> {
7712 if matches!(self.mode, EditorMode::SingleLine) {
7713 return None;
7714 }
7715 if !self.use_selection_highlight || !EditorSettings::get_global(cx).selection_highlight {
7716 return None;
7717 }
7718 if self.selections.count() != 1 || self.selections.line_mode() {
7719 return None;
7720 }
7721 let selection = self.selections.newest::<Point>(&snapshot);
7722 // If the selection spans multiple rows OR it is empty
7723 if selection.start.row != selection.end.row
7724 || selection.start.column == selection.end.column
7725 {
7726 return None;
7727 }
7728 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7729 let query = snapshot
7730 .buffer_snapshot()
7731 .text_for_range(selection_anchor_range.clone())
7732 .collect::<String>();
7733 if query.trim().is_empty() {
7734 return None;
7735 }
7736 Some((query, selection_anchor_range))
7737 }
7738
7739 #[ztracing::instrument(skip_all)]
7740 fn update_selection_occurrence_highlights(
7741 &mut self,
7742 multi_buffer_snapshot: MultiBufferSnapshot,
7743 query_text: String,
7744 query_range: Range<Anchor>,
7745 multi_buffer_range_to_query: Range<Point>,
7746 use_debounce: bool,
7747 window: &mut Window,
7748 cx: &mut Context<Editor>,
7749 ) -> Task<()> {
7750 cx.spawn_in(window, async move |editor, cx| {
7751 if use_debounce {
7752 cx.background_executor()
7753 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7754 .await;
7755 }
7756 let match_task = cx.background_spawn(async move {
7757 let buffer_ranges = multi_buffer_snapshot
7758 .range_to_buffer_ranges(
7759 multi_buffer_range_to_query.start..multi_buffer_range_to_query.end,
7760 )
7761 .into_iter()
7762 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7763 let mut match_ranges = Vec::new();
7764 let Ok(regex) = project::search::SearchQuery::text(
7765 query_text,
7766 false,
7767 false,
7768 false,
7769 Default::default(),
7770 Default::default(),
7771 false,
7772 None,
7773 ) else {
7774 return Vec::default();
7775 };
7776 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7777 for (buffer_snapshot, search_range, _) in buffer_ranges {
7778 match_ranges.extend(
7779 regex
7780 .search(
7781 &buffer_snapshot,
7782 Some(search_range.start.0..search_range.end.0),
7783 )
7784 .await
7785 .into_iter()
7786 .filter_map(|match_range| {
7787 let match_start = buffer_snapshot
7788 .anchor_after(search_range.start + match_range.start);
7789 let match_end = buffer_snapshot
7790 .anchor_before(search_range.start + match_range.end);
7791 {
7792 let range = multi_buffer_snapshot
7793 .anchor_in_buffer(match_start)?
7794 ..multi_buffer_snapshot.anchor_in_buffer(match_end)?;
7795 Some(range).filter(|match_anchor_range| {
7796 match_anchor_range != &query_range
7797 })
7798 }
7799 }),
7800 );
7801 }
7802 match_ranges
7803 });
7804 let match_ranges = match_task.await;
7805 editor
7806 .update_in(cx, |editor, _, cx| {
7807 if use_debounce {
7808 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7809 editor.debounced_selection_highlight_complete = true;
7810 } else if editor.debounced_selection_highlight_complete {
7811 return;
7812 }
7813 if !match_ranges.is_empty() {
7814 editor.highlight_background(
7815 HighlightKey::SelectedTextHighlight,
7816 &match_ranges,
7817 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7818 cx,
7819 )
7820 }
7821 })
7822 .log_err();
7823 })
7824 }
7825
7826 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7827 struct NewlineFold;
7828 let type_id = std::any::TypeId::of::<NewlineFold>();
7829 if !self.mode.is_single_line() {
7830 return;
7831 }
7832 let snapshot = self.snapshot(window, cx);
7833 if snapshot.buffer_snapshot().max_point().row == 0 {
7834 return;
7835 }
7836 let task = cx.background_spawn(async move {
7837 let new_newlines = snapshot
7838 .buffer_chars_at(MultiBufferOffset(0))
7839 .filter_map(|(c, i)| {
7840 if c == '\n' {
7841 Some(
7842 snapshot.buffer_snapshot().anchor_after(i)
7843 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7844 )
7845 } else {
7846 None
7847 }
7848 })
7849 .collect::<Vec<_>>();
7850 let existing_newlines = snapshot
7851 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7852 .filter_map(|fold| {
7853 if fold.placeholder.type_tag == Some(type_id) {
7854 Some(fold.range.start..fold.range.end)
7855 } else {
7856 None
7857 }
7858 })
7859 .collect::<Vec<_>>();
7860
7861 (new_newlines, existing_newlines)
7862 });
7863 self.folding_newlines = cx.spawn(async move |this, cx| {
7864 let (new_newlines, existing_newlines) = task.await;
7865 if new_newlines == existing_newlines {
7866 return;
7867 }
7868 let placeholder = FoldPlaceholder {
7869 render: Arc::new(move |_, _, cx| {
7870 div()
7871 .bg(cx.theme().status().hint_background)
7872 .border_b_1()
7873 .size_full()
7874 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7875 .border_color(cx.theme().status().hint)
7876 .child("\\n")
7877 .into_any()
7878 }),
7879 constrain_width: false,
7880 merge_adjacent: false,
7881 type_tag: Some(type_id),
7882 collapsed_text: None,
7883 };
7884 let creases = new_newlines
7885 .into_iter()
7886 .map(|range| Crease::simple(range, placeholder.clone()))
7887 .collect();
7888 this.update(cx, |this, cx| {
7889 this.display_map.update(cx, |display_map, cx| {
7890 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7891 display_map.fold(creases, cx);
7892 });
7893 })
7894 .ok();
7895 });
7896 }
7897
7898 #[ztracing::instrument(skip_all)]
7899 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7900 if !self.lsp_data_enabled() {
7901 return;
7902 }
7903 let cursor = self.selections.newest_anchor().head();
7904 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7905
7906 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7907 self.outline_symbols_at_cursor =
7908 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7909 cx.emit(EditorEvent::OutlineSymbolsChanged);
7910 cx.notify();
7911 } else {
7912 let syntax = cx.theme().syntax().clone();
7913 let background_task = cx.background_spawn(async move {
7914 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7915 });
7916 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7917 cx.spawn(async move |this, cx| {
7918 let symbols = background_task.await;
7919 this.update(cx, |this, cx| {
7920 this.outline_symbols_at_cursor = symbols;
7921 cx.emit(EditorEvent::OutlineSymbolsChanged);
7922 cx.notify();
7923 })
7924 .ok();
7925 });
7926 }
7927 }
7928
7929 #[ztracing::instrument(skip_all)]
7930 fn refresh_selected_text_highlights(
7931 &mut self,
7932 snapshot: &DisplaySnapshot,
7933 on_buffer_edit: bool,
7934 window: &mut Window,
7935 cx: &mut Context<Editor>,
7936 ) {
7937 let Some((query_text, query_range)) =
7938 self.prepare_highlight_query_from_selection(snapshot, cx)
7939 else {
7940 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7941 self.quick_selection_highlight_task.take();
7942 self.debounced_selection_highlight_task.take();
7943 self.debounced_selection_highlight_complete = false;
7944 return;
7945 };
7946 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7947 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7948 let query_changed = self
7949 .quick_selection_highlight_task
7950 .as_ref()
7951 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7952 if query_changed {
7953 self.debounced_selection_highlight_complete = false;
7954 }
7955 if on_buffer_edit || query_changed {
7956 self.quick_selection_highlight_task = Some((
7957 query_range.clone(),
7958 self.update_selection_occurrence_highlights(
7959 snapshot.buffer.clone(),
7960 query_text.clone(),
7961 query_range.clone(),
7962 self.multi_buffer_visible_range(&display_snapshot, cx),
7963 false,
7964 window,
7965 cx,
7966 ),
7967 ));
7968 }
7969 if on_buffer_edit
7970 || self
7971 .debounced_selection_highlight_task
7972 .as_ref()
7973 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7974 {
7975 let multi_buffer_start = multi_buffer_snapshot
7976 .anchor_before(MultiBufferOffset(0))
7977 .to_point(&multi_buffer_snapshot);
7978 let multi_buffer_end = multi_buffer_snapshot
7979 .anchor_after(multi_buffer_snapshot.len())
7980 .to_point(&multi_buffer_snapshot);
7981 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7982 self.debounced_selection_highlight_task = Some((
7983 query_range.clone(),
7984 self.update_selection_occurrence_highlights(
7985 snapshot.buffer.clone(),
7986 query_text,
7987 query_range,
7988 multi_buffer_full_range,
7989 true,
7990 window,
7991 cx,
7992 ),
7993 ));
7994 }
7995 }
7996
7997 pub fn multi_buffer_visible_range(
7998 &self,
7999 display_snapshot: &DisplaySnapshot,
8000 cx: &App,
8001 ) -> Range<Point> {
8002 let visible_start = self
8003 .scroll_manager
8004 .native_anchor(display_snapshot, cx)
8005 .anchor
8006 .to_point(display_snapshot.buffer_snapshot())
8007 .to_display_point(display_snapshot);
8008
8009 let mut target_end = visible_start;
8010 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
8011
8012 visible_start.to_point(display_snapshot)
8013 ..display_snapshot
8014 .clip_point(target_end, Bias::Right)
8015 .to_point(display_snapshot)
8016 }
8017
8018 pub fn refresh_edit_prediction(
8019 &mut self,
8020 debounce: bool,
8021 user_requested: bool,
8022 window: &mut Window,
8023 cx: &mut Context<Self>,
8024 ) -> Option<()> {
8025 if self.leader_id.is_some() {
8026 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8027 return None;
8028 }
8029
8030 let cursor = self.selections.newest_anchor().head();
8031 let (buffer, cursor_buffer_position) =
8032 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8033
8034 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8035 return None;
8036 }
8037
8038 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
8039 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8040 return None;
8041 }
8042
8043 self.update_visible_edit_prediction(window, cx);
8044
8045 if !user_requested
8046 && (!self.should_show_edit_predictions()
8047 || !self.is_focused(window)
8048 || buffer.read(cx).is_empty())
8049 {
8050 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8051 return None;
8052 }
8053
8054 self.edit_prediction_provider()?
8055 .refresh(buffer, cursor_buffer_position, debounce, cx);
8056 Some(())
8057 }
8058
8059 fn show_edit_predictions_in_menu(&self) -> bool {
8060 match self.edit_prediction_settings {
8061 EditPredictionSettings::Disabled => false,
8062 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
8063 }
8064 }
8065
8066 pub fn edit_predictions_enabled(&self) -> bool {
8067 match self.edit_prediction_settings {
8068 EditPredictionSettings::Disabled => false,
8069 EditPredictionSettings::Enabled { .. } => true,
8070 }
8071 }
8072
8073 fn edit_prediction_requires_modifier(&self) -> bool {
8074 match self.edit_prediction_settings {
8075 EditPredictionSettings::Disabled => false,
8076 EditPredictionSettings::Enabled {
8077 preview_requires_modifier,
8078 ..
8079 } => preview_requires_modifier,
8080 }
8081 }
8082
8083 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
8084 if self.edit_prediction_provider.is_none() {
8085 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8086 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8087 return;
8088 }
8089
8090 let selection = self.selections.newest_anchor();
8091 let cursor = selection.head();
8092
8093 if let Some((buffer, cursor_buffer_position)) =
8094 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8095 {
8096 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8097 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8098 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8099 return;
8100 }
8101 self.edit_prediction_settings =
8102 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8103 }
8104 }
8105
8106 fn edit_prediction_settings_at_position(
8107 &self,
8108 buffer: &Entity<Buffer>,
8109 buffer_position: language::Anchor,
8110 cx: &App,
8111 ) -> EditPredictionSettings {
8112 if !self.mode.is_full()
8113 || !self.show_edit_predictions_override.unwrap_or(true)
8114 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
8115 {
8116 return EditPredictionSettings::Disabled;
8117 }
8118
8119 if !LanguageSettings::for_buffer(&buffer.read(cx), cx).show_edit_predictions {
8120 return EditPredictionSettings::Disabled;
8121 };
8122
8123 let by_provider = matches!(
8124 self.menu_edit_predictions_policy,
8125 MenuEditPredictionsPolicy::ByProvider
8126 );
8127
8128 let show_in_menu = by_provider
8129 && self
8130 .edit_prediction_provider
8131 .as_ref()
8132 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
8133
8134 let file = buffer.read(cx).file();
8135 let preview_requires_modifier =
8136 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
8137
8138 EditPredictionSettings::Enabled {
8139 show_in_menu,
8140 preview_requires_modifier,
8141 }
8142 }
8143
8144 fn should_show_edit_predictions(&self) -> bool {
8145 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
8146 }
8147
8148 pub fn edit_prediction_preview_is_active(&self) -> bool {
8149 matches!(
8150 self.edit_prediction_preview,
8151 EditPredictionPreview::Active { .. }
8152 )
8153 }
8154
8155 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
8156 let cursor = self.selections.newest_anchor().head();
8157 if let Some((buffer, cursor_position)) =
8158 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8159 {
8160 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
8161 } else {
8162 false
8163 }
8164 }
8165
8166 pub fn supports_minimap(&self, cx: &App) -> bool {
8167 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
8168 }
8169
8170 fn edit_predictions_enabled_in_buffer(
8171 &self,
8172 buffer: &Entity<Buffer>,
8173 buffer_position: language::Anchor,
8174 cx: &App,
8175 ) -> bool {
8176 maybe!({
8177 if self.read_only(cx) || self.leader_id.is_some() {
8178 return Some(false);
8179 }
8180 let provider = self.edit_prediction_provider()?;
8181 if !provider.is_enabled(buffer, buffer_position, cx) {
8182 return Some(false);
8183 }
8184 let buffer = buffer.read(cx);
8185 let Some(file) = buffer.file() else {
8186 return Some(true);
8187 };
8188 let settings = all_language_settings(Some(file), cx);
8189 Some(settings.edit_predictions_enabled_for_file(file, cx))
8190 })
8191 .unwrap_or(false)
8192 }
8193
8194 pub fn show_edit_prediction(
8195 &mut self,
8196 _: &ShowEditPrediction,
8197 window: &mut Window,
8198 cx: &mut Context<Self>,
8199 ) {
8200 if !self.has_active_edit_prediction() {
8201 self.refresh_edit_prediction(false, true, window, cx);
8202 return;
8203 }
8204
8205 self.update_visible_edit_prediction(window, cx);
8206 }
8207
8208 pub fn display_cursor_names(
8209 &mut self,
8210 _: &DisplayCursorNames,
8211 window: &mut Window,
8212 cx: &mut Context<Self>,
8213 ) {
8214 self.show_cursor_names(window, cx);
8215 }
8216
8217 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8218 self.show_cursor_names = true;
8219 cx.notify();
8220 cx.spawn_in(window, async move |this, cx| {
8221 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8222 this.update(cx, |this, cx| {
8223 this.show_cursor_names = false;
8224 cx.notify()
8225 })
8226 .ok()
8227 })
8228 .detach();
8229 }
8230
8231 pub fn accept_partial_edit_prediction(
8232 &mut self,
8233 granularity: EditPredictionGranularity,
8234 window: &mut Window,
8235 cx: &mut Context<Self>,
8236 ) {
8237 if self.show_edit_predictions_in_menu() {
8238 self.hide_context_menu(window, cx);
8239 }
8240
8241 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8242 return;
8243 };
8244
8245 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8246 return;
8247 }
8248
8249 match &active_edit_prediction.completion {
8250 EditPrediction::MoveWithin { target, .. } => {
8251 let target = *target;
8252
8253 if matches!(granularity, EditPredictionGranularity::Full) {
8254 if let Some(position_map) = &self.last_position_map {
8255 let target_row = target.to_display_point(&position_map.snapshot).row();
8256 let is_visible = position_map.visible_row_range.contains(&target_row);
8257
8258 if is_visible || !self.edit_prediction_requires_modifier() {
8259 self.unfold_ranges(&[target..target], true, false, cx);
8260 self.change_selections(
8261 SelectionEffects::scroll(Autoscroll::newest()),
8262 window,
8263 cx,
8264 |selections| {
8265 selections.select_anchor_ranges([target..target]);
8266 },
8267 );
8268 self.clear_row_highlights::<EditPredictionPreview>();
8269 self.edit_prediction_preview
8270 .set_previous_scroll_position(None);
8271 } else {
8272 // Highlight and request scroll
8273 self.edit_prediction_preview
8274 .set_previous_scroll_position(Some(
8275 position_map.snapshot.scroll_anchor,
8276 ));
8277 self.highlight_rows::<EditPredictionPreview>(
8278 target..target,
8279 cx.theme().colors().editor_highlighted_line_background,
8280 RowHighlightOptions {
8281 autoscroll: true,
8282 ..Default::default()
8283 },
8284 cx,
8285 );
8286 self.request_autoscroll(Autoscroll::fit(), cx);
8287 }
8288 }
8289 } else {
8290 self.change_selections(
8291 SelectionEffects::scroll(Autoscroll::newest()),
8292 window,
8293 cx,
8294 |selections| {
8295 selections.select_anchor_ranges([target..target]);
8296 },
8297 );
8298 }
8299 }
8300 EditPrediction::MoveOutside { snapshot, target } => {
8301 if let Some(workspace) = self.workspace() {
8302 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8303 .detach_and_log_err(cx);
8304 }
8305 }
8306 EditPrediction::Edit {
8307 edits,
8308 cursor_position,
8309 ..
8310 } => {
8311 self.report_edit_prediction_event(
8312 active_edit_prediction.completion_id.clone(),
8313 true,
8314 cx,
8315 );
8316
8317 match granularity {
8318 EditPredictionGranularity::Full => {
8319 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8320
8321 // Compute fallback cursor position BEFORE applying the edit,
8322 // so the anchor tracks through the edit correctly
8323 let fallback_cursor_target = {
8324 let snapshot = self.buffer.read(cx).snapshot(cx);
8325 edits.last().unwrap().0.end.bias_right(&snapshot)
8326 };
8327
8328 self.buffer.update(cx, |buffer, cx| {
8329 buffer.edit(edits.iter().cloned(), None, cx)
8330 });
8331
8332 if let Some(provider) = self.edit_prediction_provider() {
8333 provider.accept(cx);
8334 }
8335
8336 // Resolve cursor position after the edit is applied
8337 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8338 // The anchor tracks through the edit, then we add the offset
8339 let snapshot = self.buffer.read(cx).snapshot(cx);
8340 let base_offset = anchor.to_offset(&snapshot).0;
8341 let target_offset =
8342 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8343 snapshot.anchor_after(target_offset)
8344 } else {
8345 fallback_cursor_target
8346 };
8347
8348 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8349 s.select_anchor_ranges([cursor_target..cursor_target]);
8350 });
8351
8352 let selections = self.selections.disjoint_anchors_arc();
8353 if let Some(transaction_id_now) =
8354 self.buffer.read(cx).last_transaction_id(cx)
8355 {
8356 if transaction_id_prev != Some(transaction_id_now) {
8357 self.selection_history
8358 .insert_transaction(transaction_id_now, selections);
8359 }
8360 }
8361
8362 self.update_visible_edit_prediction(window, cx);
8363 if self.active_edit_prediction.is_none() {
8364 self.refresh_edit_prediction(true, true, window, cx);
8365 }
8366 cx.notify();
8367 }
8368 _ => {
8369 let snapshot = self.buffer.read(cx).snapshot(cx);
8370 let cursor_offset = self
8371 .selections
8372 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8373 .head();
8374
8375 let insertion = edits.iter().find_map(|(range, text)| {
8376 let range = range.to_offset(&snapshot);
8377 if range.is_empty() && range.start == cursor_offset {
8378 Some(text)
8379 } else {
8380 None
8381 }
8382 });
8383
8384 if let Some(text) = insertion {
8385 let text_to_insert = match granularity {
8386 EditPredictionGranularity::Word => {
8387 let mut partial = text
8388 .chars()
8389 .by_ref()
8390 .take_while(|c| c.is_alphabetic())
8391 .collect::<String>();
8392 if partial.is_empty() {
8393 partial = text
8394 .chars()
8395 .by_ref()
8396 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8397 .collect::<String>();
8398 }
8399 partial
8400 }
8401 EditPredictionGranularity::Line => {
8402 if let Some(line) = text.split_inclusive('\n').next() {
8403 line.to_string()
8404 } else {
8405 text.to_string()
8406 }
8407 }
8408 EditPredictionGranularity::Full => unreachable!(),
8409 };
8410
8411 cx.emit(EditorEvent::InputHandled {
8412 utf16_range_to_replace: None,
8413 text: text_to_insert.clone().into(),
8414 });
8415
8416 self.replace_selections(&text_to_insert, None, window, cx, false);
8417 self.refresh_edit_prediction(true, true, window, cx);
8418 cx.notify();
8419 } else {
8420 self.accept_partial_edit_prediction(
8421 EditPredictionGranularity::Full,
8422 window,
8423 cx,
8424 );
8425 }
8426 }
8427 }
8428 }
8429 }
8430 }
8431
8432 pub fn accept_next_word_edit_prediction(
8433 &mut self,
8434 _: &AcceptNextWordEditPrediction,
8435 window: &mut Window,
8436 cx: &mut Context<Self>,
8437 ) {
8438 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8439 }
8440
8441 pub fn accept_next_line_edit_prediction(
8442 &mut self,
8443 _: &AcceptNextLineEditPrediction,
8444 window: &mut Window,
8445 cx: &mut Context<Self>,
8446 ) {
8447 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8448 }
8449
8450 pub fn accept_edit_prediction(
8451 &mut self,
8452 _: &AcceptEditPrediction,
8453 window: &mut Window,
8454 cx: &mut Context<Self>,
8455 ) {
8456 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8457 }
8458
8459 fn discard_edit_prediction(
8460 &mut self,
8461 reason: EditPredictionDiscardReason,
8462 cx: &mut Context<Self>,
8463 ) -> bool {
8464 if reason == EditPredictionDiscardReason::Rejected {
8465 let completion_id = self
8466 .active_edit_prediction
8467 .as_ref()
8468 .and_then(|active_completion| active_completion.completion_id.clone());
8469
8470 self.report_edit_prediction_event(completion_id, false, cx);
8471 }
8472
8473 if let Some(provider) = self.edit_prediction_provider() {
8474 provider.discard(reason, cx);
8475 }
8476
8477 self.take_active_edit_prediction(reason == EditPredictionDiscardReason::Ignored, cx)
8478 }
8479
8480 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8481 let Some(provider) = self.edit_prediction_provider() else {
8482 return;
8483 };
8484
8485 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
8486 let Some((position, _)) =
8487 buffer_snapshot.anchor_to_buffer_anchor(self.selections.newest_anchor().head())
8488 else {
8489 return;
8490 };
8491 let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
8492 return;
8493 };
8494
8495 let extension = buffer
8496 .read(cx)
8497 .file()
8498 .and_then(|file| Some(file.path().extension()?.to_string()));
8499
8500 let event_type = match accepted {
8501 true => "Edit Prediction Accepted",
8502 false => "Edit Prediction Discarded",
8503 };
8504 telemetry::event!(
8505 event_type,
8506 provider = provider.name(),
8507 prediction_id = id,
8508 suggestion_accepted = accepted,
8509 file_extension = extension,
8510 );
8511 }
8512
8513 fn open_editor_at_anchor(
8514 snapshot: &language::BufferSnapshot,
8515 target: language::Anchor,
8516 workspace: &Entity<Workspace>,
8517 window: &mut Window,
8518 cx: &mut App,
8519 ) -> Task<Result<()>> {
8520 workspace.update(cx, |workspace, cx| {
8521 let path = snapshot.file().map(|file| file.full_path(cx));
8522 let Some(path) =
8523 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8524 else {
8525 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8526 };
8527 let target = text::ToPoint::to_point(&target, snapshot);
8528 let item = workspace.open_path(path, None, true, window, cx);
8529 window.spawn(cx, async move |cx| {
8530 let Some(editor) = item.await?.downcast::<Editor>() else {
8531 return Ok(());
8532 };
8533 editor
8534 .update_in(cx, |editor, window, cx| {
8535 editor.go_to_singleton_buffer_point(target, window, cx);
8536 })
8537 .ok();
8538 anyhow::Ok(())
8539 })
8540 })
8541 }
8542
8543 pub fn has_active_edit_prediction(&self) -> bool {
8544 self.active_edit_prediction.is_some()
8545 }
8546
8547 fn take_active_edit_prediction(
8548 &mut self,
8549 preserve_stale_in_menu: bool,
8550 cx: &mut Context<Self>,
8551 ) -> bool {
8552 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8553 if !preserve_stale_in_menu {
8554 self.stale_edit_prediction_in_menu = None;
8555 }
8556 return false;
8557 };
8558
8559 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8560 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8561 self.stale_edit_prediction_in_menu =
8562 preserve_stale_in_menu.then_some(active_edit_prediction);
8563 true
8564 }
8565
8566 /// Returns true when we're displaying the edit prediction popover below the cursor
8567 /// like we are not previewing and the LSP autocomplete menu is visible
8568 /// or we are in `when_holding_modifier` mode.
8569 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8570 if self.edit_prediction_preview_is_active()
8571 || !self.show_edit_predictions_in_menu()
8572 || !self.edit_predictions_enabled()
8573 {
8574 return false;
8575 }
8576
8577 if self.has_visible_completions_menu() {
8578 return true;
8579 }
8580
8581 has_completion && self.edit_prediction_requires_modifier()
8582 }
8583
8584 fn handle_modifiers_changed(
8585 &mut self,
8586 modifiers: Modifiers,
8587 position_map: &PositionMap,
8588 window: &mut Window,
8589 cx: &mut Context<Self>,
8590 ) {
8591 self.update_edit_prediction_settings(cx);
8592
8593 // Ensure that the edit prediction preview is updated, even when not
8594 // enabled, if there's an active edit prediction preview.
8595 if self.show_edit_predictions_in_menu()
8596 || self.edit_prediction_requires_modifier()
8597 || matches!(
8598 self.edit_prediction_preview,
8599 EditPredictionPreview::Active { .. }
8600 )
8601 {
8602 self.update_edit_prediction_preview(&modifiers, window, cx);
8603 }
8604
8605 self.update_selection_mode(&modifiers, position_map, window, cx);
8606
8607 let mouse_position = window.mouse_position();
8608 if !position_map.text_hitbox.is_hovered(window) {
8609 return;
8610 }
8611
8612 self.update_hovered_link(
8613 position_map.point_for_position(mouse_position),
8614 Some(mouse_position),
8615 &position_map.snapshot,
8616 modifiers,
8617 window,
8618 cx,
8619 )
8620 }
8621
8622 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8623 match EditorSettings::get_global(cx).multi_cursor_modifier {
8624 MultiCursorModifier::Alt => modifiers.secondary(),
8625 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8626 }
8627 }
8628
8629 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8630 match EditorSettings::get_global(cx).multi_cursor_modifier {
8631 MultiCursorModifier::Alt => modifiers.alt,
8632 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8633 }
8634 }
8635
8636 fn columnar_selection_mode(
8637 modifiers: &Modifiers,
8638 cx: &mut Context<Self>,
8639 ) -> Option<ColumnarMode> {
8640 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8641 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8642 Some(ColumnarMode::FromMouse)
8643 } else if Self::is_alt_pressed(modifiers, cx) {
8644 Some(ColumnarMode::FromSelection)
8645 } else {
8646 None
8647 }
8648 } else {
8649 None
8650 }
8651 }
8652
8653 fn update_selection_mode(
8654 &mut self,
8655 modifiers: &Modifiers,
8656 position_map: &PositionMap,
8657 window: &mut Window,
8658 cx: &mut Context<Self>,
8659 ) {
8660 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8661 return;
8662 };
8663 if self.selections.pending_anchor().is_none() {
8664 return;
8665 }
8666
8667 let mouse_position = window.mouse_position();
8668 let point_for_position = position_map.point_for_position(mouse_position);
8669 let position = point_for_position.previous_valid;
8670
8671 self.select(
8672 SelectPhase::BeginColumnar {
8673 position,
8674 reset: false,
8675 mode,
8676 goal_column: point_for_position.exact_unclipped.column(),
8677 },
8678 window,
8679 cx,
8680 );
8681 }
8682
8683 fn update_edit_prediction_preview(
8684 &mut self,
8685 modifiers: &Modifiers,
8686 window: &mut Window,
8687 cx: &mut Context<Self>,
8688 ) {
8689 let modifiers_held = self.edit_prediction_preview_modifiers_held(modifiers, window, cx);
8690
8691 if modifiers_held {
8692 if matches!(
8693 self.edit_prediction_preview,
8694 EditPredictionPreview::Inactive { .. }
8695 ) {
8696 self.edit_prediction_preview = EditPredictionPreview::Active {
8697 previous_scroll_position: None,
8698 since: Instant::now(),
8699 };
8700
8701 self.update_visible_edit_prediction(window, cx);
8702 cx.notify();
8703 }
8704 } else if let EditPredictionPreview::Active {
8705 previous_scroll_position,
8706 since,
8707 } = self.edit_prediction_preview
8708 {
8709 if let (Some(previous_scroll_position), Some(position_map)) =
8710 (previous_scroll_position, self.last_position_map.as_ref())
8711 {
8712 self.set_scroll_position(
8713 previous_scroll_position
8714 .scroll_position(&position_map.snapshot.display_snapshot),
8715 window,
8716 cx,
8717 );
8718 }
8719
8720 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8721 released_too_fast: since.elapsed() < Duration::from_millis(200),
8722 };
8723 self.clear_row_highlights::<EditPredictionPreview>();
8724 self.update_visible_edit_prediction(window, cx);
8725 cx.notify();
8726 }
8727 }
8728
8729 fn update_visible_edit_prediction(
8730 &mut self,
8731 _window: &mut Window,
8732 cx: &mut Context<Self>,
8733 ) -> Option<()> {
8734 if self.ime_transaction.is_some() {
8735 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8736 return None;
8737 }
8738
8739 let selection = self.selections.newest_anchor();
8740 let multibuffer = self.buffer.read(cx).snapshot(cx);
8741 let cursor = selection.head();
8742 let (cursor_text_anchor, _) = multibuffer.anchor_to_buffer_anchor(cursor)?;
8743 let buffer = self.buffer.read(cx).buffer(cursor_text_anchor.buffer_id)?;
8744
8745 // Check project-level disable_ai setting for the current buffer
8746 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8747 return None;
8748 }
8749 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8750
8751 let show_in_menu = self.show_edit_predictions_in_menu();
8752 let completions_menu_has_precedence = !show_in_menu
8753 && (self.context_menu.borrow().is_some()
8754 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8755
8756 if completions_menu_has_precedence
8757 || !offset_selection.is_empty()
8758 || self
8759 .active_edit_prediction
8760 .as_ref()
8761 .is_some_and(|completion| {
8762 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8763 return false;
8764 };
8765 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8766 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8767 !invalidation_range.contains(&offset_selection.head())
8768 })
8769 {
8770 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8771 return None;
8772 }
8773
8774 self.take_active_edit_prediction(true, cx);
8775 let Some(provider) = self.edit_prediction_provider() else {
8776 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8777 return None;
8778 };
8779
8780 self.edit_prediction_settings =
8781 self.edit_prediction_settings_at_position(&buffer, cursor_text_anchor, cx);
8782
8783 self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor);
8784
8785 if self.in_leading_whitespace {
8786 let cursor_point = cursor.to_point(&multibuffer);
8787 let mut suggested_indent = None;
8788 multibuffer.suggested_indents_callback(
8789 cursor_point.row..cursor_point.row + 1,
8790 &mut |_, indent| {
8791 suggested_indent = Some(indent);
8792 ControlFlow::Break(())
8793 },
8794 cx,
8795 );
8796
8797 if let Some(indent) = suggested_indent
8798 && indent.len == cursor_point.column
8799 {
8800 self.in_leading_whitespace = false;
8801 }
8802 }
8803
8804 let edit_prediction = provider.suggest(&buffer, cursor_text_anchor, cx)?;
8805
8806 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8807 {
8808 edit_prediction_types::EditPrediction::Local {
8809 id,
8810 edits,
8811 cursor_position,
8812 edit_preview,
8813 } => (id, edits, cursor_position, edit_preview),
8814 edit_prediction_types::EditPrediction::Jump {
8815 id,
8816 snapshot,
8817 target,
8818 } => {
8819 if let Some(provider) = &self.edit_prediction_provider {
8820 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8821 }
8822 self.stale_edit_prediction_in_menu = None;
8823 self.active_edit_prediction = Some(EditPredictionState {
8824 inlay_ids: vec![],
8825 completion: EditPrediction::MoveOutside { snapshot, target },
8826 completion_id: id,
8827 invalidation_range: None,
8828 });
8829 cx.notify();
8830 return Some(());
8831 }
8832 };
8833
8834 let edits = edits
8835 .into_iter()
8836 .flat_map(|(range, new_text)| {
8837 Some((
8838 multibuffer.buffer_anchor_range_to_anchor_range(range)?,
8839 new_text,
8840 ))
8841 })
8842 .collect::<Vec<_>>();
8843 if edits.is_empty() {
8844 return None;
8845 }
8846
8847 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8848 let anchor = multibuffer.anchor_in_excerpt(predicted.anchor)?;
8849 Some((anchor, predicted.offset))
8850 });
8851
8852 let first_edit_start = edits.first().unwrap().0.start;
8853 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8854 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8855
8856 let last_edit_end = edits.last().unwrap().0.end;
8857 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8858 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8859
8860 let cursor_row = cursor.to_point(&multibuffer).row;
8861
8862 let snapshot = multibuffer
8863 .buffer_for_id(cursor_text_anchor.buffer_id)
8864 .cloned()?;
8865
8866 let mut inlay_ids = Vec::new();
8867 let invalidation_row_range;
8868 let move_invalidation_row_range = if cursor_row < edit_start_row {
8869 Some(cursor_row..edit_end_row)
8870 } else if cursor_row > edit_end_row {
8871 Some(edit_start_row..cursor_row)
8872 } else {
8873 None
8874 };
8875 let supports_jump = self
8876 .edit_prediction_provider
8877 .as_ref()
8878 .map(|provider| provider.provider.supports_jump_to_edit())
8879 .unwrap_or(true);
8880
8881 let is_move = supports_jump
8882 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8883 let completion = if is_move {
8884 if let Some(provider) = &self.edit_prediction_provider {
8885 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8886 }
8887 invalidation_row_range =
8888 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8889 let target = first_edit_start;
8890 EditPrediction::MoveWithin { target, snapshot }
8891 } else {
8892 let show_completions_in_menu = self.has_visible_completions_menu();
8893 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8894 && !self.edit_predictions_hidden_for_vim_mode;
8895
8896 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8897 if provider.show_tab_accept_marker() {
8898 EditDisplayMode::TabAccept
8899 } else {
8900 EditDisplayMode::Inline
8901 }
8902 } else {
8903 EditDisplayMode::DiffPopover
8904 };
8905
8906 let report_shown = match display_mode {
8907 EditDisplayMode::DiffPopover | EditDisplayMode::Inline => {
8908 show_completions_in_buffer || show_completions_in_menu
8909 }
8910 EditDisplayMode::TabAccept => {
8911 show_completions_in_menu || self.edit_prediction_preview_is_active()
8912 }
8913 };
8914
8915 if report_shown && let Some(provider) = &self.edit_prediction_provider {
8916 let suggestion_display_type = match display_mode {
8917 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8918 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8919 SuggestionDisplayType::GhostText
8920 }
8921 };
8922 provider.provider.did_show(suggestion_display_type, cx);
8923 }
8924
8925 if show_completions_in_buffer {
8926 if edits
8927 .iter()
8928 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8929 {
8930 let mut inlays = Vec::new();
8931 for (range, new_text) in &edits {
8932 let inlay = Inlay::edit_prediction(
8933 post_inc(&mut self.next_inlay_id),
8934 range.start,
8935 new_text.as_ref(),
8936 );
8937 inlay_ids.push(inlay.id);
8938 inlays.push(inlay);
8939 }
8940
8941 self.splice_inlays(&[], inlays, cx);
8942 } else {
8943 let background_color = cx.theme().status().deleted_background;
8944 self.highlight_text(
8945 HighlightKey::EditPredictionHighlight,
8946 edits.iter().map(|(range, _)| range.clone()).collect(),
8947 HighlightStyle {
8948 background_color: Some(background_color),
8949 ..Default::default()
8950 },
8951 cx,
8952 );
8953 }
8954 }
8955
8956 invalidation_row_range = edit_start_row..edit_end_row;
8957
8958 EditPrediction::Edit {
8959 edits,
8960 cursor_position,
8961 edit_preview,
8962 display_mode,
8963 snapshot,
8964 }
8965 };
8966
8967 let invalidation_range = multibuffer
8968 .anchor_before(Point::new(invalidation_row_range.start, 0))
8969 ..multibuffer.anchor_after(Point::new(
8970 invalidation_row_range.end,
8971 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8972 ));
8973
8974 self.stale_edit_prediction_in_menu = None;
8975 self.active_edit_prediction = Some(EditPredictionState {
8976 inlay_ids,
8977 completion,
8978 completion_id,
8979 invalidation_range: Some(invalidation_range),
8980 });
8981
8982 cx.notify();
8983
8984 Some(())
8985 }
8986
8987 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8988 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8989 }
8990
8991 /// Get all display points of breakpoints that will be rendered within editor
8992 ///
8993 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8994 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8995 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8996 fn active_breakpoints(
8997 &self,
8998 range: Range<DisplayRow>,
8999 window: &mut Window,
9000 cx: &mut Context<Self>,
9001 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
9002 let mut breakpoint_display_points = HashMap::default();
9003
9004 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
9005 return breakpoint_display_points;
9006 };
9007
9008 let snapshot = self.snapshot(window, cx);
9009
9010 let multi_buffer_snapshot = snapshot.buffer_snapshot();
9011
9012 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
9013 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
9014
9015 for (buffer_snapshot, range, _) in
9016 multi_buffer_snapshot.range_to_buffer_ranges(range.start..range.end)
9017 {
9018 let Some(buffer) = self.buffer().read(cx).buffer(buffer_snapshot.remote_id()) else {
9019 continue;
9020 };
9021 let breakpoints = breakpoint_store.read(cx).breakpoints(
9022 &buffer,
9023 Some(
9024 buffer_snapshot.anchor_before(range.start)
9025 ..buffer_snapshot.anchor_after(range.end),
9026 ),
9027 &buffer_snapshot,
9028 cx,
9029 );
9030 for (breakpoint, state) in breakpoints {
9031 let Some(multi_buffer_anchor) =
9032 multi_buffer_snapshot.anchor_in_excerpt(breakpoint.position)
9033 else {
9034 continue;
9035 };
9036 let position = multi_buffer_anchor
9037 .to_point(&multi_buffer_snapshot)
9038 .to_display_point(&snapshot);
9039
9040 breakpoint_display_points.insert(
9041 position.row(),
9042 (multi_buffer_anchor, breakpoint.bp.clone(), state),
9043 );
9044 }
9045 }
9046
9047 breakpoint_display_points
9048 }
9049
9050 fn breakpoint_context_menu(
9051 &self,
9052 anchor: Anchor,
9053 window: &mut Window,
9054 cx: &mut Context<Self>,
9055 ) -> Entity<ui::ContextMenu> {
9056 let weak_editor = cx.weak_entity();
9057 let focus_handle = self.focus_handle(cx);
9058
9059 let row = self
9060 .buffer
9061 .read(cx)
9062 .snapshot(cx)
9063 .summary_for_anchor::<Point>(&anchor)
9064 .row;
9065
9066 let breakpoint = self
9067 .breakpoint_at_row(row, window, cx)
9068 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
9069
9070 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
9071 "Edit Log Breakpoint"
9072 } else {
9073 "Set Log Breakpoint"
9074 };
9075
9076 let condition_breakpoint_msg = if breakpoint
9077 .as_ref()
9078 .is_some_and(|bp| bp.1.condition.is_some())
9079 {
9080 "Edit Condition Breakpoint"
9081 } else {
9082 "Set Condition Breakpoint"
9083 };
9084
9085 let hit_condition_breakpoint_msg = if breakpoint
9086 .as_ref()
9087 .is_some_and(|bp| bp.1.hit_condition.is_some())
9088 {
9089 "Edit Hit Condition Breakpoint"
9090 } else {
9091 "Set Hit Condition Breakpoint"
9092 };
9093
9094 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
9095 "Unset Breakpoint"
9096 } else {
9097 "Set Breakpoint"
9098 };
9099
9100 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
9101
9102 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
9103 BreakpointState::Enabled => Some("Disable"),
9104 BreakpointState::Disabled => Some("Enable"),
9105 });
9106
9107 let (anchor, breakpoint) =
9108 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
9109
9110 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
9111 menu.on_blur_subscription(Subscription::new(|| {}))
9112 .context(focus_handle)
9113 .when(run_to_cursor, |this| {
9114 let weak_editor = weak_editor.clone();
9115 this.entry("Run to Cursor", None, move |window, cx| {
9116 weak_editor
9117 .update(cx, |editor, cx| {
9118 editor.change_selections(
9119 SelectionEffects::no_scroll(),
9120 window,
9121 cx,
9122 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
9123 );
9124 })
9125 .ok();
9126
9127 window.dispatch_action(Box::new(RunToCursor), cx);
9128 })
9129 .separator()
9130 })
9131 .when_some(toggle_state_msg, |this, msg| {
9132 this.entry(msg, None, {
9133 let weak_editor = weak_editor.clone();
9134 let breakpoint = breakpoint.clone();
9135 move |_window, cx| {
9136 weak_editor
9137 .update(cx, |this, cx| {
9138 this.edit_breakpoint_at_anchor(
9139 anchor,
9140 breakpoint.as_ref().clone(),
9141 BreakpointEditAction::InvertState,
9142 cx,
9143 );
9144 })
9145 .log_err();
9146 }
9147 })
9148 })
9149 .entry(set_breakpoint_msg, None, {
9150 let weak_editor = weak_editor.clone();
9151 let breakpoint = breakpoint.clone();
9152 move |_window, cx| {
9153 weak_editor
9154 .update(cx, |this, cx| {
9155 this.edit_breakpoint_at_anchor(
9156 anchor,
9157 breakpoint.as_ref().clone(),
9158 BreakpointEditAction::Toggle,
9159 cx,
9160 );
9161 })
9162 .log_err();
9163 }
9164 })
9165 .entry(log_breakpoint_msg, None, {
9166 let breakpoint = breakpoint.clone();
9167 let weak_editor = weak_editor.clone();
9168 move |window, cx| {
9169 weak_editor
9170 .update(cx, |this, cx| {
9171 this.add_edit_breakpoint_block(
9172 anchor,
9173 breakpoint.as_ref(),
9174 BreakpointPromptEditAction::Log,
9175 window,
9176 cx,
9177 );
9178 })
9179 .log_err();
9180 }
9181 })
9182 .entry(condition_breakpoint_msg, None, {
9183 let breakpoint = breakpoint.clone();
9184 let weak_editor = weak_editor.clone();
9185 move |window, cx| {
9186 weak_editor
9187 .update(cx, |this, cx| {
9188 this.add_edit_breakpoint_block(
9189 anchor,
9190 breakpoint.as_ref(),
9191 BreakpointPromptEditAction::Condition,
9192 window,
9193 cx,
9194 );
9195 })
9196 .log_err();
9197 }
9198 })
9199 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
9200 weak_editor
9201 .update(cx, |this, cx| {
9202 this.add_edit_breakpoint_block(
9203 anchor,
9204 breakpoint.as_ref(),
9205 BreakpointPromptEditAction::HitCondition,
9206 window,
9207 cx,
9208 );
9209 })
9210 .log_err();
9211 })
9212 })
9213 }
9214
9215 fn render_breakpoint(
9216 &self,
9217 position: Anchor,
9218 row: DisplayRow,
9219 breakpoint: &Breakpoint,
9220 state: Option<BreakpointSessionState>,
9221 cx: &mut Context<Self>,
9222 ) -> IconButton {
9223 let is_rejected = state.is_some_and(|s| !s.verified);
9224 // Is it a breakpoint that shows up when hovering over gutter?
9225 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9226 (false, false),
9227 |PhantomBreakpointIndicator {
9228 is_active,
9229 display_row,
9230 collides_with_existing_breakpoint,
9231 }| {
9232 (
9233 is_active && display_row == row,
9234 collides_with_existing_breakpoint,
9235 )
9236 },
9237 );
9238
9239 let (color, icon) = {
9240 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9241 (false, false) => ui::IconName::DebugBreakpoint,
9242 (true, false) => ui::IconName::DebugLogBreakpoint,
9243 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9244 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9245 };
9246
9247 let theme_colors = cx.theme().colors();
9248
9249 let color = if is_phantom {
9250 if collides_with_existing {
9251 Color::Custom(
9252 theme_colors
9253 .debugger_accent
9254 .blend(theme_colors.text.opacity(0.6)),
9255 )
9256 } else {
9257 Color::Hint
9258 }
9259 } else if is_rejected {
9260 Color::Disabled
9261 } else {
9262 Color::Debugger
9263 };
9264
9265 (color, icon)
9266 };
9267
9268 let breakpoint = Arc::from(breakpoint.clone());
9269
9270 let alt_as_text = gpui::Keystroke {
9271 modifiers: Modifiers::secondary_key(),
9272 ..Default::default()
9273 };
9274 let primary_action_text = if breakpoint.is_disabled() {
9275 "Enable breakpoint"
9276 } else if is_phantom && !collides_with_existing {
9277 "Set breakpoint"
9278 } else {
9279 "Unset breakpoint"
9280 };
9281 let focus_handle = self.focus_handle.clone();
9282
9283 let meta = if is_rejected {
9284 SharedString::from("No executable code is associated with this line.")
9285 } else if collides_with_existing && !breakpoint.is_disabled() {
9286 SharedString::from(format!(
9287 "{alt_as_text}-click to disable,\nright-click for more options."
9288 ))
9289 } else {
9290 SharedString::from("Right-click for more options.")
9291 };
9292 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9293 .icon_size(IconSize::XSmall)
9294 .size(ui::ButtonSize::None)
9295 .when(is_rejected, |this| {
9296 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9297 })
9298 .icon_color(color)
9299 .style(ButtonStyle::Transparent)
9300 .on_click(cx.listener({
9301 move |editor, event: &ClickEvent, window, cx| {
9302 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9303 BreakpointEditAction::InvertState
9304 } else {
9305 BreakpointEditAction::Toggle
9306 };
9307
9308 window.focus(&editor.focus_handle(cx), cx);
9309 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9310 editor.edit_breakpoint_at_anchor(
9311 position,
9312 breakpoint.as_ref().clone(),
9313 edit_action,
9314 cx,
9315 );
9316 }
9317 }))
9318 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9319 editor.set_breakpoint_context_menu(
9320 row,
9321 Some(position),
9322 event.position(),
9323 window,
9324 cx,
9325 );
9326 }))
9327 .tooltip(move |_window, cx| {
9328 Tooltip::with_meta_in(
9329 primary_action_text,
9330 Some(&ToggleBreakpoint),
9331 meta.clone(),
9332 &focus_handle,
9333 cx,
9334 )
9335 })
9336 }
9337
9338 fn build_tasks_context(
9339 project: &Entity<Project>,
9340 buffer: &Entity<Buffer>,
9341 buffer_row: u32,
9342 tasks: &Arc<RunnableTasks>,
9343 cx: &mut Context<Self>,
9344 ) -> Task<Option<task::TaskContext>> {
9345 let position = Point::new(buffer_row, tasks.column);
9346 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9347 let location = Location {
9348 buffer: buffer.clone(),
9349 range: range_start..range_start,
9350 };
9351 // Fill in the environmental variables from the tree-sitter captures
9352 let mut captured_task_variables = TaskVariables::default();
9353 for (capture_name, value) in tasks.extra_variables.clone() {
9354 captured_task_variables.insert(
9355 task::VariableName::Custom(capture_name.into()),
9356 value.clone(),
9357 );
9358 }
9359 project.update(cx, |project, cx| {
9360 project.task_store().update(cx, |task_store, cx| {
9361 task_store.task_context_for_location(captured_task_variables, location, cx)
9362 })
9363 })
9364 }
9365
9366 pub fn context_menu_visible(&self) -> bool {
9367 !self.edit_prediction_preview_is_active()
9368 && self
9369 .context_menu
9370 .borrow()
9371 .as_ref()
9372 .is_some_and(|menu| menu.visible())
9373 }
9374
9375 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9376 self.context_menu
9377 .borrow()
9378 .as_ref()
9379 .map(|menu| menu.origin())
9380 }
9381
9382 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9383 self.context_menu_options = Some(options);
9384 }
9385
9386 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9387 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9388
9389 fn render_edit_prediction_popover(
9390 &mut self,
9391 text_bounds: &Bounds<Pixels>,
9392 content_origin: gpui::Point<Pixels>,
9393 right_margin: Pixels,
9394 editor_snapshot: &EditorSnapshot,
9395 visible_row_range: Range<DisplayRow>,
9396 scroll_top: ScrollOffset,
9397 scroll_bottom: ScrollOffset,
9398 line_layouts: &[LineWithInvisibles],
9399 line_height: Pixels,
9400 scroll_position: gpui::Point<ScrollOffset>,
9401 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9402 newest_selection_head: Option<DisplayPoint>,
9403 editor_width: Pixels,
9404 style: &EditorStyle,
9405 window: &mut Window,
9406 cx: &mut App,
9407 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9408 if self.mode().is_minimap() {
9409 return None;
9410 }
9411 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9412
9413 if self.edit_prediction_visible_in_cursor_popover(true) {
9414 return None;
9415 }
9416
9417 match &active_edit_prediction.completion {
9418 EditPrediction::MoveWithin { target, .. } => {
9419 let target_display_point = target.to_display_point(editor_snapshot);
9420
9421 if self.edit_prediction_requires_modifier() {
9422 if !self.edit_prediction_preview_is_active() {
9423 return None;
9424 }
9425
9426 self.render_edit_prediction_modifier_jump_popover(
9427 text_bounds,
9428 content_origin,
9429 visible_row_range,
9430 line_layouts,
9431 line_height,
9432 scroll_pixel_position,
9433 newest_selection_head,
9434 target_display_point,
9435 window,
9436 cx,
9437 )
9438 } else {
9439 self.render_edit_prediction_eager_jump_popover(
9440 text_bounds,
9441 content_origin,
9442 editor_snapshot,
9443 visible_row_range,
9444 scroll_top,
9445 scroll_bottom,
9446 line_height,
9447 scroll_pixel_position,
9448 target_display_point,
9449 editor_width,
9450 window,
9451 cx,
9452 )
9453 }
9454 }
9455 EditPrediction::Edit {
9456 display_mode: EditDisplayMode::Inline,
9457 ..
9458 } => None,
9459 EditPrediction::Edit {
9460 display_mode: EditDisplayMode::TabAccept,
9461 edits,
9462 ..
9463 } => {
9464 let range = &edits.first()?.0;
9465 let target_display_point = range.end.to_display_point(editor_snapshot);
9466
9467 self.render_edit_prediction_end_of_line_popover(
9468 "Accept",
9469 editor_snapshot,
9470 visible_row_range,
9471 target_display_point,
9472 line_height,
9473 scroll_pixel_position,
9474 content_origin,
9475 editor_width,
9476 window,
9477 cx,
9478 )
9479 }
9480 EditPrediction::Edit {
9481 edits,
9482 edit_preview,
9483 display_mode: EditDisplayMode::DiffPopover,
9484 snapshot,
9485 ..
9486 } => self.render_edit_prediction_diff_popover(
9487 text_bounds,
9488 content_origin,
9489 right_margin,
9490 editor_snapshot,
9491 visible_row_range,
9492 line_layouts,
9493 line_height,
9494 scroll_position,
9495 scroll_pixel_position,
9496 newest_selection_head,
9497 editor_width,
9498 style,
9499 edits,
9500 edit_preview,
9501 snapshot,
9502 window,
9503 cx,
9504 ),
9505 EditPrediction::MoveOutside { snapshot, .. } => {
9506 let mut element = self
9507 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9508 .into_any();
9509
9510 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9511 let origin_x = text_bounds.size.width - size.width - px(30.);
9512 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9513 element.prepaint_at(origin, window, cx);
9514
9515 Some((element, origin))
9516 }
9517 }
9518 }
9519
9520 fn render_edit_prediction_modifier_jump_popover(
9521 &mut self,
9522 text_bounds: &Bounds<Pixels>,
9523 content_origin: gpui::Point<Pixels>,
9524 visible_row_range: Range<DisplayRow>,
9525 line_layouts: &[LineWithInvisibles],
9526 line_height: Pixels,
9527 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9528 newest_selection_head: Option<DisplayPoint>,
9529 target_display_point: DisplayPoint,
9530 window: &mut Window,
9531 cx: &mut App,
9532 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9533 let scrolled_content_origin =
9534 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9535
9536 const SCROLL_PADDING_Y: Pixels = px(12.);
9537
9538 if target_display_point.row() < visible_row_range.start {
9539 return self.render_edit_prediction_scroll_popover(
9540 &|_| SCROLL_PADDING_Y,
9541 IconName::ArrowUp,
9542 visible_row_range,
9543 line_layouts,
9544 newest_selection_head,
9545 scrolled_content_origin,
9546 window,
9547 cx,
9548 );
9549 } else if target_display_point.row() >= visible_row_range.end {
9550 return self.render_edit_prediction_scroll_popover(
9551 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9552 IconName::ArrowDown,
9553 visible_row_range,
9554 line_layouts,
9555 newest_selection_head,
9556 scrolled_content_origin,
9557 window,
9558 cx,
9559 );
9560 }
9561
9562 const POLE_WIDTH: Pixels = px(2.);
9563
9564 let line_layout =
9565 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9566 let target_column = target_display_point.column() as usize;
9567
9568 let target_x = line_layout.x_for_index(target_column);
9569 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9570 - scroll_pixel_position.y;
9571
9572 let flag_on_right = target_x < text_bounds.size.width / 2.;
9573
9574 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9575 border_color.l += 0.001;
9576
9577 let mut element = v_flex()
9578 .items_end()
9579 .when(flag_on_right, |el| el.items_start())
9580 .child(if flag_on_right {
9581 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9582 .rounded_bl(px(0.))
9583 .rounded_tl(px(0.))
9584 .border_l_2()
9585 .border_color(border_color)
9586 } else {
9587 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9588 .rounded_br(px(0.))
9589 .rounded_tr(px(0.))
9590 .border_r_2()
9591 .border_color(border_color)
9592 })
9593 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9594 .into_any();
9595
9596 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9597
9598 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9599 - point(
9600 if flag_on_right {
9601 POLE_WIDTH
9602 } else {
9603 size.width - POLE_WIDTH
9604 },
9605 size.height - line_height,
9606 );
9607
9608 origin.x = origin.x.max(content_origin.x);
9609
9610 element.prepaint_at(origin, window, cx);
9611
9612 Some((element, origin))
9613 }
9614
9615 fn render_edit_prediction_scroll_popover(
9616 &mut self,
9617 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9618 scroll_icon: IconName,
9619 visible_row_range: Range<DisplayRow>,
9620 line_layouts: &[LineWithInvisibles],
9621 newest_selection_head: Option<DisplayPoint>,
9622 scrolled_content_origin: gpui::Point<Pixels>,
9623 window: &mut Window,
9624 cx: &mut App,
9625 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9626 let mut element = self
9627 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9628 .into_any();
9629
9630 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9631
9632 let cursor = newest_selection_head?;
9633 let cursor_row_layout =
9634 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9635 let cursor_column = cursor.column() as usize;
9636
9637 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9638
9639 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9640
9641 element.prepaint_at(origin, window, cx);
9642 Some((element, origin))
9643 }
9644
9645 fn render_edit_prediction_eager_jump_popover(
9646 &mut self,
9647 text_bounds: &Bounds<Pixels>,
9648 content_origin: gpui::Point<Pixels>,
9649 editor_snapshot: &EditorSnapshot,
9650 visible_row_range: Range<DisplayRow>,
9651 scroll_top: ScrollOffset,
9652 scroll_bottom: ScrollOffset,
9653 line_height: Pixels,
9654 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9655 target_display_point: DisplayPoint,
9656 editor_width: Pixels,
9657 window: &mut Window,
9658 cx: &mut App,
9659 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9660 if target_display_point.row().as_f64() < scroll_top {
9661 let mut element = self
9662 .render_edit_prediction_line_popover(
9663 "Jump to Edit",
9664 Some(IconName::ArrowUp),
9665 window,
9666 cx,
9667 )
9668 .into_any();
9669
9670 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9671 let offset = point(
9672 (text_bounds.size.width - size.width) / 2.,
9673 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9674 );
9675
9676 let origin = text_bounds.origin + offset;
9677 element.prepaint_at(origin, window, cx);
9678 Some((element, origin))
9679 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9680 let mut element = self
9681 .render_edit_prediction_line_popover(
9682 "Jump to Edit",
9683 Some(IconName::ArrowDown),
9684 window,
9685 cx,
9686 )
9687 .into_any();
9688
9689 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9690 let offset = point(
9691 (text_bounds.size.width - size.width) / 2.,
9692 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9693 );
9694
9695 let origin = text_bounds.origin + offset;
9696 element.prepaint_at(origin, window, cx);
9697 Some((element, origin))
9698 } else {
9699 self.render_edit_prediction_end_of_line_popover(
9700 "Jump to Edit",
9701 editor_snapshot,
9702 visible_row_range,
9703 target_display_point,
9704 line_height,
9705 scroll_pixel_position,
9706 content_origin,
9707 editor_width,
9708 window,
9709 cx,
9710 )
9711 }
9712 }
9713
9714 fn render_edit_prediction_end_of_line_popover(
9715 self: &mut Editor,
9716 label: &'static str,
9717 editor_snapshot: &EditorSnapshot,
9718 visible_row_range: Range<DisplayRow>,
9719 target_display_point: DisplayPoint,
9720 line_height: Pixels,
9721 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9722 content_origin: gpui::Point<Pixels>,
9723 editor_width: Pixels,
9724 window: &mut Window,
9725 cx: &mut App,
9726 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9727 let target_line_end = DisplayPoint::new(
9728 target_display_point.row(),
9729 editor_snapshot.line_len(target_display_point.row()),
9730 );
9731
9732 let mut element = self
9733 .render_edit_prediction_line_popover(label, None, window, cx)
9734 .into_any();
9735
9736 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9737
9738 let line_origin =
9739 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9740
9741 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9742 let mut origin = start_point
9743 + line_origin
9744 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9745 origin.x = origin.x.max(content_origin.x);
9746
9747 let max_x = content_origin.x + editor_width - size.width;
9748
9749 if origin.x > max_x {
9750 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9751
9752 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9753 origin.y += offset;
9754 IconName::ArrowUp
9755 } else {
9756 origin.y -= offset;
9757 IconName::ArrowDown
9758 };
9759
9760 element = self
9761 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9762 .into_any();
9763
9764 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9765
9766 origin.x = content_origin.x + editor_width - size.width - px(2.);
9767 }
9768
9769 element.prepaint_at(origin, window, cx);
9770 Some((element, origin))
9771 }
9772
9773 fn render_edit_prediction_diff_popover(
9774 self: &Editor,
9775 text_bounds: &Bounds<Pixels>,
9776 content_origin: gpui::Point<Pixels>,
9777 right_margin: Pixels,
9778 editor_snapshot: &EditorSnapshot,
9779 visible_row_range: Range<DisplayRow>,
9780 line_layouts: &[LineWithInvisibles],
9781 line_height: Pixels,
9782 scroll_position: gpui::Point<ScrollOffset>,
9783 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9784 newest_selection_head: Option<DisplayPoint>,
9785 editor_width: Pixels,
9786 style: &EditorStyle,
9787 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9788 edit_preview: &Option<language::EditPreview>,
9789 snapshot: &language::BufferSnapshot,
9790 window: &mut Window,
9791 cx: &mut App,
9792 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9793 let edit_start = edits
9794 .first()
9795 .unwrap()
9796 .0
9797 .start
9798 .to_display_point(editor_snapshot);
9799 let edit_end = edits
9800 .last()
9801 .unwrap()
9802 .0
9803 .end
9804 .to_display_point(editor_snapshot);
9805
9806 let is_visible = visible_row_range.contains(&edit_start.row())
9807 || visible_row_range.contains(&edit_end.row());
9808 if !is_visible {
9809 return None;
9810 }
9811
9812 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9813 crate::edit_prediction_edit_text(
9814 snapshot,
9815 edits,
9816 edit_preview,
9817 false,
9818 editor_snapshot.buffer_snapshot(),
9819 cx,
9820 )
9821 } else {
9822 // Fallback for providers without edit_preview
9823 crate::edit_prediction_fallback_text(edits, cx)
9824 };
9825
9826 let styled_text = highlighted_edits.to_styled_text(&style.text);
9827 let line_count = highlighted_edits.text.lines().count();
9828
9829 const BORDER_WIDTH: Pixels = px(1.);
9830
9831 let keybind = self.render_edit_prediction_keybind(window, cx);
9832 let has_keybind = keybind.is_some();
9833
9834 let mut element = h_flex()
9835 .items_start()
9836 .child(
9837 h_flex()
9838 .bg(cx.theme().colors().editor_background)
9839 .border(BORDER_WIDTH)
9840 .shadow_xs()
9841 .border_color(cx.theme().colors().border)
9842 .rounded_l_lg()
9843 .when(line_count > 1, |el| el.rounded_br_lg())
9844 .pr_1()
9845 .child(styled_text),
9846 )
9847 .child(
9848 h_flex()
9849 .h(line_height + BORDER_WIDTH * 2.)
9850 .px_1p5()
9851 .gap_1()
9852 // Workaround: For some reason, there's a gap if we don't do this
9853 .ml(-BORDER_WIDTH)
9854 .shadow(vec![gpui::BoxShadow {
9855 color: gpui::black().opacity(0.05),
9856 offset: point(px(1.), px(1.)),
9857 blur_radius: px(2.),
9858 spread_radius: px(0.),
9859 }])
9860 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9861 .border(BORDER_WIDTH)
9862 .border_color(cx.theme().colors().border)
9863 .rounded_r_lg()
9864 .id("edit_prediction_diff_popover_keybind")
9865 .when(!has_keybind, |el| {
9866 let status_colors = cx.theme().status();
9867
9868 el.bg(status_colors.error_background)
9869 .border_color(status_colors.error.opacity(0.6))
9870 .child(Icon::new(IconName::Info).color(Color::Error))
9871 .cursor_default()
9872 .hoverable_tooltip(move |_window, cx| {
9873 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9874 })
9875 })
9876 .children(keybind),
9877 )
9878 .into_any();
9879
9880 let longest_row =
9881 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9882 let longest_line_width = if visible_row_range.contains(&longest_row) {
9883 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9884 } else {
9885 layout_line(
9886 longest_row,
9887 editor_snapshot,
9888 style,
9889 editor_width,
9890 |_| false,
9891 window,
9892 cx,
9893 )
9894 .width
9895 };
9896
9897 let viewport_bounds =
9898 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9899 right: -right_margin,
9900 ..Default::default()
9901 });
9902
9903 let x_after_longest = Pixels::from(
9904 ScrollPixelOffset::from(
9905 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9906 ) - scroll_pixel_position.x,
9907 );
9908
9909 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9910
9911 // Fully visible if it can be displayed within the window (allow overlapping other
9912 // panes). However, this is only allowed if the popover starts within text_bounds.
9913 let can_position_to_the_right = x_after_longest < text_bounds.right()
9914 && x_after_longest + element_bounds.width < viewport_bounds.right();
9915
9916 let mut origin = if can_position_to_the_right {
9917 point(
9918 x_after_longest,
9919 text_bounds.origin.y
9920 + Pixels::from(
9921 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9922 - scroll_pixel_position.y,
9923 ),
9924 )
9925 } else {
9926 let cursor_row = newest_selection_head.map(|head| head.row());
9927 let above_edit = edit_start
9928 .row()
9929 .0
9930 .checked_sub(line_count as u32)
9931 .map(DisplayRow);
9932 let below_edit = Some(edit_end.row() + 1);
9933 let above_cursor =
9934 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9935 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9936
9937 // Place the edit popover adjacent to the edit if there is a location
9938 // available that is onscreen and does not obscure the cursor. Otherwise,
9939 // place it adjacent to the cursor.
9940 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9941 .into_iter()
9942 .flatten()
9943 .find(|&start_row| {
9944 let end_row = start_row + line_count as u32;
9945 visible_row_range.contains(&start_row)
9946 && visible_row_range.contains(&end_row)
9947 && cursor_row
9948 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9949 })?;
9950
9951 content_origin
9952 + point(
9953 Pixels::from(-scroll_pixel_position.x),
9954 Pixels::from(
9955 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9956 ),
9957 )
9958 };
9959
9960 origin.x -= BORDER_WIDTH;
9961
9962 window.with_content_mask(
9963 Some(gpui::ContentMask {
9964 bounds: *text_bounds,
9965 }),
9966 |window| {
9967 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9968 },
9969 );
9970
9971 // Do not return an element, since it will already be drawn due to defer_draw.
9972 None
9973 }
9974
9975 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9976 px(30.)
9977 }
9978
9979 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9980 if self.read_only(cx) {
9981 cx.theme().players().read_only()
9982 } else {
9983 self.style.as_ref().unwrap().local_player
9984 }
9985 }
9986
9987 fn render_edit_prediction_inline_keystroke(
9988 &self,
9989 keystroke: &gpui::KeybindingKeystroke,
9990 modifiers_color: Color,
9991 cx: &App,
9992 ) -> AnyElement {
9993 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9994
9995 h_flex()
9996 .px_0p5()
9997 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9998 .font(
9999 theme_settings::ThemeSettings::get_global(cx)
10000 .buffer_font
10001 .clone(),
10002 )
10003 .text_size(TextSize::XSmall.rems(cx))
10004 .child(h_flex().children(ui::render_modifiers(
10005 keystroke.modifiers(),
10006 PlatformStyle::platform(),
10007 Some(modifiers_color),
10008 Some(IconSize::XSmall.rems().into()),
10009 true,
10010 )))
10011 .when(is_platform_style_mac, |parent| {
10012 parent.child(keystroke.key().to_string())
10013 })
10014 .when(!is_platform_style_mac, |parent| {
10015 parent.child(
10016 Key::new(ui::utils::capitalize(keystroke.key()), Some(Color::Default))
10017 .size(Some(IconSize::XSmall.rems().into())),
10018 )
10019 })
10020 .into_any()
10021 }
10022
10023 fn render_edit_prediction_popover_keystroke(
10024 &self,
10025 keystroke: &gpui::KeybindingKeystroke,
10026 color: Color,
10027 cx: &App,
10028 ) -> AnyElement {
10029 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10030
10031 if keystroke.modifiers().modified() {
10032 h_flex()
10033 .font(
10034 theme_settings::ThemeSettings::get_global(cx)
10035 .buffer_font
10036 .clone(),
10037 )
10038 .when(is_platform_style_mac, |parent| parent.gap_1())
10039 .child(h_flex().children(ui::render_modifiers(
10040 keystroke.modifiers(),
10041 PlatformStyle::platform(),
10042 Some(color),
10043 None,
10044 false,
10045 )))
10046 .into_any()
10047 } else {
10048 Key::new(ui::utils::capitalize(keystroke.key()), Some(color))
10049 .size(Some(IconSize::XSmall.rems().into()))
10050 .into_any_element()
10051 }
10052 }
10053
10054 fn render_edit_prediction_keybind(
10055 &self,
10056 window: &mut Window,
10057 cx: &mut App,
10058 ) -> Option<AnyElement> {
10059 let keybind_display =
10060 self.edit_prediction_keybind_display(EditPredictionKeybindSurface::Inline, window, cx);
10061 let keystroke = keybind_display.displayed_keystroke.as_ref()?;
10062
10063 let modifiers_color = if *keystroke.modifiers() == window.modifiers() {
10064 Color::Accent
10065 } else {
10066 Color::Muted
10067 };
10068
10069 Some(self.render_edit_prediction_inline_keystroke(keystroke, modifiers_color, cx))
10070 }
10071
10072 fn render_edit_prediction_line_popover(
10073 &self,
10074 label: impl Into<SharedString>,
10075 icon: Option<IconName>,
10076 window: &mut Window,
10077 cx: &mut App,
10078 ) -> Stateful<Div> {
10079 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
10080
10081 let keybind = self.render_edit_prediction_keybind(window, cx);
10082 let has_keybind = keybind.is_some();
10083 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10084
10085 h_flex()
10086 .id("ep-line-popover")
10087 .py_0p5()
10088 .pl_1()
10089 .pr(padding_right)
10090 .gap_1()
10091 .rounded_md()
10092 .border_1()
10093 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10094 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10095 .shadow_xs()
10096 .when(!has_keybind, |el| {
10097 let status_colors = cx.theme().status();
10098
10099 el.bg(status_colors.error_background)
10100 .border_color(status_colors.error.opacity(0.6))
10101 .pl_2()
10102 .child(Icon::new(icons.error).color(Color::Error))
10103 .cursor_default()
10104 .hoverable_tooltip(move |_window, cx| {
10105 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10106 })
10107 })
10108 .children(keybind)
10109 .child(
10110 Label::new(label)
10111 .size(LabelSize::Small)
10112 .when(!has_keybind, |el| {
10113 el.color(cx.theme().status().error.into()).strikethrough()
10114 }),
10115 )
10116 .when(!has_keybind, |el| {
10117 el.child(
10118 h_flex().ml_1().child(
10119 Icon::new(IconName::Info)
10120 .size(IconSize::Small)
10121 .color(cx.theme().status().error.into()),
10122 ),
10123 )
10124 })
10125 .when_some(icon, |element, icon| {
10126 element.child(
10127 div()
10128 .mt(px(1.5))
10129 .child(Icon::new(icon).size(IconSize::Small)),
10130 )
10131 })
10132 }
10133
10134 fn render_edit_prediction_jump_outside_popover(
10135 &self,
10136 snapshot: &BufferSnapshot,
10137 window: &mut Window,
10138 cx: &mut App,
10139 ) -> Stateful<Div> {
10140 let keybind = self.render_edit_prediction_keybind(window, cx);
10141 let has_keybind = keybind.is_some();
10142 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10143
10144 let file_name = snapshot
10145 .file()
10146 .map(|file| SharedString::new(file.file_name(cx)))
10147 .unwrap_or(SharedString::new_static("untitled"));
10148
10149 h_flex()
10150 .id("ep-jump-outside-popover")
10151 .py_1()
10152 .px_2()
10153 .gap_1()
10154 .rounded_md()
10155 .border_1()
10156 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10157 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10158 .shadow_xs()
10159 .when(!has_keybind, |el| {
10160 let status_colors = cx.theme().status();
10161
10162 el.bg(status_colors.error_background)
10163 .border_color(status_colors.error.opacity(0.6))
10164 .pl_2()
10165 .child(Icon::new(icons.error).color(Color::Error))
10166 .cursor_default()
10167 .hoverable_tooltip(move |_window, cx| {
10168 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10169 })
10170 })
10171 .children(keybind)
10172 .child(
10173 Label::new(file_name)
10174 .size(LabelSize::Small)
10175 .buffer_font(cx)
10176 .when(!has_keybind, |el| {
10177 el.color(cx.theme().status().error.into()).strikethrough()
10178 }),
10179 )
10180 .when(!has_keybind, |el| {
10181 el.child(
10182 h_flex().ml_1().child(
10183 Icon::new(IconName::Info)
10184 .size(IconSize::Small)
10185 .color(cx.theme().status().error.into()),
10186 ),
10187 )
10188 })
10189 .child(
10190 div()
10191 .mt(px(1.5))
10192 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10193 )
10194 }
10195
10196 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10197 let accent_color = cx.theme().colors().text_accent;
10198 let editor_bg_color = cx.theme().colors().editor_background;
10199 editor_bg_color.blend(accent_color.opacity(0.1))
10200 }
10201
10202 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10203 let accent_color = cx.theme().colors().text_accent;
10204 let editor_bg_color = cx.theme().colors().editor_background;
10205 editor_bg_color.blend(accent_color.opacity(0.6))
10206 }
10207 fn get_prediction_provider_icons(
10208 provider: &Option<RegisteredEditPredictionDelegate>,
10209 cx: &App,
10210 ) -> edit_prediction_types::EditPredictionIconSet {
10211 match provider {
10212 Some(provider) => provider.provider.icons(cx),
10213 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10214 }
10215 }
10216
10217 fn render_edit_prediction_cursor_popover(
10218 &self,
10219 min_width: Pixels,
10220 max_width: Pixels,
10221 cursor_point: Point,
10222 style: &EditorStyle,
10223 window: &mut Window,
10224 cx: &mut Context<Editor>,
10225 ) -> Option<AnyElement> {
10226 let provider = self.edit_prediction_provider.as_ref()?;
10227 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10228
10229 let is_refreshing = provider.provider.is_refreshing(cx);
10230
10231 fn pending_completion_container(icon: IconName) -> Div {
10232 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10233 }
10234
10235 let completion = match &self.active_edit_prediction {
10236 Some(prediction) => {
10237 if !self.has_visible_completions_menu() {
10238 const RADIUS: Pixels = px(6.);
10239 const BORDER_WIDTH: Pixels = px(1.);
10240 let keybind_display = self.edit_prediction_keybind_display(
10241 EditPredictionKeybindSurface::CursorPopoverCompact,
10242 window,
10243 cx,
10244 );
10245
10246 return Some(
10247 h_flex()
10248 .elevation_2(cx)
10249 .border(BORDER_WIDTH)
10250 .border_color(cx.theme().colors().border)
10251 .when(keybind_display.missing_accept_keystroke, |el| {
10252 el.border_color(cx.theme().status().error)
10253 })
10254 .rounded(RADIUS)
10255 .rounded_tl(px(0.))
10256 .overflow_hidden()
10257 .child(div().px_1p5().child(match &prediction.completion {
10258 EditPrediction::MoveWithin { target, snapshot } => {
10259 use text::ToPoint as _;
10260 if target.text_anchor_in(&snapshot).to_point(snapshot).row
10261 > cursor_point.row
10262 {
10263 Icon::new(icons.down)
10264 } else {
10265 Icon::new(icons.up)
10266 }
10267 }
10268 EditPrediction::MoveOutside { .. } => {
10269 // TODO [zeta2] custom icon for external jump?
10270 Icon::new(icons.base)
10271 }
10272 EditPrediction::Edit { .. } => Icon::new(icons.base),
10273 }))
10274 .child(
10275 h_flex()
10276 .gap_1()
10277 .py_1()
10278 .px_2()
10279 .rounded_r(RADIUS - BORDER_WIDTH)
10280 .border_l_1()
10281 .border_color(cx.theme().colors().border)
10282 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10283 .when(keybind_display.show_hold_label, |el| {
10284 el.child(
10285 Label::new("Hold")
10286 .size(LabelSize::Small)
10287 .when(
10288 keybind_display.missing_accept_keystroke,
10289 |el| el.strikethrough(),
10290 )
10291 .line_height_style(LineHeightStyle::UiLabel),
10292 )
10293 })
10294 .id("edit_prediction_cursor_popover_keybind")
10295 .when(keybind_display.missing_accept_keystroke, |el| {
10296 let status_colors = cx.theme().status();
10297
10298 el.bg(status_colors.error_background)
10299 .border_color(status_colors.error.opacity(0.6))
10300 .child(Icon::new(IconName::Info).color(Color::Error))
10301 .cursor_default()
10302 .hoverable_tooltip(move |_window, cx| {
10303 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10304 .into()
10305 })
10306 })
10307 .when_some(
10308 keybind_display.displayed_keystroke.as_ref(),
10309 |el, compact_keystroke| {
10310 el.child(self.render_edit_prediction_popover_keystroke(
10311 compact_keystroke,
10312 Color::Default,
10313 cx,
10314 ))
10315 },
10316 ),
10317 )
10318 .into_any(),
10319 );
10320 }
10321
10322 self.render_edit_prediction_cursor_popover_preview(
10323 prediction,
10324 cursor_point,
10325 style,
10326 cx,
10327 )?
10328 }
10329
10330 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10331 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10332 stale_completion,
10333 cursor_point,
10334 style,
10335 cx,
10336 )?,
10337
10338 None => pending_completion_container(icons.base)
10339 .child(Label::new("...").size(LabelSize::Small)),
10340 },
10341
10342 None => pending_completion_container(icons.base)
10343 .child(Label::new("...").size(LabelSize::Small)),
10344 };
10345
10346 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10347 completion
10348 .with_animation(
10349 "loading-completion",
10350 Animation::new(Duration::from_secs(2))
10351 .repeat()
10352 .with_easing(pulsating_between(0.4, 0.8)),
10353 |label, delta| label.opacity(delta),
10354 )
10355 .into_any_element()
10356 } else {
10357 completion.into_any_element()
10358 };
10359
10360 let has_completion = self.active_edit_prediction.is_some();
10361 let keybind_display = self.edit_prediction_keybind_display(
10362 EditPredictionKeybindSurface::CursorPopoverExpanded,
10363 window,
10364 cx,
10365 );
10366
10367 Some(
10368 h_flex()
10369 .min_w(min_width)
10370 .max_w(max_width)
10371 .flex_1()
10372 .elevation_2(cx)
10373 .border_color(cx.theme().colors().border)
10374 .child(
10375 div()
10376 .flex_1()
10377 .py_1()
10378 .px_2()
10379 .overflow_hidden()
10380 .child(completion),
10381 )
10382 .when_some(
10383 keybind_display.displayed_keystroke.as_ref(),
10384 |el, keystroke| {
10385 let key_color = if !has_completion {
10386 Color::Muted
10387 } else {
10388 Color::Default
10389 };
10390
10391 if keybind_display.action == EditPredictionKeybindAction::Preview {
10392 el.child(
10393 h_flex()
10394 .h_full()
10395 .border_l_1()
10396 .rounded_r_lg()
10397 .border_color(cx.theme().colors().border)
10398 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10399 .gap_1()
10400 .py_1()
10401 .px_2()
10402 .child(self.render_edit_prediction_popover_keystroke(
10403 keystroke, key_color, cx,
10404 ))
10405 .child(Label::new("Preview").into_any_element())
10406 .opacity(if has_completion { 1.0 } else { 0.4 }),
10407 )
10408 } else {
10409 el.child(
10410 h_flex()
10411 .h_full()
10412 .border_l_1()
10413 .rounded_r_lg()
10414 .border_color(cx.theme().colors().border)
10415 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10416 .gap_1()
10417 .py_1()
10418 .px_2()
10419 .child(self.render_edit_prediction_popover_keystroke(
10420 keystroke, key_color, cx,
10421 ))
10422 .opacity(if has_completion { 1.0 } else { 0.4 }),
10423 )
10424 }
10425 },
10426 )
10427 .into_any(),
10428 )
10429 }
10430
10431 fn render_edit_prediction_cursor_popover_preview(
10432 &self,
10433 completion: &EditPredictionState,
10434 cursor_point: Point,
10435 style: &EditorStyle,
10436 cx: &mut Context<Editor>,
10437 ) -> Option<Div> {
10438 use text::ToPoint as _;
10439
10440 fn render_relative_row_jump(
10441 prefix: impl Into<String>,
10442 current_row: u32,
10443 target_row: u32,
10444 ) -> Div {
10445 let (row_diff, arrow) = if target_row < current_row {
10446 (current_row - target_row, IconName::ArrowUp)
10447 } else {
10448 (target_row - current_row, IconName::ArrowDown)
10449 };
10450
10451 h_flex()
10452 .child(
10453 Label::new(format!("{}{}", prefix.into(), row_diff))
10454 .color(Color::Muted)
10455 .size(LabelSize::Small),
10456 )
10457 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10458 }
10459
10460 let supports_jump = self
10461 .edit_prediction_provider
10462 .as_ref()
10463 .map(|provider| provider.provider.supports_jump_to_edit())
10464 .unwrap_or(true);
10465
10466 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10467
10468 match &completion.completion {
10469 EditPrediction::MoveWithin {
10470 target, snapshot, ..
10471 } => {
10472 if !supports_jump {
10473 return None;
10474 }
10475 let (target, _) = self.display_snapshot(cx).anchor_to_buffer_anchor(*target)?;
10476
10477 Some(
10478 h_flex()
10479 .px_2()
10480 .gap_2()
10481 .flex_1()
10482 .child(if target.to_point(snapshot).row > cursor_point.row {
10483 Icon::new(icons.down)
10484 } else {
10485 Icon::new(icons.up)
10486 })
10487 .child(Label::new("Jump to Edit")),
10488 )
10489 }
10490 EditPrediction::MoveOutside { snapshot, .. } => {
10491 let file_name = snapshot
10492 .file()
10493 .map(|file| file.file_name(cx))
10494 .unwrap_or("untitled");
10495 Some(
10496 h_flex()
10497 .px_2()
10498 .gap_2()
10499 .flex_1()
10500 .child(Icon::new(icons.base))
10501 .child(Label::new(format!("Jump to {file_name}"))),
10502 )
10503 }
10504 EditPrediction::Edit {
10505 edits,
10506 edit_preview,
10507 snapshot,
10508 ..
10509 } => {
10510 let first_edit_row = self
10511 .display_snapshot(cx)
10512 .anchor_to_buffer_anchor(edits.first()?.0.start)?
10513 .0
10514 .to_point(snapshot)
10515 .row;
10516
10517 let (highlighted_edits, has_more_lines) =
10518 if let Some(edit_preview) = edit_preview.as_ref() {
10519 crate::edit_prediction_edit_text(
10520 snapshot,
10521 edits,
10522 edit_preview,
10523 true,
10524 &self.display_snapshot(cx),
10525 cx,
10526 )
10527 .first_line_preview()
10528 } else {
10529 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10530 };
10531
10532 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10533 .with_default_highlights(&style.text, highlighted_edits.highlights);
10534
10535 let preview = h_flex()
10536 .gap_1()
10537 .min_w_16()
10538 .child(styled_text)
10539 .when(has_more_lines, |parent| parent.child("…"));
10540
10541 let left = if supports_jump && first_edit_row != cursor_point.row {
10542 render_relative_row_jump("", cursor_point.row, first_edit_row)
10543 .into_any_element()
10544 } else {
10545 Icon::new(icons.base).into_any_element()
10546 };
10547
10548 Some(
10549 h_flex()
10550 .h_full()
10551 .flex_1()
10552 .gap_2()
10553 .pr_1()
10554 .overflow_x_hidden()
10555 .font(
10556 theme_settings::ThemeSettings::get_global(cx)
10557 .buffer_font
10558 .clone(),
10559 )
10560 .child(left)
10561 .child(preview),
10562 )
10563 }
10564 }
10565 }
10566
10567 pub fn render_context_menu(
10568 &mut self,
10569 max_height_in_lines: u32,
10570 window: &mut Window,
10571 cx: &mut Context<Editor>,
10572 ) -> Option<AnyElement> {
10573 let menu = self.context_menu.borrow();
10574 let menu = menu.as_ref()?;
10575 if !menu.visible() {
10576 return None;
10577 };
10578 self.style
10579 .as_ref()
10580 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10581 }
10582
10583 fn render_context_menu_aside(
10584 &mut self,
10585 max_size: Size<Pixels>,
10586 window: &mut Window,
10587 cx: &mut Context<Editor>,
10588 ) -> Option<AnyElement> {
10589 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10590 if menu.visible() {
10591 menu.render_aside(max_size, window, cx)
10592 } else {
10593 None
10594 }
10595 })
10596 }
10597
10598 fn hide_context_menu(
10599 &mut self,
10600 window: &mut Window,
10601 cx: &mut Context<Self>,
10602 ) -> Option<CodeContextMenu> {
10603 cx.notify();
10604 self.completion_tasks.clear();
10605 let context_menu = self.context_menu.borrow_mut().take();
10606 self.stale_edit_prediction_in_menu.take();
10607 self.update_visible_edit_prediction(window, cx);
10608 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10609 && let Some(completion_provider) = &self.completion_provider
10610 {
10611 completion_provider.selection_changed(None, window, cx);
10612 }
10613 context_menu
10614 }
10615
10616 fn show_snippet_choices(
10617 &mut self,
10618 choices: &Vec<String>,
10619 selection: Range<Anchor>,
10620 cx: &mut Context<Self>,
10621 ) {
10622 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
10623 let Some((buffer_snapshot, range)) =
10624 buffer_snapshot.anchor_range_to_buffer_anchor_range(selection.clone())
10625 else {
10626 return;
10627 };
10628 let Some(buffer) = self.buffer.read(cx).buffer(buffer_snapshot.remote_id()) else {
10629 return;
10630 };
10631
10632 let id = post_inc(&mut self.next_completion_id);
10633 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10634 let mut context_menu = self.context_menu.borrow_mut();
10635 let old_menu = context_menu.take();
10636 *context_menu = Some(CodeContextMenu::Completions(
10637 CompletionsMenu::new_snippet_choices(
10638 id,
10639 true,
10640 choices,
10641 selection.start,
10642 range,
10643 buffer,
10644 old_menu.map(|menu| menu.primary_scroll_handle()),
10645 snippet_sort_order,
10646 ),
10647 ));
10648 }
10649
10650 pub fn insert_snippet(
10651 &mut self,
10652 insertion_ranges: &[Range<MultiBufferOffset>],
10653 snippet: Snippet,
10654 window: &mut Window,
10655 cx: &mut Context<Self>,
10656 ) -> Result<()> {
10657 struct Tabstop<T> {
10658 is_end_tabstop: bool,
10659 ranges: Vec<Range<T>>,
10660 choices: Option<Vec<String>>,
10661 }
10662
10663 let tabstops = self.buffer.update(cx, |buffer, cx| {
10664 let snippet_text: Arc<str> = snippet.text.clone().into();
10665 let edits = insertion_ranges
10666 .iter()
10667 .cloned()
10668 .map(|range| (range, snippet_text.clone()));
10669 let autoindent_mode = AutoindentMode::Block {
10670 original_indent_columns: Vec::new(),
10671 };
10672 buffer.edit(edits, Some(autoindent_mode), cx);
10673
10674 let snapshot = &*buffer.read(cx);
10675 let snippet = &snippet;
10676 snippet
10677 .tabstops
10678 .iter()
10679 .map(|tabstop| {
10680 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10681 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10682 });
10683 let mut tabstop_ranges = tabstop
10684 .ranges
10685 .iter()
10686 .flat_map(|tabstop_range| {
10687 let mut delta = 0_isize;
10688 insertion_ranges.iter().map(move |insertion_range| {
10689 let insertion_start = insertion_range.start + delta;
10690 delta += snippet.text.len() as isize
10691 - (insertion_range.end - insertion_range.start) as isize;
10692
10693 let start =
10694 (insertion_start + tabstop_range.start).min(snapshot.len());
10695 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10696 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10697 })
10698 })
10699 .collect::<Vec<_>>();
10700 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10701
10702 Tabstop {
10703 is_end_tabstop,
10704 ranges: tabstop_ranges,
10705 choices: tabstop.choices.clone(),
10706 }
10707 })
10708 .collect::<Vec<_>>()
10709 });
10710 if let Some(tabstop) = tabstops.first() {
10711 self.change_selections(Default::default(), window, cx, |s| {
10712 // Reverse order so that the first range is the newest created selection.
10713 // Completions will use it and autoscroll will prioritize it.
10714 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10715 });
10716
10717 if let Some(choices) = &tabstop.choices
10718 && let Some(selection) = tabstop.ranges.first()
10719 {
10720 self.show_snippet_choices(choices, selection.clone(), cx)
10721 }
10722
10723 // If we're already at the last tabstop and it's at the end of the snippet,
10724 // we're done, we don't need to keep the state around.
10725 if !tabstop.is_end_tabstop {
10726 let choices = tabstops
10727 .iter()
10728 .map(|tabstop| tabstop.choices.clone())
10729 .collect();
10730
10731 let ranges = tabstops
10732 .into_iter()
10733 .map(|tabstop| tabstop.ranges)
10734 .collect::<Vec<_>>();
10735
10736 self.snippet_stack.push(SnippetState {
10737 active_index: 0,
10738 ranges,
10739 choices,
10740 });
10741 }
10742
10743 // Check whether the just-entered snippet ends with an auto-closable bracket.
10744 if self.autoclose_regions.is_empty() {
10745 let snapshot = self.buffer.read(cx).snapshot(cx);
10746 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10747 let selection_head = selection.head();
10748 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10749 continue;
10750 };
10751
10752 let mut bracket_pair = None;
10753 let max_lookup_length = scope
10754 .brackets()
10755 .map(|(pair, _)| {
10756 pair.start
10757 .as_str()
10758 .chars()
10759 .count()
10760 .max(pair.end.as_str().chars().count())
10761 })
10762 .max();
10763 if let Some(max_lookup_length) = max_lookup_length {
10764 let next_text = snapshot
10765 .chars_at(selection_head)
10766 .take(max_lookup_length)
10767 .collect::<String>();
10768 let prev_text = snapshot
10769 .reversed_chars_at(selection_head)
10770 .take(max_lookup_length)
10771 .collect::<String>();
10772
10773 for (pair, enabled) in scope.brackets() {
10774 if enabled
10775 && pair.close
10776 && prev_text.starts_with(pair.start.as_str())
10777 && next_text.starts_with(pair.end.as_str())
10778 {
10779 bracket_pair = Some(pair.clone());
10780 break;
10781 }
10782 }
10783 }
10784
10785 if let Some(pair) = bracket_pair {
10786 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10787 let autoclose_enabled =
10788 self.use_autoclose && snapshot_settings.use_autoclose;
10789 if autoclose_enabled {
10790 let start = snapshot.anchor_after(selection_head);
10791 let end = snapshot.anchor_after(selection_head);
10792 self.autoclose_regions.push(AutocloseRegion {
10793 selection_id: selection.id,
10794 range: start..end,
10795 pair,
10796 });
10797 }
10798 }
10799 }
10800 }
10801 }
10802 Ok(())
10803 }
10804
10805 pub fn move_to_next_snippet_tabstop(
10806 &mut self,
10807 window: &mut Window,
10808 cx: &mut Context<Self>,
10809 ) -> bool {
10810 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10811 }
10812
10813 pub fn move_to_prev_snippet_tabstop(
10814 &mut self,
10815 window: &mut Window,
10816 cx: &mut Context<Self>,
10817 ) -> bool {
10818 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10819 }
10820
10821 pub fn move_to_snippet_tabstop(
10822 &mut self,
10823 bias: Bias,
10824 window: &mut Window,
10825 cx: &mut Context<Self>,
10826 ) -> bool {
10827 if let Some(mut snippet) = self.snippet_stack.pop() {
10828 match bias {
10829 Bias::Left => {
10830 if snippet.active_index > 0 {
10831 snippet.active_index -= 1;
10832 } else {
10833 self.snippet_stack.push(snippet);
10834 return false;
10835 }
10836 }
10837 Bias::Right => {
10838 if snippet.active_index + 1 < snippet.ranges.len() {
10839 snippet.active_index += 1;
10840 } else {
10841 self.snippet_stack.push(snippet);
10842 return false;
10843 }
10844 }
10845 }
10846 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10847 self.change_selections(Default::default(), window, cx, |s| {
10848 // Reverse order so that the first range is the newest created selection.
10849 // Completions will use it and autoscroll will prioritize it.
10850 s.select_ranges(current_ranges.iter().rev().cloned())
10851 });
10852
10853 if let Some(choices) = &snippet.choices[snippet.active_index]
10854 && let Some(selection) = current_ranges.first()
10855 {
10856 self.show_snippet_choices(choices, selection.clone(), cx);
10857 }
10858
10859 // If snippet state is not at the last tabstop, push it back on the stack
10860 if snippet.active_index + 1 < snippet.ranges.len() {
10861 self.snippet_stack.push(snippet);
10862 }
10863 return true;
10864 }
10865 }
10866
10867 false
10868 }
10869
10870 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10871 self.transact(window, cx, |this, window, cx| {
10872 this.select_all(&SelectAll, window, cx);
10873 this.insert("", window, cx);
10874 });
10875 }
10876
10877 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10878 if self.read_only(cx) {
10879 return;
10880 }
10881 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10882 self.transact(window, cx, |this, window, cx| {
10883 this.select_autoclose_pair(window, cx);
10884
10885 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10886
10887 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10888 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10889 for selection in &mut selections {
10890 if selection.is_empty() {
10891 let old_head = selection.head();
10892 let mut new_head =
10893 movement::left(&display_map, old_head.to_display_point(&display_map))
10894 .to_point(&display_map);
10895 if let Some((buffer, line_buffer_range)) = display_map
10896 .buffer_snapshot()
10897 .buffer_line_for_row(MultiBufferRow(old_head.row))
10898 {
10899 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10900 let indent_len = match indent_size.kind {
10901 IndentKind::Space => {
10902 buffer.settings_at(line_buffer_range.start, cx).tab_size
10903 }
10904 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10905 };
10906 if old_head.column <= indent_size.len && old_head.column > 0 {
10907 let indent_len = indent_len.get();
10908 new_head = cmp::min(
10909 new_head,
10910 MultiBufferPoint::new(
10911 old_head.row,
10912 ((old_head.column - 1) / indent_len) * indent_len,
10913 ),
10914 );
10915 }
10916 }
10917
10918 selection.set_head(new_head, SelectionGoal::None);
10919 }
10920 }
10921
10922 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10923 this.insert("", window, cx);
10924 linked_edits.apply_with_left_expansion(cx);
10925 this.refresh_edit_prediction(true, false, window, cx);
10926 refresh_linked_ranges(this, window, cx);
10927 });
10928 }
10929
10930 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10931 if self.read_only(cx) {
10932 return;
10933 }
10934 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10935 self.transact(window, cx, |this, window, cx| {
10936 this.change_selections(Default::default(), window, cx, |s| {
10937 s.move_with(&mut |map, selection| {
10938 if selection.is_empty() {
10939 let cursor = movement::right(map, selection.head());
10940 selection.end = cursor;
10941 selection.reversed = true;
10942 selection.goal = SelectionGoal::None;
10943 }
10944 })
10945 });
10946 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10947 this.insert("", window, cx);
10948 linked_edits.apply(cx);
10949 this.refresh_edit_prediction(true, false, window, cx);
10950 refresh_linked_ranges(this, window, cx);
10951 });
10952 }
10953
10954 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10955 if self.mode.is_single_line() {
10956 cx.propagate();
10957 return;
10958 }
10959
10960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10961 if self.move_to_prev_snippet_tabstop(window, cx) {
10962 return;
10963 }
10964 self.outdent(&Outdent, window, cx);
10965 }
10966
10967 pub fn next_snippet_tabstop(
10968 &mut self,
10969 _: &NextSnippetTabstop,
10970 window: &mut Window,
10971 cx: &mut Context<Self>,
10972 ) {
10973 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10974 cx.propagate();
10975 return;
10976 }
10977
10978 if self.move_to_next_snippet_tabstop(window, cx) {
10979 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10980 return;
10981 }
10982 cx.propagate();
10983 }
10984
10985 pub fn previous_snippet_tabstop(
10986 &mut self,
10987 _: &PreviousSnippetTabstop,
10988 window: &mut Window,
10989 cx: &mut Context<Self>,
10990 ) {
10991 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10992 cx.propagate();
10993 return;
10994 }
10995
10996 if self.move_to_prev_snippet_tabstop(window, cx) {
10997 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10998 return;
10999 }
11000 cx.propagate();
11001 }
11002
11003 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
11004 if self.mode.is_single_line() {
11005 cx.propagate();
11006 return;
11007 }
11008
11009 if self.move_to_next_snippet_tabstop(window, cx) {
11010 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11011 return;
11012 }
11013 if self.read_only(cx) {
11014 return;
11015 }
11016 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11017 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
11018 let buffer = self.buffer.read(cx);
11019 let snapshot = buffer.snapshot(cx);
11020 let rows_iter = selections.iter().map(|s| s.head().row);
11021 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
11022
11023 let has_some_cursor_in_whitespace = selections
11024 .iter()
11025 .filter(|selection| selection.is_empty())
11026 .any(|selection| {
11027 let cursor = selection.head();
11028 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
11029 cursor.column < current_indent.len
11030 });
11031
11032 let mut edits = Vec::new();
11033 let mut prev_edited_row = 0;
11034 let mut row_delta = 0;
11035 for selection in &mut selections {
11036 if selection.start.row != prev_edited_row {
11037 row_delta = 0;
11038 }
11039 prev_edited_row = selection.end.row;
11040
11041 // If cursor is after a list prefix, make selection non-empty to trigger line indent
11042 if selection.is_empty() {
11043 let cursor = selection.head();
11044 let settings = buffer.language_settings_at(cursor, cx);
11045 if settings.indent_list_on_tab {
11046 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
11047 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
11048 row_delta = Self::indent_selection(
11049 buffer, &snapshot, selection, &mut edits, row_delta, cx,
11050 );
11051 continue;
11052 }
11053 }
11054 }
11055 }
11056
11057 // If the selection is non-empty, then increase the indentation of the selected lines.
11058 if !selection.is_empty() {
11059 row_delta =
11060 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11061 continue;
11062 }
11063
11064 let cursor = selection.head();
11065 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
11066 if let Some(suggested_indent) =
11067 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
11068 {
11069 // Don't do anything if already at suggested indent
11070 // and there is any other cursor which is not
11071 if has_some_cursor_in_whitespace
11072 && cursor.column == current_indent.len
11073 && current_indent.len == suggested_indent.len
11074 {
11075 continue;
11076 }
11077
11078 // Adjust line and move cursor to suggested indent
11079 // if cursor is not at suggested indent
11080 if cursor.column < suggested_indent.len
11081 && cursor.column <= current_indent.len
11082 && current_indent.len <= suggested_indent.len
11083 {
11084 selection.start = Point::new(cursor.row, suggested_indent.len);
11085 selection.end = selection.start;
11086 if row_delta == 0 {
11087 edits.extend(Buffer::edit_for_indent_size_adjustment(
11088 cursor.row,
11089 current_indent,
11090 suggested_indent,
11091 ));
11092 row_delta = suggested_indent.len - current_indent.len;
11093 }
11094 continue;
11095 }
11096
11097 // If current indent is more than suggested indent
11098 // only move cursor to current indent and skip indent
11099 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
11100 selection.start = Point::new(cursor.row, current_indent.len);
11101 selection.end = selection.start;
11102 continue;
11103 }
11104 }
11105
11106 // Otherwise, insert a hard or soft tab.
11107 let settings = buffer.language_settings_at(cursor, cx);
11108 let tab_size = if settings.hard_tabs {
11109 IndentSize::tab()
11110 } else {
11111 let tab_size = settings.tab_size.get();
11112 let indent_remainder = snapshot
11113 .text_for_range(Point::new(cursor.row, 0)..cursor)
11114 .flat_map(str::chars)
11115 .fold(row_delta % tab_size, |counter: u32, c| {
11116 if c == '\t' {
11117 0
11118 } else {
11119 (counter + 1) % tab_size
11120 }
11121 });
11122
11123 let chars_to_next_tab_stop = tab_size - indent_remainder;
11124 IndentSize::spaces(chars_to_next_tab_stop)
11125 };
11126 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
11127 selection.end = selection.start;
11128 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11129 row_delta += tab_size.len;
11130 }
11131
11132 self.transact(window, cx, |this, window, cx| {
11133 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11134 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11135 this.refresh_edit_prediction(true, false, window, cx);
11136 });
11137 }
11138
11139 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11140 if self.read_only(cx) {
11141 return;
11142 }
11143 if self.mode.is_single_line() {
11144 cx.propagate();
11145 return;
11146 }
11147
11148 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11149 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11150 let mut prev_edited_row = 0;
11151 let mut row_delta = 0;
11152 let mut edits = Vec::new();
11153 let buffer = self.buffer.read(cx);
11154 let snapshot = buffer.snapshot(cx);
11155 for selection in &mut selections {
11156 if selection.start.row != prev_edited_row {
11157 row_delta = 0;
11158 }
11159 prev_edited_row = selection.end.row;
11160
11161 row_delta =
11162 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11163 }
11164
11165 self.transact(window, cx, |this, window, cx| {
11166 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11167 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11168 });
11169 }
11170
11171 fn indent_selection(
11172 buffer: &MultiBuffer,
11173 snapshot: &MultiBufferSnapshot,
11174 selection: &mut Selection<Point>,
11175 edits: &mut Vec<(Range<Point>, String)>,
11176 delta_for_start_row: u32,
11177 cx: &App,
11178 ) -> u32 {
11179 let settings = buffer.language_settings_at(selection.start, cx);
11180 let tab_size = settings.tab_size.get();
11181 let indent_kind = if settings.hard_tabs {
11182 IndentKind::Tab
11183 } else {
11184 IndentKind::Space
11185 };
11186 let mut start_row = selection.start.row;
11187 let mut end_row = selection.end.row + 1;
11188
11189 // If a selection ends at the beginning of a line, don't indent
11190 // that last line.
11191 if selection.end.column == 0 && selection.end.row > selection.start.row {
11192 end_row -= 1;
11193 }
11194
11195 // Avoid re-indenting a row that has already been indented by a
11196 // previous selection, but still update this selection's column
11197 // to reflect that indentation.
11198 if delta_for_start_row > 0 {
11199 start_row += 1;
11200 selection.start.column += delta_for_start_row;
11201 if selection.end.row == selection.start.row {
11202 selection.end.column += delta_for_start_row;
11203 }
11204 }
11205
11206 let mut delta_for_end_row = 0;
11207 let has_multiple_rows = start_row + 1 != end_row;
11208 for row in start_row..end_row {
11209 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11210 let indent_delta = match (current_indent.kind, indent_kind) {
11211 (IndentKind::Space, IndentKind::Space) => {
11212 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11213 IndentSize::spaces(columns_to_next_tab_stop)
11214 }
11215 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11216 (_, IndentKind::Tab) => IndentSize::tab(),
11217 };
11218
11219 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11220 0
11221 } else {
11222 selection.start.column
11223 };
11224 let row_start = Point::new(row, start);
11225 edits.push((
11226 row_start..row_start,
11227 indent_delta.chars().collect::<String>(),
11228 ));
11229
11230 // Update this selection's endpoints to reflect the indentation.
11231 if row == selection.start.row {
11232 selection.start.column += indent_delta.len;
11233 }
11234 if row == selection.end.row {
11235 selection.end.column += indent_delta.len;
11236 delta_for_end_row = indent_delta.len;
11237 }
11238 }
11239
11240 if selection.start.row == selection.end.row {
11241 delta_for_start_row + delta_for_end_row
11242 } else {
11243 delta_for_end_row
11244 }
11245 }
11246
11247 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11248 if self.read_only(cx) {
11249 return;
11250 }
11251 if self.mode.is_single_line() {
11252 cx.propagate();
11253 return;
11254 }
11255
11256 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11257 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11258 let selections = self.selections.all::<Point>(&display_map);
11259 let mut deletion_ranges = Vec::new();
11260 let mut last_outdent = None;
11261 {
11262 let buffer = self.buffer.read(cx);
11263 let snapshot = buffer.snapshot(cx);
11264 for selection in &selections {
11265 let settings = buffer.language_settings_at(selection.start, cx);
11266 let tab_size = settings.tab_size.get();
11267 let mut rows = selection.spanned_rows(false, &display_map);
11268
11269 // Avoid re-outdenting a row that has already been outdented by a
11270 // previous selection.
11271 if let Some(last_row) = last_outdent
11272 && last_row == rows.start
11273 {
11274 rows.start = rows.start.next_row();
11275 }
11276 let has_multiple_rows = rows.len() > 1;
11277 for row in rows.iter_rows() {
11278 let indent_size = snapshot.indent_size_for_line(row);
11279 if indent_size.len > 0 {
11280 let deletion_len = match indent_size.kind {
11281 IndentKind::Space => {
11282 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11283 if columns_to_prev_tab_stop == 0 {
11284 tab_size
11285 } else {
11286 columns_to_prev_tab_stop
11287 }
11288 }
11289 IndentKind::Tab => 1,
11290 };
11291 let start = if has_multiple_rows
11292 || deletion_len > selection.start.column
11293 || indent_size.len < selection.start.column
11294 {
11295 0
11296 } else {
11297 selection.start.column - deletion_len
11298 };
11299 deletion_ranges.push(
11300 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11301 );
11302 last_outdent = Some(row);
11303 }
11304 }
11305 }
11306 }
11307
11308 self.transact(window, cx, |this, window, cx| {
11309 this.buffer.update(cx, |buffer, cx| {
11310 let empty_str: Arc<str> = Arc::default();
11311 buffer.edit(
11312 deletion_ranges
11313 .into_iter()
11314 .map(|range| (range, empty_str.clone())),
11315 None,
11316 cx,
11317 );
11318 });
11319 let selections = this
11320 .selections
11321 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11322 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11323 });
11324 }
11325
11326 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11327 if self.read_only(cx) {
11328 return;
11329 }
11330 if self.mode.is_single_line() {
11331 cx.propagate();
11332 return;
11333 }
11334
11335 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11336 let selections = self
11337 .selections
11338 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11339 .into_iter()
11340 .map(|s| s.range());
11341
11342 self.transact(window, cx, |this, window, cx| {
11343 this.buffer.update(cx, |buffer, cx| {
11344 buffer.autoindent_ranges(selections, cx);
11345 });
11346 let selections = this
11347 .selections
11348 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11349 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11350 });
11351 }
11352
11353 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11354 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11355 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11356 let selections = self.selections.all::<Point>(&display_map);
11357
11358 let mut new_cursors = Vec::new();
11359 let mut edit_ranges = Vec::new();
11360 let mut selections = selections.iter().peekable();
11361 while let Some(selection) = selections.next() {
11362 let mut rows = selection.spanned_rows(false, &display_map);
11363
11364 // Accumulate contiguous regions of rows that we want to delete.
11365 while let Some(next_selection) = selections.peek() {
11366 let next_rows = next_selection.spanned_rows(false, &display_map);
11367 if next_rows.start <= rows.end {
11368 rows.end = next_rows.end;
11369 selections.next().unwrap();
11370 } else {
11371 break;
11372 }
11373 }
11374
11375 let buffer = display_map.buffer_snapshot();
11376 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11377 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11378 // If there's a line after the range, delete the \n from the end of the row range
11379 (
11380 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11381 rows.end,
11382 )
11383 } else {
11384 // If there isn't a line after the range, delete the \n from the line before the
11385 // start of the row range
11386 edit_start = edit_start.saturating_sub_usize(1);
11387 (buffer.len(), rows.start.previous_row())
11388 };
11389
11390 let text_layout_details = self.text_layout_details(window, cx);
11391 let x = display_map.x_for_display_point(
11392 selection.head().to_display_point(&display_map),
11393 &text_layout_details,
11394 );
11395 let row = Point::new(target_row.0, 0)
11396 .to_display_point(&display_map)
11397 .row();
11398 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11399
11400 new_cursors.push((
11401 selection.id,
11402 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11403 SelectionGoal::None,
11404 ));
11405 edit_ranges.push(edit_start..edit_end);
11406 }
11407
11408 self.transact(window, cx, |this, window, cx| {
11409 let buffer = this.buffer.update(cx, |buffer, cx| {
11410 let empty_str: Arc<str> = Arc::default();
11411 buffer.edit(
11412 edit_ranges
11413 .into_iter()
11414 .map(|range| (range, empty_str.clone())),
11415 None,
11416 cx,
11417 );
11418 buffer.snapshot(cx)
11419 });
11420 let new_selections = new_cursors
11421 .into_iter()
11422 .map(|(id, cursor, goal)| {
11423 let cursor = cursor.to_point(&buffer);
11424 Selection {
11425 id,
11426 start: cursor,
11427 end: cursor,
11428 reversed: false,
11429 goal,
11430 }
11431 })
11432 .collect();
11433
11434 this.change_selections(Default::default(), window, cx, |s| {
11435 s.select(new_selections);
11436 });
11437 });
11438 }
11439
11440 pub fn join_lines_impl(
11441 &mut self,
11442 insert_whitespace: bool,
11443 window: &mut Window,
11444 cx: &mut Context<Self>,
11445 ) {
11446 if self.read_only(cx) {
11447 return;
11448 }
11449 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11450 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11451 let start = MultiBufferRow(selection.start.row);
11452 // Treat single line selections as if they include the next line. Otherwise this action
11453 // would do nothing for single line selections individual cursors.
11454 let end = if selection.start.row == selection.end.row {
11455 MultiBufferRow(selection.start.row + 1)
11456 } else if selection.end.column == 0 {
11457 // If the selection ends at the start of a line, it's logically at the end of the
11458 // previous line (plus its newline).
11459 // Don't include the end line unless there's only one line selected.
11460 if selection.start.row + 1 == selection.end.row {
11461 MultiBufferRow(selection.end.row)
11462 } else {
11463 MultiBufferRow(selection.end.row - 1)
11464 }
11465 } else {
11466 MultiBufferRow(selection.end.row)
11467 };
11468
11469 if let Some(last_row_range) = row_ranges.last_mut()
11470 && start <= last_row_range.end
11471 {
11472 last_row_range.end = end;
11473 continue;
11474 }
11475 row_ranges.push(start..end);
11476 }
11477
11478 let snapshot = self.buffer.read(cx).snapshot(cx);
11479 let mut cursor_positions = Vec::new();
11480 for row_range in &row_ranges {
11481 let anchor = snapshot.anchor_before(Point::new(
11482 row_range.end.previous_row().0,
11483 snapshot.line_len(row_range.end.previous_row()),
11484 ));
11485 cursor_positions.push(anchor..anchor);
11486 }
11487
11488 self.transact(window, cx, |this, window, cx| {
11489 for row_range in row_ranges.into_iter().rev() {
11490 for row in row_range.iter_rows().rev() {
11491 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11492 let next_line_row = row.next_row();
11493 let indent = snapshot.indent_size_for_line(next_line_row);
11494 let mut join_start_column = indent.len;
11495
11496 if let Some(language_scope) =
11497 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11498 {
11499 let line_end =
11500 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11501 let line_text_after_indent = snapshot
11502 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11503 .collect::<String>();
11504
11505 if !line_text_after_indent.is_empty() {
11506 let block_prefix = language_scope
11507 .block_comment()
11508 .map(|c| c.prefix.as_ref())
11509 .filter(|p| !p.is_empty());
11510 let doc_prefix = language_scope
11511 .documentation_comment()
11512 .map(|c| c.prefix.as_ref())
11513 .filter(|p| !p.is_empty());
11514 let all_prefixes = language_scope
11515 .line_comment_prefixes()
11516 .iter()
11517 .map(|p| p.as_ref())
11518 .chain(block_prefix)
11519 .chain(doc_prefix)
11520 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11521
11522 let mut longest_prefix_len = None;
11523 for prefix in all_prefixes {
11524 let trimmed = prefix.trim_end();
11525 if line_text_after_indent.starts_with(trimmed) {
11526 let candidate_len =
11527 if line_text_after_indent.starts_with(prefix) {
11528 prefix.len()
11529 } else {
11530 trimmed.len()
11531 };
11532 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11533 longest_prefix_len = Some(candidate_len);
11534 }
11535 }
11536 }
11537
11538 if let Some(prefix_len) = longest_prefix_len {
11539 join_start_column =
11540 join_start_column.saturating_add(prefix_len as u32);
11541 }
11542 }
11543 }
11544
11545 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11546
11547 let replace = if snapshot.line_len(next_line_row) > join_start_column
11548 && insert_whitespace
11549 {
11550 " "
11551 } else {
11552 ""
11553 };
11554
11555 this.buffer.update(cx, |buffer, cx| {
11556 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11557 });
11558 }
11559 }
11560
11561 this.change_selections(Default::default(), window, cx, |s| {
11562 s.select_anchor_ranges(cursor_positions)
11563 });
11564 });
11565 }
11566
11567 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11568 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11569 self.join_lines_impl(true, window, cx);
11570 }
11571
11572 pub fn sort_lines_case_sensitive(
11573 &mut self,
11574 _: &SortLinesCaseSensitive,
11575 window: &mut Window,
11576 cx: &mut Context<Self>,
11577 ) {
11578 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11579 }
11580
11581 pub fn sort_lines_by_length(
11582 &mut self,
11583 _: &SortLinesByLength,
11584 window: &mut Window,
11585 cx: &mut Context<Self>,
11586 ) {
11587 self.manipulate_immutable_lines(window, cx, |lines| {
11588 lines.sort_by_key(|&line| line.chars().count())
11589 })
11590 }
11591
11592 pub fn sort_lines_case_insensitive(
11593 &mut self,
11594 _: &SortLinesCaseInsensitive,
11595 window: &mut Window,
11596 cx: &mut Context<Self>,
11597 ) {
11598 self.manipulate_immutable_lines(window, cx, |lines| {
11599 lines.sort_by_key(|line| line.to_lowercase())
11600 })
11601 }
11602
11603 pub fn unique_lines_case_insensitive(
11604 &mut self,
11605 _: &UniqueLinesCaseInsensitive,
11606 window: &mut Window,
11607 cx: &mut Context<Self>,
11608 ) {
11609 self.manipulate_immutable_lines(window, cx, |lines| {
11610 let mut seen = HashSet::default();
11611 lines.retain(|line| seen.insert(line.to_lowercase()));
11612 })
11613 }
11614
11615 pub fn unique_lines_case_sensitive(
11616 &mut self,
11617 _: &UniqueLinesCaseSensitive,
11618 window: &mut Window,
11619 cx: &mut Context<Self>,
11620 ) {
11621 self.manipulate_immutable_lines(window, cx, |lines| {
11622 let mut seen = HashSet::default();
11623 lines.retain(|line| seen.insert(*line));
11624 })
11625 }
11626
11627 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11628 let snapshot = self.buffer.read(cx).snapshot(cx);
11629 for selection in self.selections.disjoint_anchors_arc().iter() {
11630 if snapshot
11631 .language_at(selection.start)
11632 .and_then(|lang| lang.config().wrap_characters.as_ref())
11633 .is_some()
11634 {
11635 return true;
11636 }
11637 }
11638 false
11639 }
11640
11641 fn wrap_selections_in_tag(
11642 &mut self,
11643 _: &WrapSelectionsInTag,
11644 window: &mut Window,
11645 cx: &mut Context<Self>,
11646 ) {
11647 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11648
11649 let snapshot = self.buffer.read(cx).snapshot(cx);
11650
11651 let mut edits = Vec::new();
11652 let mut boundaries = Vec::new();
11653
11654 for selection in self
11655 .selections
11656 .all_adjusted(&self.display_snapshot(cx))
11657 .iter()
11658 {
11659 let Some(wrap_config) = snapshot
11660 .language_at(selection.start)
11661 .and_then(|lang| lang.config().wrap_characters.clone())
11662 else {
11663 continue;
11664 };
11665
11666 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11667 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11668
11669 let start_before = snapshot.anchor_before(selection.start);
11670 let end_after = snapshot.anchor_after(selection.end);
11671
11672 edits.push((start_before..start_before, open_tag));
11673 edits.push((end_after..end_after, close_tag));
11674
11675 boundaries.push((
11676 start_before,
11677 end_after,
11678 wrap_config.start_prefix.len(),
11679 wrap_config.end_suffix.len(),
11680 ));
11681 }
11682
11683 if edits.is_empty() {
11684 return;
11685 }
11686
11687 self.transact(window, cx, |this, window, cx| {
11688 let buffer = this.buffer.update(cx, |buffer, cx| {
11689 buffer.edit(edits, None, cx);
11690 buffer.snapshot(cx)
11691 });
11692
11693 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11694 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11695 boundaries.into_iter()
11696 {
11697 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11698 let close_offset = end_after
11699 .to_offset(&buffer)
11700 .saturating_sub_usize(end_suffix_len);
11701 new_selections.push(open_offset..open_offset);
11702 new_selections.push(close_offset..close_offset);
11703 }
11704
11705 this.change_selections(Default::default(), window, cx, |s| {
11706 s.select_ranges(new_selections);
11707 });
11708
11709 this.request_autoscroll(Autoscroll::fit(), cx);
11710 });
11711 }
11712
11713 pub fn toggle_read_only(
11714 &mut self,
11715 _: &workspace::ToggleReadOnlyFile,
11716 _: &mut Window,
11717 cx: &mut Context<Self>,
11718 ) {
11719 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11720 buffer.update(cx, |buffer, cx| {
11721 buffer.set_capability(
11722 match buffer.capability() {
11723 Capability::ReadWrite => Capability::Read,
11724 Capability::Read => Capability::ReadWrite,
11725 Capability::ReadOnly => Capability::ReadOnly,
11726 },
11727 cx,
11728 );
11729 })
11730 }
11731 }
11732
11733 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11734 let Some(project) = self.project.clone() else {
11735 return;
11736 };
11737 let task = self.reload(project, window, cx);
11738 self.detach_and_notify_err(task, window, cx);
11739 }
11740
11741 pub fn restore_file(
11742 &mut self,
11743 _: &::git::RestoreFile,
11744 window: &mut Window,
11745 cx: &mut Context<Self>,
11746 ) {
11747 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11748 let mut buffer_ids = HashSet::default();
11749 let snapshot = self.buffer().read(cx).snapshot(cx);
11750 for selection in self
11751 .selections
11752 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11753 {
11754 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11755 }
11756
11757 let ranges = buffer_ids
11758 .into_iter()
11759 .flat_map(|buffer_id| snapshot.range_for_buffer(buffer_id))
11760 .collect::<Vec<_>>();
11761
11762 self.restore_hunks_in_ranges(ranges, window, cx);
11763 }
11764
11765 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11767 let selections = self
11768 .selections
11769 .all(&self.display_snapshot(cx))
11770 .into_iter()
11771 .map(|s| s.range())
11772 .collect();
11773 self.restore_hunks_in_ranges(selections, window, cx);
11774 }
11775
11776 /// Restores the diff hunks in the editor's selections and moves the cursor
11777 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11778 /// not all diff hunks are expanded.
11779 pub fn restore_and_next(
11780 &mut self,
11781 _: &::git::RestoreAndNext,
11782 window: &mut Window,
11783 cx: &mut Context<Self>,
11784 ) {
11785 let selections = self
11786 .selections
11787 .all(&self.display_snapshot(cx))
11788 .into_iter()
11789 .map(|selection| selection.range())
11790 .collect();
11791
11792 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11793 self.restore_hunks_in_ranges(selections, window, cx);
11794
11795 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11796 let wrap_around = !all_diff_hunks_expanded;
11797 let snapshot = self.snapshot(window, cx);
11798 let position = self
11799 .selections
11800 .newest::<Point>(&snapshot.display_snapshot)
11801 .head();
11802
11803 self.go_to_hunk_before_or_after_position(
11804 &snapshot,
11805 position,
11806 Direction::Next,
11807 wrap_around,
11808 window,
11809 cx,
11810 );
11811 }
11812
11813 pub fn restore_hunks_in_ranges(
11814 &mut self,
11815 ranges: Vec<Range<Point>>,
11816 window: &mut Window,
11817 cx: &mut Context<Editor>,
11818 ) {
11819 if self.delegate_stage_and_restore {
11820 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11821 if !hunks.is_empty() {
11822 cx.emit(EditorEvent::RestoreRequested { hunks });
11823 }
11824 return;
11825 }
11826 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11827 self.transact(window, cx, |editor, window, cx| {
11828 editor.restore_diff_hunks(hunks, cx);
11829 let selections = editor
11830 .selections
11831 .all::<MultiBufferOffset>(&editor.display_snapshot(cx));
11832 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11833 s.select(selections);
11834 });
11835 });
11836 }
11837
11838 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11839 let mut revert_changes = HashMap::default();
11840 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11841 for (buffer_id, hunks) in &chunk_by {
11842 let hunks = hunks.collect::<Vec<_>>();
11843 for hunk in &hunks {
11844 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11845 }
11846 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11847 }
11848 if !revert_changes.is_empty() {
11849 self.buffer().update(cx, |multi_buffer, cx| {
11850 for (buffer_id, changes) in revert_changes {
11851 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11852 buffer.update(cx, |buffer, cx| {
11853 buffer.edit(
11854 changes
11855 .into_iter()
11856 .map(|(range, text)| (range, text.to_string())),
11857 None,
11858 cx,
11859 );
11860 });
11861 }
11862 }
11863 });
11864 }
11865 }
11866
11867 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11868 if let Some(status) = self
11869 .addons
11870 .iter()
11871 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11872 {
11873 return Some(status);
11874 }
11875 self.project
11876 .as_ref()?
11877 .read(cx)
11878 .status_for_buffer_id(buffer_id, cx)
11879 }
11880
11881 pub fn open_active_item_in_terminal(
11882 &mut self,
11883 _: &OpenInTerminal,
11884 window: &mut Window,
11885 cx: &mut Context<Self>,
11886 ) {
11887 if let Some(working_directory) = self.active_buffer(cx).and_then(|buffer| {
11888 let project_path = buffer.read(cx).project_path(cx)?;
11889 let project = self.project()?.read(cx);
11890 let entry = project.entry_for_path(&project_path, cx)?;
11891 let parent = match &entry.canonical_path {
11892 Some(canonical_path) => canonical_path.to_path_buf(),
11893 None => project.absolute_path(&project_path, cx)?,
11894 }
11895 .parent()?
11896 .to_path_buf();
11897 Some(parent)
11898 }) {
11899 window.dispatch_action(
11900 OpenTerminal {
11901 working_directory,
11902 local: false,
11903 }
11904 .boxed_clone(),
11905 cx,
11906 );
11907 }
11908 }
11909
11910 fn set_breakpoint_context_menu(
11911 &mut self,
11912 display_row: DisplayRow,
11913 position: Option<Anchor>,
11914 clicked_point: gpui::Point<Pixels>,
11915 window: &mut Window,
11916 cx: &mut Context<Self>,
11917 ) {
11918 let source = self
11919 .buffer
11920 .read(cx)
11921 .snapshot(cx)
11922 .anchor_before(Point::new(display_row.0, 0u32));
11923
11924 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11925
11926 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11927 self,
11928 source,
11929 clicked_point,
11930 context_menu,
11931 window,
11932 cx,
11933 );
11934 }
11935
11936 fn add_edit_breakpoint_block(
11937 &mut self,
11938 anchor: Anchor,
11939 breakpoint: &Breakpoint,
11940 edit_action: BreakpointPromptEditAction,
11941 window: &mut Window,
11942 cx: &mut Context<Self>,
11943 ) {
11944 let weak_editor = cx.weak_entity();
11945 let bp_prompt = cx.new(|cx| {
11946 BreakpointPromptEditor::new(
11947 weak_editor,
11948 anchor,
11949 breakpoint.clone(),
11950 edit_action,
11951 window,
11952 cx,
11953 )
11954 });
11955
11956 let height = bp_prompt.update(cx, |this, cx| {
11957 this.prompt
11958 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11959 });
11960 let cloned_prompt = bp_prompt.clone();
11961 let blocks = vec![BlockProperties {
11962 style: BlockStyle::Sticky,
11963 placement: BlockPlacement::Above(anchor),
11964 height: Some(height),
11965 render: Arc::new(move |cx| {
11966 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11967 cloned_prompt.clone().into_any_element()
11968 }),
11969 priority: 0,
11970 }];
11971
11972 let focus_handle = bp_prompt.focus_handle(cx);
11973 window.focus(&focus_handle, cx);
11974
11975 let block_ids = self.insert_blocks(blocks, None, cx);
11976 bp_prompt.update(cx, |prompt, _| {
11977 prompt.add_block_ids(block_ids);
11978 });
11979 }
11980
11981 pub(crate) fn breakpoint_at_row(
11982 &self,
11983 row: u32,
11984 window: &mut Window,
11985 cx: &mut Context<Self>,
11986 ) -> Option<(Anchor, Breakpoint)> {
11987 let snapshot = self.snapshot(window, cx);
11988 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11989
11990 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11991 }
11992
11993 pub(crate) fn breakpoint_at_anchor(
11994 &self,
11995 breakpoint_position: Anchor,
11996 snapshot: &EditorSnapshot,
11997 cx: &mut Context<Self>,
11998 ) -> Option<(Anchor, Breakpoint)> {
11999 let (breakpoint_position, _) = snapshot
12000 .buffer_snapshot()
12001 .anchor_to_buffer_anchor(breakpoint_position)?;
12002 let buffer = self.buffer.read(cx).buffer(breakpoint_position.buffer_id)?;
12003
12004 let buffer_snapshot = buffer.read(cx).snapshot();
12005
12006 let row = buffer_snapshot
12007 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position)
12008 .row;
12009
12010 let line_len = buffer_snapshot.line_len(row);
12011 let anchor_end = buffer_snapshot.anchor_after(Point::new(row, line_len));
12012
12013 self.breakpoint_store
12014 .as_ref()?
12015 .read_with(cx, |breakpoint_store, cx| {
12016 breakpoint_store
12017 .breakpoints(
12018 &buffer,
12019 Some(breakpoint_position..anchor_end),
12020 &buffer_snapshot,
12021 cx,
12022 )
12023 .next()
12024 .and_then(|(bp, _)| {
12025 let breakpoint_row = buffer_snapshot
12026 .summary_for_anchor::<text::PointUtf16>(&bp.position)
12027 .row;
12028
12029 if breakpoint_row == row {
12030 snapshot
12031 .buffer_snapshot()
12032 .anchor_in_excerpt(bp.position)
12033 .map(|position| (position, bp.bp.clone()))
12034 } else {
12035 None
12036 }
12037 })
12038 })
12039 }
12040
12041 pub fn edit_log_breakpoint(
12042 &mut self,
12043 _: &EditLogBreakpoint,
12044 window: &mut Window,
12045 cx: &mut Context<Self>,
12046 ) {
12047 if self.breakpoint_store.is_none() {
12048 return;
12049 }
12050
12051 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12052 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
12053 message: None,
12054 state: BreakpointState::Enabled,
12055 condition: None,
12056 hit_condition: None,
12057 });
12058
12059 self.add_edit_breakpoint_block(
12060 anchor,
12061 &breakpoint,
12062 BreakpointPromptEditAction::Log,
12063 window,
12064 cx,
12065 );
12066 }
12067 }
12068
12069 fn breakpoints_at_cursors(
12070 &self,
12071 window: &mut Window,
12072 cx: &mut Context<Self>,
12073 ) -> Vec<(Anchor, Option<Breakpoint>)> {
12074 let snapshot = self.snapshot(window, cx);
12075 let cursors = self
12076 .selections
12077 .disjoint_anchors_arc()
12078 .iter()
12079 .map(|selection| {
12080 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
12081
12082 let breakpoint_position = self
12083 .breakpoint_at_row(cursor_position.row, window, cx)
12084 .map(|bp| bp.0)
12085 .unwrap_or_else(|| {
12086 snapshot
12087 .display_snapshot
12088 .buffer_snapshot()
12089 .anchor_after(Point::new(cursor_position.row, 0))
12090 });
12091
12092 let breakpoint = self
12093 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
12094 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
12095
12096 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
12097 })
12098 // There might be multiple cursors on the same line; all of them should have the same anchors though as their breakpoints positions, which makes it possible to sort and dedup the list.
12099 .collect::<HashMap<Anchor, _>>();
12100
12101 cursors.into_iter().collect()
12102 }
12103
12104 pub fn enable_breakpoint(
12105 &mut self,
12106 _: &crate::actions::EnableBreakpoint,
12107 window: &mut Window,
12108 cx: &mut Context<Self>,
12109 ) {
12110 if self.breakpoint_store.is_none() {
12111 return;
12112 }
12113
12114 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12115 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
12116 continue;
12117 };
12118 self.edit_breakpoint_at_anchor(
12119 anchor,
12120 breakpoint,
12121 BreakpointEditAction::InvertState,
12122 cx,
12123 );
12124 }
12125 }
12126
12127 pub fn align_selections(
12128 &mut self,
12129 _: &crate::actions::AlignSelections,
12130 window: &mut Window,
12131 cx: &mut Context<Self>,
12132 ) {
12133 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12134
12135 let display_snapshot = self.display_snapshot(cx);
12136
12137 struct CursorData {
12138 anchor: Anchor,
12139 point: Point,
12140 }
12141 let cursor_data: Vec<CursorData> = self
12142 .selections
12143 .disjoint_anchors()
12144 .iter()
12145 .map(|selection| {
12146 let anchor = if selection.reversed {
12147 selection.head()
12148 } else {
12149 selection.tail()
12150 };
12151 CursorData {
12152 anchor: anchor,
12153 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
12154 }
12155 })
12156 .collect();
12157
12158 let rows_anchors_count: Vec<usize> = cursor_data
12159 .iter()
12160 .map(|cursor| cursor.point.row)
12161 .chunk_by(|&row| row)
12162 .into_iter()
12163 .map(|(_, group)| group.count())
12164 .collect();
12165 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
12166 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
12167 let mut edits = Vec::new();
12168
12169 for column_idx in 0..max_columns {
12170 let mut cursor_index = 0;
12171
12172 // Calculate target_column => position that the selections will go
12173 let mut target_column = 0;
12174 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12175 // Skip rows that don't have this column
12176 if column_idx >= *cursor_count {
12177 cursor_index += cursor_count;
12178 continue;
12179 }
12180
12181 let point = &cursor_data[cursor_index + column_idx].point;
12182 let adjusted_column = point.column + rows_column_offset[row_idx];
12183 if adjusted_column > target_column {
12184 target_column = adjusted_column;
12185 }
12186 cursor_index += cursor_count;
12187 }
12188
12189 // Collect edits for this column
12190 cursor_index = 0;
12191 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12192 // Skip rows that don't have this column
12193 if column_idx >= *cursor_count {
12194 cursor_index += *cursor_count;
12195 continue;
12196 }
12197
12198 let point = &cursor_data[cursor_index + column_idx].point;
12199 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
12200 if spaces_needed > 0 {
12201 let anchor = cursor_data[cursor_index + column_idx]
12202 .anchor
12203 .bias_left(&display_snapshot);
12204 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
12205 }
12206 rows_column_offset[row_idx] += spaces_needed;
12207
12208 cursor_index += *cursor_count;
12209 }
12210 }
12211
12212 if !edits.is_empty() {
12213 self.transact(window, cx, |editor, _window, cx| {
12214 editor.edit(edits, cx);
12215 });
12216 }
12217 }
12218
12219 pub fn disable_breakpoint(
12220 &mut self,
12221 _: &crate::actions::DisableBreakpoint,
12222 window: &mut Window,
12223 cx: &mut Context<Self>,
12224 ) {
12225 if self.breakpoint_store.is_none() {
12226 return;
12227 }
12228
12229 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12230 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12231 continue;
12232 };
12233 self.edit_breakpoint_at_anchor(
12234 anchor,
12235 breakpoint,
12236 BreakpointEditAction::InvertState,
12237 cx,
12238 );
12239 }
12240 }
12241
12242 pub fn toggle_breakpoint(
12243 &mut self,
12244 _: &crate::actions::ToggleBreakpoint,
12245 window: &mut Window,
12246 cx: &mut Context<Self>,
12247 ) {
12248 if self.breakpoint_store.is_none() {
12249 return;
12250 }
12251
12252 let snapshot = self.snapshot(window, cx);
12253 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12254 if self.gutter_breakpoint_indicator.0.is_some() {
12255 let display_row = anchor
12256 .to_point(snapshot.buffer_snapshot())
12257 .to_display_point(&snapshot.display_snapshot)
12258 .row();
12259 self.update_breakpoint_collision_on_toggle(
12260 display_row,
12261 &BreakpointEditAction::Toggle,
12262 );
12263 }
12264
12265 if let Some(breakpoint) = breakpoint {
12266 self.edit_breakpoint_at_anchor(
12267 anchor,
12268 breakpoint,
12269 BreakpointEditAction::Toggle,
12270 cx,
12271 );
12272 } else {
12273 self.edit_breakpoint_at_anchor(
12274 anchor,
12275 Breakpoint::new_standard(),
12276 BreakpointEditAction::Toggle,
12277 cx,
12278 );
12279 }
12280 }
12281 }
12282
12283 fn update_breakpoint_collision_on_toggle(
12284 &mut self,
12285 display_row: DisplayRow,
12286 edit_action: &BreakpointEditAction,
12287 ) {
12288 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12289 if breakpoint_indicator.display_row == display_row
12290 && matches!(edit_action, BreakpointEditAction::Toggle)
12291 {
12292 breakpoint_indicator.collides_with_existing_breakpoint =
12293 !breakpoint_indicator.collides_with_existing_breakpoint;
12294 }
12295 }
12296 }
12297
12298 pub fn edit_breakpoint_at_anchor(
12299 &mut self,
12300 breakpoint_position: Anchor,
12301 breakpoint: Breakpoint,
12302 edit_action: BreakpointEditAction,
12303 cx: &mut Context<Self>,
12304 ) {
12305 let Some(breakpoint_store) = &self.breakpoint_store else {
12306 return;
12307 };
12308 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
12309 let Some((position, _)) = buffer_snapshot.anchor_to_buffer_anchor(breakpoint_position)
12310 else {
12311 return;
12312 };
12313 let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
12314 return;
12315 };
12316
12317 breakpoint_store.update(cx, |breakpoint_store, cx| {
12318 breakpoint_store.toggle_breakpoint(
12319 buffer,
12320 BreakpointWithPosition {
12321 position,
12322 bp: breakpoint,
12323 },
12324 edit_action,
12325 cx,
12326 );
12327 });
12328
12329 cx.notify();
12330 }
12331
12332 #[cfg(any(test, feature = "test-support"))]
12333 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12334 self.breakpoint_store.clone()
12335 }
12336
12337 pub fn prepare_restore_change(
12338 &self,
12339 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12340 hunk: &MultiBufferDiffHunk,
12341 cx: &mut App,
12342 ) -> Option<()> {
12343 if hunk.is_created_file() {
12344 return None;
12345 }
12346 let multi_buffer = self.buffer.read(cx);
12347 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
12348 let diff_snapshot = multi_buffer_snapshot.diff_for_buffer_id(hunk.buffer_id)?;
12349 let original_text = diff_snapshot
12350 .base_text()
12351 .as_rope()
12352 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12353 let buffer = multi_buffer.buffer(hunk.buffer_id)?;
12354 let buffer = buffer.read(cx);
12355 let buffer_snapshot = buffer.snapshot();
12356 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12357 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12358 probe
12359 .0
12360 .start
12361 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12362 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12363 }) {
12364 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12365 Some(())
12366 } else {
12367 None
12368 }
12369 }
12370
12371 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12372 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12373 }
12374
12375 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12376 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12377 }
12378
12379 pub fn rotate_selections_forward(
12380 &mut self,
12381 _: &RotateSelectionsForward,
12382 window: &mut Window,
12383 cx: &mut Context<Self>,
12384 ) {
12385 self.rotate_selections(window, cx, false)
12386 }
12387
12388 pub fn rotate_selections_backward(
12389 &mut self,
12390 _: &RotateSelectionsBackward,
12391 window: &mut Window,
12392 cx: &mut Context<Self>,
12393 ) {
12394 self.rotate_selections(window, cx, true)
12395 }
12396
12397 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12399 let display_snapshot = self.display_snapshot(cx);
12400 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12401
12402 if selections.len() < 2 {
12403 return;
12404 }
12405
12406 let (edits, new_selections) = {
12407 let buffer = self.buffer.read(cx).read(cx);
12408 let has_selections = selections.iter().any(|s| !s.is_empty());
12409 if has_selections {
12410 let mut selected_texts: Vec<String> = selections
12411 .iter()
12412 .map(|selection| {
12413 buffer
12414 .text_for_range(selection.start..selection.end)
12415 .collect()
12416 })
12417 .collect();
12418
12419 if reverse {
12420 selected_texts.rotate_left(1);
12421 } else {
12422 selected_texts.rotate_right(1);
12423 }
12424
12425 let mut offset_delta: i64 = 0;
12426 let mut new_selections = Vec::new();
12427 let edits: Vec<_> = selections
12428 .iter()
12429 .zip(selected_texts.iter())
12430 .map(|(selection, new_text)| {
12431 let old_len = (selection.end.0 - selection.start.0) as i64;
12432 let new_len = new_text.len() as i64;
12433 let adjusted_start =
12434 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12435 let adjusted_end =
12436 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12437
12438 new_selections.push(Selection {
12439 id: selection.id,
12440 start: adjusted_start,
12441 end: adjusted_end,
12442 reversed: selection.reversed,
12443 goal: selection.goal,
12444 });
12445
12446 offset_delta += new_len - old_len;
12447 (selection.start..selection.end, new_text.clone())
12448 })
12449 .collect();
12450 (edits, new_selections)
12451 } else {
12452 let mut all_rows: Vec<u32> = selections
12453 .iter()
12454 .map(|selection| buffer.offset_to_point(selection.start).row)
12455 .collect();
12456 all_rows.sort_unstable();
12457 all_rows.dedup();
12458
12459 if all_rows.len() < 2 {
12460 return;
12461 }
12462
12463 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12464 .iter()
12465 .map(|&row| {
12466 let start = Point::new(row, 0);
12467 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12468 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12469 })
12470 .collect();
12471
12472 let mut line_texts: Vec<String> = line_ranges
12473 .iter()
12474 .map(|range| buffer.text_for_range(range.clone()).collect())
12475 .collect();
12476
12477 if reverse {
12478 line_texts.rotate_left(1);
12479 } else {
12480 line_texts.rotate_right(1);
12481 }
12482
12483 let edits = line_ranges
12484 .iter()
12485 .zip(line_texts.iter())
12486 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12487 .collect();
12488
12489 let num_rows = all_rows.len();
12490 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12491 .iter()
12492 .enumerate()
12493 .map(|(i, &row)| (row, i))
12494 .collect();
12495
12496 // Compute new line start offsets after rotation (handles CRLF)
12497 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12498 let first_line_start = line_ranges[0].start.0;
12499 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12500 for text in line_texts.iter().take(num_rows - 1) {
12501 let prev_start = *new_line_starts.last().unwrap();
12502 new_line_starts.push(prev_start + text.len() + newline_len);
12503 }
12504
12505 let new_selections = selections
12506 .iter()
12507 .map(|selection| {
12508 let point = buffer.offset_to_point(selection.start);
12509 let old_index = row_to_index[&point.row];
12510 let new_index = if reverse {
12511 (old_index + num_rows - 1) % num_rows
12512 } else {
12513 (old_index + 1) % num_rows
12514 };
12515 let new_offset =
12516 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12517 Selection {
12518 id: selection.id,
12519 start: new_offset,
12520 end: new_offset,
12521 reversed: selection.reversed,
12522 goal: selection.goal,
12523 }
12524 })
12525 .collect();
12526
12527 (edits, new_selections)
12528 }
12529 };
12530
12531 self.transact(window, cx, |this, window, cx| {
12532 this.buffer.update(cx, |buffer, cx| {
12533 buffer.edit(edits, None, cx);
12534 });
12535 this.change_selections(Default::default(), window, cx, |s| {
12536 s.select(new_selections);
12537 });
12538 });
12539 }
12540
12541 fn manipulate_lines<M>(
12542 &mut self,
12543 window: &mut Window,
12544 cx: &mut Context<Self>,
12545 mut manipulate: M,
12546 ) where
12547 M: FnMut(&str) -> LineManipulationResult,
12548 {
12549 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12550
12551 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12552 let buffer = self.buffer.read(cx).snapshot(cx);
12553
12554 let mut edits = Vec::new();
12555
12556 let selections = self.selections.all::<Point>(&display_map);
12557 let mut selections = selections.iter().peekable();
12558 let mut contiguous_row_selections = Vec::new();
12559 let mut new_selections = Vec::new();
12560 let mut added_lines = 0;
12561 let mut removed_lines = 0;
12562
12563 while let Some(selection) = selections.next() {
12564 let (start_row, end_row) = consume_contiguous_rows(
12565 &mut contiguous_row_selections,
12566 selection,
12567 &display_map,
12568 &mut selections,
12569 );
12570
12571 let start_point = Point::new(start_row.0, 0);
12572 let end_point = Point::new(
12573 end_row.previous_row().0,
12574 buffer.line_len(end_row.previous_row()),
12575 );
12576 let text = buffer
12577 .text_for_range(start_point..end_point)
12578 .collect::<String>();
12579
12580 let LineManipulationResult {
12581 new_text,
12582 line_count_before,
12583 line_count_after,
12584 } = manipulate(&text);
12585
12586 edits.push((start_point..end_point, new_text));
12587
12588 // Selections must change based on added and removed line count
12589 let start_row =
12590 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12591 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12592 new_selections.push(Selection {
12593 id: selection.id,
12594 start: start_row,
12595 end: end_row,
12596 goal: SelectionGoal::None,
12597 reversed: selection.reversed,
12598 });
12599
12600 if line_count_after > line_count_before {
12601 added_lines += line_count_after - line_count_before;
12602 } else if line_count_before > line_count_after {
12603 removed_lines += line_count_before - line_count_after;
12604 }
12605 }
12606
12607 self.transact(window, cx, |this, window, cx| {
12608 let buffer = this.buffer.update(cx, |buffer, cx| {
12609 buffer.edit(edits, None, cx);
12610 buffer.snapshot(cx)
12611 });
12612
12613 // Recalculate offsets on newly edited buffer
12614 let new_selections = new_selections
12615 .iter()
12616 .map(|s| {
12617 let start_point = Point::new(s.start.0, 0);
12618 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12619 Selection {
12620 id: s.id,
12621 start: buffer.point_to_offset(start_point),
12622 end: buffer.point_to_offset(end_point),
12623 goal: s.goal,
12624 reversed: s.reversed,
12625 }
12626 })
12627 .collect();
12628
12629 this.change_selections(Default::default(), window, cx, |s| {
12630 s.select(new_selections);
12631 });
12632
12633 this.request_autoscroll(Autoscroll::fit(), cx);
12634 });
12635 }
12636
12637 fn manipulate_immutable_lines<Fn>(
12638 &mut self,
12639 window: &mut Window,
12640 cx: &mut Context<Self>,
12641 mut callback: Fn,
12642 ) where
12643 Fn: FnMut(&mut Vec<&str>),
12644 {
12645 self.manipulate_lines(window, cx, |text| {
12646 let mut lines: Vec<&str> = text.split('\n').collect();
12647 let line_count_before = lines.len();
12648
12649 callback(&mut lines);
12650
12651 LineManipulationResult {
12652 new_text: lines.join("\n"),
12653 line_count_before,
12654 line_count_after: lines.len(),
12655 }
12656 });
12657 }
12658
12659 fn manipulate_mutable_lines<Fn>(
12660 &mut self,
12661 window: &mut Window,
12662 cx: &mut Context<Self>,
12663 mut callback: Fn,
12664 ) where
12665 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12666 {
12667 self.manipulate_lines(window, cx, |text| {
12668 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12669 let line_count_before = lines.len();
12670
12671 callback(&mut lines);
12672
12673 LineManipulationResult {
12674 new_text: lines.join("\n"),
12675 line_count_before,
12676 line_count_after: lines.len(),
12677 }
12678 });
12679 }
12680
12681 pub fn convert_indentation_to_spaces(
12682 &mut self,
12683 _: &ConvertIndentationToSpaces,
12684 window: &mut Window,
12685 cx: &mut Context<Self>,
12686 ) {
12687 let settings = self.buffer.read(cx).language_settings(cx);
12688 let tab_size = settings.tab_size.get() as usize;
12689
12690 self.manipulate_mutable_lines(window, cx, |lines| {
12691 // Allocates a reasonably sized scratch buffer once for the whole loop
12692 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12693 // Avoids recomputing spaces that could be inserted many times
12694 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12695 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12696 .collect();
12697
12698 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12699 let mut chars = line.as_ref().chars();
12700 let mut col = 0;
12701 let mut changed = false;
12702
12703 for ch in chars.by_ref() {
12704 match ch {
12705 ' ' => {
12706 reindented_line.push(' ');
12707 col += 1;
12708 }
12709 '\t' => {
12710 // \t are converted to spaces depending on the current column
12711 let spaces_len = tab_size - (col % tab_size);
12712 reindented_line.extend(&space_cache[spaces_len - 1]);
12713 col += spaces_len;
12714 changed = true;
12715 }
12716 _ => {
12717 // If we dont append before break, the character is consumed
12718 reindented_line.push(ch);
12719 break;
12720 }
12721 }
12722 }
12723
12724 if !changed {
12725 reindented_line.clear();
12726 continue;
12727 }
12728 // Append the rest of the line and replace old reference with new one
12729 reindented_line.extend(chars);
12730 *line = Cow::Owned(reindented_line.clone());
12731 reindented_line.clear();
12732 }
12733 });
12734 }
12735
12736 pub fn convert_indentation_to_tabs(
12737 &mut self,
12738 _: &ConvertIndentationToTabs,
12739 window: &mut Window,
12740 cx: &mut Context<Self>,
12741 ) {
12742 let settings = self.buffer.read(cx).language_settings(cx);
12743 let tab_size = settings.tab_size.get() as usize;
12744
12745 self.manipulate_mutable_lines(window, cx, |lines| {
12746 // Allocates a reasonably sized buffer once for the whole loop
12747 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12748 // Avoids recomputing spaces that could be inserted many times
12749 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12750 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12751 .collect();
12752
12753 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12754 let mut chars = line.chars();
12755 let mut spaces_count = 0;
12756 let mut first_non_indent_char = None;
12757 let mut changed = false;
12758
12759 for ch in chars.by_ref() {
12760 match ch {
12761 ' ' => {
12762 // Keep track of spaces. Append \t when we reach tab_size
12763 spaces_count += 1;
12764 changed = true;
12765 if spaces_count == tab_size {
12766 reindented_line.push('\t');
12767 spaces_count = 0;
12768 }
12769 }
12770 '\t' => {
12771 reindented_line.push('\t');
12772 spaces_count = 0;
12773 }
12774 _ => {
12775 // Dont append it yet, we might have remaining spaces
12776 first_non_indent_char = Some(ch);
12777 break;
12778 }
12779 }
12780 }
12781
12782 if !changed {
12783 reindented_line.clear();
12784 continue;
12785 }
12786 // Remaining spaces that didn't make a full tab stop
12787 if spaces_count > 0 {
12788 reindented_line.extend(&space_cache[spaces_count - 1]);
12789 }
12790 // If we consume an extra character that was not indentation, add it back
12791 if let Some(extra_char) = first_non_indent_char {
12792 reindented_line.push(extra_char);
12793 }
12794 // Append the rest of the line and replace old reference with new one
12795 reindented_line.extend(chars);
12796 *line = Cow::Owned(reindented_line.clone());
12797 reindented_line.clear();
12798 }
12799 });
12800 }
12801
12802 pub fn convert_to_upper_case(
12803 &mut self,
12804 _: &ConvertToUpperCase,
12805 window: &mut Window,
12806 cx: &mut Context<Self>,
12807 ) {
12808 self.manipulate_text(window, cx, |text| text.to_uppercase())
12809 }
12810
12811 pub fn convert_to_lower_case(
12812 &mut self,
12813 _: &ConvertToLowerCase,
12814 window: &mut Window,
12815 cx: &mut Context<Self>,
12816 ) {
12817 self.manipulate_text(window, cx, |text| text.to_lowercase())
12818 }
12819
12820 pub fn convert_to_title_case(
12821 &mut self,
12822 _: &ConvertToTitleCase,
12823 window: &mut Window,
12824 cx: &mut Context<Self>,
12825 ) {
12826 self.manipulate_text(window, cx, |text| {
12827 Self::convert_text_case(text, Case::Title)
12828 })
12829 }
12830
12831 pub fn convert_to_snake_case(
12832 &mut self,
12833 _: &ConvertToSnakeCase,
12834 window: &mut Window,
12835 cx: &mut Context<Self>,
12836 ) {
12837 self.manipulate_text(window, cx, |text| {
12838 Self::convert_text_case(text, Case::Snake)
12839 })
12840 }
12841
12842 pub fn convert_to_kebab_case(
12843 &mut self,
12844 _: &ConvertToKebabCase,
12845 window: &mut Window,
12846 cx: &mut Context<Self>,
12847 ) {
12848 self.manipulate_text(window, cx, |text| {
12849 Self::convert_text_case(text, Case::Kebab)
12850 })
12851 }
12852
12853 pub fn convert_to_upper_camel_case(
12854 &mut self,
12855 _: &ConvertToUpperCamelCase,
12856 window: &mut Window,
12857 cx: &mut Context<Self>,
12858 ) {
12859 self.manipulate_text(window, cx, |text| {
12860 Self::convert_text_case(text, Case::UpperCamel)
12861 })
12862 }
12863
12864 pub fn convert_to_lower_camel_case(
12865 &mut self,
12866 _: &ConvertToLowerCamelCase,
12867 window: &mut Window,
12868 cx: &mut Context<Self>,
12869 ) {
12870 self.manipulate_text(window, cx, |text| {
12871 Self::convert_text_case(text, Case::Camel)
12872 })
12873 }
12874
12875 pub fn convert_to_opposite_case(
12876 &mut self,
12877 _: &ConvertToOppositeCase,
12878 window: &mut Window,
12879 cx: &mut Context<Self>,
12880 ) {
12881 self.manipulate_text(window, cx, |text| {
12882 text.chars()
12883 .fold(String::with_capacity(text.len()), |mut t, c| {
12884 if c.is_uppercase() {
12885 t.extend(c.to_lowercase());
12886 } else {
12887 t.extend(c.to_uppercase());
12888 }
12889 t
12890 })
12891 })
12892 }
12893
12894 pub fn convert_to_sentence_case(
12895 &mut self,
12896 _: &ConvertToSentenceCase,
12897 window: &mut Window,
12898 cx: &mut Context<Self>,
12899 ) {
12900 self.manipulate_text(window, cx, |text| {
12901 Self::convert_text_case(text, Case::Sentence)
12902 })
12903 }
12904
12905 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12906 self.manipulate_text(window, cx, |text| {
12907 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12908 if has_upper_case_characters {
12909 text.to_lowercase()
12910 } else {
12911 text.to_uppercase()
12912 }
12913 })
12914 }
12915
12916 pub fn convert_to_rot13(
12917 &mut self,
12918 _: &ConvertToRot13,
12919 window: &mut Window,
12920 cx: &mut Context<Self>,
12921 ) {
12922 self.manipulate_text(window, cx, |text| {
12923 text.chars()
12924 .map(|c| match c {
12925 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12926 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12927 _ => c,
12928 })
12929 .collect()
12930 })
12931 }
12932
12933 fn convert_text_case(text: &str, case: Case) -> String {
12934 text.lines()
12935 .map(|line| {
12936 let trimmed_start = line.trim_start();
12937 let leading = &line[..line.len() - trimmed_start.len()];
12938 let trimmed = trimmed_start.trim_end();
12939 let trailing = &trimmed_start[trimmed.len()..];
12940 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12941 })
12942 .join("\n")
12943 }
12944
12945 pub fn convert_to_rot47(
12946 &mut self,
12947 _: &ConvertToRot47,
12948 window: &mut Window,
12949 cx: &mut Context<Self>,
12950 ) {
12951 self.manipulate_text(window, cx, |text| {
12952 text.chars()
12953 .map(|c| {
12954 let code_point = c as u32;
12955 if code_point >= 33 && code_point <= 126 {
12956 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12957 }
12958 c
12959 })
12960 .collect()
12961 })
12962 }
12963
12964 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12965 where
12966 Fn: FnMut(&str) -> String,
12967 {
12968 let buffer = self.buffer.read(cx).snapshot(cx);
12969
12970 let mut new_selections = Vec::new();
12971 let mut edits = Vec::new();
12972 let mut selection_adjustment = 0isize;
12973
12974 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12975 let selection_is_empty = selection.is_empty();
12976
12977 let (start, end) = if selection_is_empty {
12978 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12979 (word_range.start, word_range.end)
12980 } else {
12981 (
12982 buffer.point_to_offset(selection.start),
12983 buffer.point_to_offset(selection.end),
12984 )
12985 };
12986
12987 let text = buffer.text_for_range(start..end).collect::<String>();
12988 let old_length = text.len() as isize;
12989 let text = callback(&text);
12990
12991 new_selections.push(Selection {
12992 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12993 end: MultiBufferOffset(
12994 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12995 ),
12996 goal: SelectionGoal::None,
12997 id: selection.id,
12998 reversed: selection.reversed,
12999 });
13000
13001 selection_adjustment += old_length - text.len() as isize;
13002
13003 edits.push((start..end, text));
13004 }
13005
13006 self.transact(window, cx, |this, window, cx| {
13007 this.buffer.update(cx, |buffer, cx| {
13008 buffer.edit(edits, None, cx);
13009 });
13010
13011 this.change_selections(Default::default(), window, cx, |s| {
13012 s.select(new_selections);
13013 });
13014
13015 this.request_autoscroll(Autoscroll::fit(), cx);
13016 });
13017 }
13018
13019 pub fn move_selection_on_drop(
13020 &mut self,
13021 selection: &Selection<Anchor>,
13022 target: DisplayPoint,
13023 is_cut: bool,
13024 window: &mut Window,
13025 cx: &mut Context<Self>,
13026 ) {
13027 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13028 let buffer = display_map.buffer_snapshot();
13029 let mut edits = Vec::new();
13030 let insert_point = display_map
13031 .clip_point(target, Bias::Left)
13032 .to_point(&display_map);
13033 let text = buffer
13034 .text_for_range(selection.start..selection.end)
13035 .collect::<String>();
13036 if is_cut {
13037 edits.push(((selection.start..selection.end), String::new()));
13038 }
13039 let insert_anchor = buffer.anchor_before(insert_point);
13040 edits.push(((insert_anchor..insert_anchor), text));
13041 let last_edit_start = insert_anchor.bias_left(buffer);
13042 let last_edit_end = insert_anchor.bias_right(buffer);
13043 self.transact(window, cx, |this, window, cx| {
13044 this.buffer.update(cx, |buffer, cx| {
13045 buffer.edit(edits, None, cx);
13046 });
13047 this.change_selections(Default::default(), window, cx, |s| {
13048 s.select_anchor_ranges([last_edit_start..last_edit_end]);
13049 });
13050 });
13051 }
13052
13053 pub fn clear_selection_drag_state(&mut self) {
13054 self.selection_drag_state = SelectionDragState::None;
13055 }
13056
13057 pub fn duplicate(
13058 &mut self,
13059 upwards: bool,
13060 whole_lines: bool,
13061 window: &mut Window,
13062 cx: &mut Context<Self>,
13063 ) {
13064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13065
13066 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13067 let buffer = display_map.buffer_snapshot();
13068 let selections = self.selections.all::<Point>(&display_map);
13069
13070 let mut edits = Vec::new();
13071 let mut selections_iter = selections.iter().peekable();
13072 while let Some(selection) = selections_iter.next() {
13073 let mut rows = selection.spanned_rows(false, &display_map);
13074 // duplicate line-wise
13075 if whole_lines || selection.start == selection.end {
13076 // Avoid duplicating the same lines twice.
13077 while let Some(next_selection) = selections_iter.peek() {
13078 let next_rows = next_selection.spanned_rows(false, &display_map);
13079 if next_rows.start < rows.end {
13080 rows.end = next_rows.end;
13081 selections_iter.next().unwrap();
13082 } else {
13083 break;
13084 }
13085 }
13086
13087 // Copy the text from the selected row region and splice it either at the start
13088 // or end of the region.
13089 let start = Point::new(rows.start.0, 0);
13090 let end = Point::new(
13091 rows.end.previous_row().0,
13092 buffer.line_len(rows.end.previous_row()),
13093 );
13094
13095 let mut text = buffer.text_for_range(start..end).collect::<String>();
13096
13097 let insert_location = if upwards {
13098 // When duplicating upward, we need to insert before the current line.
13099 // If we're on the last line and it doesn't end with a newline,
13100 // we need to add a newline before the duplicated content.
13101 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
13102 && buffer.max_point().column > 0
13103 && !text.ends_with('\n');
13104
13105 if needs_leading_newline {
13106 text.insert(0, '\n');
13107 end
13108 } else {
13109 text.push('\n');
13110 Point::new(rows.start.0, 0)
13111 }
13112 } else {
13113 text.push('\n');
13114 start
13115 };
13116 edits.push((insert_location..insert_location, text));
13117 } else {
13118 // duplicate character-wise
13119 let start = selection.start;
13120 let end = selection.end;
13121 let text = buffer.text_for_range(start..end).collect::<String>();
13122 edits.push((selection.end..selection.end, text));
13123 }
13124 }
13125
13126 self.transact(window, cx, |this, window, cx| {
13127 this.buffer.update(cx, |buffer, cx| {
13128 buffer.edit(edits, None, cx);
13129 });
13130
13131 // When duplicating upward with whole lines, move the cursor to the duplicated line
13132 if upwards && whole_lines {
13133 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
13134
13135 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13136 let mut new_ranges = Vec::new();
13137 let selections = s.all::<Point>(&display_map);
13138 let mut selections_iter = selections.iter().peekable();
13139
13140 while let Some(first_selection) = selections_iter.next() {
13141 // Group contiguous selections together to find the total row span
13142 let mut group_selections = vec![first_selection];
13143 let mut rows = first_selection.spanned_rows(false, &display_map);
13144
13145 while let Some(next_selection) = selections_iter.peek() {
13146 let next_rows = next_selection.spanned_rows(false, &display_map);
13147 if next_rows.start < rows.end {
13148 rows.end = next_rows.end;
13149 group_selections.push(selections_iter.next().unwrap());
13150 } else {
13151 break;
13152 }
13153 }
13154
13155 let row_count = rows.end.0 - rows.start.0;
13156
13157 // Move all selections in this group up by the total number of duplicated rows
13158 for selection in group_selections {
13159 let new_start = Point::new(
13160 selection.start.row.saturating_sub(row_count),
13161 selection.start.column,
13162 );
13163
13164 let new_end = Point::new(
13165 selection.end.row.saturating_sub(row_count),
13166 selection.end.column,
13167 );
13168
13169 new_ranges.push(new_start..new_end);
13170 }
13171 }
13172
13173 s.select_ranges(new_ranges);
13174 });
13175 }
13176
13177 this.request_autoscroll(Autoscroll::fit(), cx);
13178 });
13179 }
13180
13181 pub fn duplicate_line_up(
13182 &mut self,
13183 _: &DuplicateLineUp,
13184 window: &mut Window,
13185 cx: &mut Context<Self>,
13186 ) {
13187 self.duplicate(true, true, window, cx);
13188 }
13189
13190 pub fn duplicate_line_down(
13191 &mut self,
13192 _: &DuplicateLineDown,
13193 window: &mut Window,
13194 cx: &mut Context<Self>,
13195 ) {
13196 self.duplicate(false, true, window, cx);
13197 }
13198
13199 pub fn duplicate_selection(
13200 &mut self,
13201 _: &DuplicateSelection,
13202 window: &mut Window,
13203 cx: &mut Context<Self>,
13204 ) {
13205 self.duplicate(false, false, window, cx);
13206 }
13207
13208 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13209 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13210 if self.mode.is_single_line() {
13211 cx.propagate();
13212 return;
13213 }
13214
13215 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13216 let buffer = self.buffer.read(cx).snapshot(cx);
13217
13218 let mut edits = Vec::new();
13219 let mut unfold_ranges = Vec::new();
13220 let mut refold_creases = Vec::new();
13221
13222 let selections = self.selections.all::<Point>(&display_map);
13223 let mut selections = selections.iter().peekable();
13224 let mut contiguous_row_selections = Vec::new();
13225 let mut new_selections = Vec::new();
13226
13227 while let Some(selection) = selections.next() {
13228 // Find all the selections that span a contiguous row range
13229 let (start_row, end_row) = consume_contiguous_rows(
13230 &mut contiguous_row_selections,
13231 selection,
13232 &display_map,
13233 &mut selections,
13234 );
13235
13236 // Move the text spanned by the row range to be before the line preceding the row range
13237 if start_row.0 > 0 {
13238 let range_to_move = Point::new(
13239 start_row.previous_row().0,
13240 buffer.line_len(start_row.previous_row()),
13241 )
13242 ..Point::new(
13243 end_row.previous_row().0,
13244 buffer.line_len(end_row.previous_row()),
13245 );
13246 let insertion_point = display_map
13247 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13248 .0;
13249
13250 // Don't move lines across excerpts
13251 if buffer
13252 .excerpt_containing(insertion_point..range_to_move.end)
13253 .is_some()
13254 {
13255 let text = buffer
13256 .text_for_range(range_to_move.clone())
13257 .flat_map(|s| s.chars())
13258 .skip(1)
13259 .chain(['\n'])
13260 .collect::<String>();
13261
13262 edits.push((
13263 buffer.anchor_after(range_to_move.start)
13264 ..buffer.anchor_before(range_to_move.end),
13265 String::new(),
13266 ));
13267 let insertion_anchor = buffer.anchor_after(insertion_point);
13268 edits.push((insertion_anchor..insertion_anchor, text));
13269
13270 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13271
13272 // Move selections up
13273 new_selections.extend(contiguous_row_selections.drain(..).map(
13274 |mut selection| {
13275 selection.start.row -= row_delta;
13276 selection.end.row -= row_delta;
13277 selection
13278 },
13279 ));
13280
13281 // Move folds up
13282 unfold_ranges.push(range_to_move.clone());
13283 for fold in display_map.folds_in_range(
13284 buffer.anchor_before(range_to_move.start)
13285 ..buffer.anchor_after(range_to_move.end),
13286 ) {
13287 let mut start = fold.range.start.to_point(&buffer);
13288 let mut end = fold.range.end.to_point(&buffer);
13289 start.row -= row_delta;
13290 end.row -= row_delta;
13291 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13292 }
13293 }
13294 }
13295
13296 // If we didn't move line(s), preserve the existing selections
13297 new_selections.append(&mut contiguous_row_selections);
13298 }
13299
13300 self.transact(window, cx, |this, window, cx| {
13301 this.unfold_ranges(&unfold_ranges, true, true, cx);
13302 this.buffer.update(cx, |buffer, cx| {
13303 for (range, text) in edits {
13304 buffer.edit([(range, text)], None, cx);
13305 }
13306 });
13307 this.fold_creases(refold_creases, true, window, cx);
13308 this.change_selections(Default::default(), window, cx, |s| {
13309 s.select(new_selections);
13310 })
13311 });
13312 }
13313
13314 pub fn move_line_down(
13315 &mut self,
13316 _: &MoveLineDown,
13317 window: &mut Window,
13318 cx: &mut Context<Self>,
13319 ) {
13320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13321 if self.mode.is_single_line() {
13322 cx.propagate();
13323 return;
13324 }
13325
13326 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13327 let buffer = self.buffer.read(cx).snapshot(cx);
13328
13329 let mut edits = Vec::new();
13330 let mut unfold_ranges = Vec::new();
13331 let mut refold_creases = Vec::new();
13332
13333 let selections = self.selections.all::<Point>(&display_map);
13334 let mut selections = selections.iter().peekable();
13335 let mut contiguous_row_selections = Vec::new();
13336 let mut new_selections = Vec::new();
13337
13338 while let Some(selection) = selections.next() {
13339 // Find all the selections that span a contiguous row range
13340 let (start_row, end_row) = consume_contiguous_rows(
13341 &mut contiguous_row_selections,
13342 selection,
13343 &display_map,
13344 &mut selections,
13345 );
13346
13347 // Move the text spanned by the row range to be after the last line of the row range
13348 if end_row.0 <= buffer.max_point().row {
13349 let range_to_move =
13350 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13351 let insertion_point = display_map
13352 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13353 .0;
13354
13355 // Don't move lines across excerpt boundaries
13356 if buffer
13357 .excerpt_containing(range_to_move.start..insertion_point)
13358 .is_some()
13359 {
13360 let mut text = String::from("\n");
13361 text.extend(buffer.text_for_range(range_to_move.clone()));
13362 text.pop(); // Drop trailing newline
13363 edits.push((
13364 buffer.anchor_after(range_to_move.start)
13365 ..buffer.anchor_before(range_to_move.end),
13366 String::new(),
13367 ));
13368 let insertion_anchor = buffer.anchor_after(insertion_point);
13369 edits.push((insertion_anchor..insertion_anchor, text));
13370
13371 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13372
13373 // Move selections down
13374 new_selections.extend(contiguous_row_selections.drain(..).map(
13375 |mut selection| {
13376 selection.start.row += row_delta;
13377 selection.end.row += row_delta;
13378 selection
13379 },
13380 ));
13381
13382 // Move folds down
13383 unfold_ranges.push(range_to_move.clone());
13384 for fold in display_map.folds_in_range(
13385 buffer.anchor_before(range_to_move.start)
13386 ..buffer.anchor_after(range_to_move.end),
13387 ) {
13388 let mut start = fold.range.start.to_point(&buffer);
13389 let mut end = fold.range.end.to_point(&buffer);
13390 start.row += row_delta;
13391 end.row += row_delta;
13392 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13393 }
13394 }
13395 }
13396
13397 // If we didn't move line(s), preserve the existing selections
13398 new_selections.append(&mut contiguous_row_selections);
13399 }
13400
13401 self.transact(window, cx, |this, window, cx| {
13402 this.unfold_ranges(&unfold_ranges, true, true, cx);
13403 this.buffer.update(cx, |buffer, cx| {
13404 for (range, text) in edits {
13405 buffer.edit([(range, text)], None, cx);
13406 }
13407 });
13408 this.fold_creases(refold_creases, true, window, cx);
13409 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13410 });
13411 }
13412
13413 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13414 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13415 let text_layout_details = &self.text_layout_details(window, cx);
13416 self.transact(window, cx, |this, window, cx| {
13417 let edits = this.change_selections(Default::default(), window, cx, |s| {
13418 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13419 s.move_with(&mut |display_map, selection| {
13420 if !selection.is_empty() {
13421 return;
13422 }
13423
13424 let mut head = selection.head();
13425 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13426 if head.column() == display_map.line_len(head.row()) {
13427 transpose_offset = display_map
13428 .buffer_snapshot()
13429 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13430 }
13431
13432 if transpose_offset == MultiBufferOffset(0) {
13433 return;
13434 }
13435
13436 *head.column_mut() += 1;
13437 head = display_map.clip_point(head, Bias::Right);
13438 let goal = SelectionGoal::HorizontalPosition(
13439 display_map
13440 .x_for_display_point(head, text_layout_details)
13441 .into(),
13442 );
13443 selection.collapse_to(head, goal);
13444
13445 let transpose_start = display_map
13446 .buffer_snapshot()
13447 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13448 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13449 let transpose_end = display_map
13450 .buffer_snapshot()
13451 .clip_offset(transpose_offset + 1usize, Bias::Right);
13452 if let Some(ch) = display_map
13453 .buffer_snapshot()
13454 .chars_at(transpose_start)
13455 .next()
13456 {
13457 edits.push((transpose_start..transpose_offset, String::new()));
13458 edits.push((transpose_end..transpose_end, ch.to_string()));
13459 }
13460 }
13461 });
13462 edits
13463 });
13464 this.buffer
13465 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13466 let selections = this
13467 .selections
13468 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13469 this.change_selections(Default::default(), window, cx, |s| {
13470 s.select(selections);
13471 });
13472 });
13473 }
13474
13475 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13476 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13477 if self.mode.is_single_line() {
13478 cx.propagate();
13479 return;
13480 }
13481
13482 self.rewrap_impl(RewrapOptions::default(), cx)
13483 }
13484
13485 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13486 let buffer = self.buffer.read(cx).snapshot(cx);
13487 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13488
13489 #[derive(Clone, Debug, PartialEq)]
13490 enum CommentFormat {
13491 /// single line comment, with prefix for line
13492 Line(String),
13493 /// single line within a block comment, with prefix for line
13494 BlockLine(String),
13495 /// a single line of a block comment that includes the initial delimiter
13496 BlockCommentWithStart(BlockCommentConfig),
13497 /// a single line of a block comment that includes the ending delimiter
13498 BlockCommentWithEnd(BlockCommentConfig),
13499 }
13500
13501 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13502 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13503 let language_settings = buffer.language_settings_at(selection.head(), cx);
13504 let language_scope = buffer.language_scope_at(selection.head());
13505
13506 let indent_and_prefix_for_row =
13507 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13508 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13509 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13510 &language_scope
13511 {
13512 let indent_end = Point::new(row, indent.len);
13513 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13514 let line_text_after_indent = buffer
13515 .text_for_range(indent_end..line_end)
13516 .collect::<String>();
13517
13518 let is_within_comment_override = buffer
13519 .language_scope_at(indent_end)
13520 .is_some_and(|scope| scope.override_name() == Some("comment"));
13521 let comment_delimiters = if is_within_comment_override {
13522 // we are within a comment syntax node, but we don't
13523 // yet know what kind of comment: block, doc or line
13524 match (
13525 language_scope.documentation_comment(),
13526 language_scope.block_comment(),
13527 ) {
13528 (Some(config), _) | (_, Some(config))
13529 if buffer.contains_str_at(indent_end, &config.start) =>
13530 {
13531 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13532 }
13533 (Some(config), _) | (_, Some(config))
13534 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13535 {
13536 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13537 }
13538 (Some(config), _) | (_, Some(config))
13539 if buffer.contains_str_at(indent_end, &config.prefix) =>
13540 {
13541 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13542 }
13543 (_, _) => language_scope
13544 .line_comment_prefixes()
13545 .iter()
13546 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13547 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13548 }
13549 } else {
13550 // we not in an overridden comment node, but we may
13551 // be within a non-overridden line comment node
13552 language_scope
13553 .line_comment_prefixes()
13554 .iter()
13555 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13556 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13557 };
13558
13559 let rewrap_prefix = language_scope
13560 .rewrap_prefixes()
13561 .iter()
13562 .find_map(|prefix_regex| {
13563 prefix_regex.find(&line_text_after_indent).map(|mat| {
13564 if mat.start() == 0 {
13565 Some(mat.as_str().to_string())
13566 } else {
13567 None
13568 }
13569 })
13570 })
13571 .flatten();
13572 (comment_delimiters, rewrap_prefix)
13573 } else {
13574 (None, None)
13575 };
13576 (indent, comment_prefix, rewrap_prefix)
13577 };
13578
13579 let mut start_row = selection.start.row;
13580 let mut end_row = selection.end.row;
13581
13582 if selection.is_empty() {
13583 let cursor_row = selection.start.row;
13584
13585 let (mut indent_size, comment_prefix, _) = indent_and_prefix_for_row(cursor_row);
13586 let line_prefix = match &comment_prefix {
13587 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13588 Some(prefix.as_str())
13589 }
13590 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13591 prefix, ..
13592 })) => Some(prefix.as_ref()),
13593 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13594 start: _,
13595 end: _,
13596 prefix,
13597 tab_size,
13598 })) => {
13599 indent_size.len += tab_size;
13600 Some(prefix.as_ref())
13601 }
13602 None => None,
13603 };
13604 let indent_prefix = indent_size.chars().collect::<String>();
13605 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13606
13607 'expand_upwards: while start_row > 0 {
13608 let prev_row = start_row - 1;
13609 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13610 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13611 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13612 {
13613 start_row = prev_row;
13614 } else {
13615 break 'expand_upwards;
13616 }
13617 }
13618
13619 'expand_downwards: while end_row < buffer.max_point().row {
13620 let next_row = end_row + 1;
13621 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13622 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13623 && !buffer.is_line_blank(MultiBufferRow(next_row))
13624 {
13625 end_row = next_row;
13626 } else {
13627 break 'expand_downwards;
13628 }
13629 }
13630 }
13631
13632 let mut non_blank_rows_iter = (start_row..=end_row)
13633 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13634 .peekable();
13635
13636 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13637 row
13638 } else {
13639 return Vec::new();
13640 };
13641
13642 let mut ranges = Vec::new();
13643
13644 let mut current_range_start = first_row;
13645 let mut prev_row = first_row;
13646 let (
13647 mut current_range_indent,
13648 mut current_range_comment_delimiters,
13649 mut current_range_rewrap_prefix,
13650 ) = indent_and_prefix_for_row(first_row);
13651
13652 for row in non_blank_rows_iter.skip(1) {
13653 let has_paragraph_break = row > prev_row + 1;
13654
13655 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13656 indent_and_prefix_for_row(row);
13657
13658 let has_indent_change = row_indent != current_range_indent;
13659 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13660
13661 let has_boundary_change = has_comment_change
13662 || row_rewrap_prefix.is_some()
13663 || (has_indent_change && current_range_comment_delimiters.is_some());
13664
13665 if has_paragraph_break || has_boundary_change {
13666 ranges.push((
13667 language_settings.clone(),
13668 Point::new(current_range_start, 0)
13669 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13670 current_range_indent,
13671 current_range_comment_delimiters.clone(),
13672 current_range_rewrap_prefix.clone(),
13673 ));
13674 current_range_start = row;
13675 current_range_indent = row_indent;
13676 current_range_comment_delimiters = row_comment_delimiters;
13677 current_range_rewrap_prefix = row_rewrap_prefix;
13678 }
13679 prev_row = row;
13680 }
13681
13682 ranges.push((
13683 language_settings.clone(),
13684 Point::new(current_range_start, 0)
13685 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13686 current_range_indent,
13687 current_range_comment_delimiters,
13688 current_range_rewrap_prefix,
13689 ));
13690
13691 ranges
13692 });
13693
13694 let mut edits = Vec::new();
13695 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13696
13697 for (language_settings, wrap_range, mut indent_size, comment_prefix, rewrap_prefix) in
13698 wrap_ranges
13699 {
13700 let start_row = wrap_range.start.row;
13701 let end_row = wrap_range.end.row;
13702
13703 // Skip selections that overlap with a range that has already been rewrapped.
13704 let selection_range = start_row..end_row;
13705 if rewrapped_row_ranges
13706 .iter()
13707 .any(|range| range.overlaps(&selection_range))
13708 {
13709 continue;
13710 }
13711
13712 let tab_size = language_settings.tab_size;
13713
13714 let (line_prefix, inside_comment) = match &comment_prefix {
13715 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13716 (Some(prefix.as_str()), true)
13717 }
13718 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13719 (Some(prefix.as_ref()), true)
13720 }
13721 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13722 start: _,
13723 end: _,
13724 prefix,
13725 tab_size,
13726 })) => {
13727 indent_size.len += tab_size;
13728 (Some(prefix.as_ref()), true)
13729 }
13730 None => (None, false),
13731 };
13732 let indent_prefix = indent_size.chars().collect::<String>();
13733 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13734
13735 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13736 RewrapBehavior::InComments => inside_comment,
13737 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13738 RewrapBehavior::Anywhere => true,
13739 };
13740
13741 let should_rewrap = options.override_language_settings
13742 || allow_rewrap_based_on_language
13743 || self.hard_wrap.is_some();
13744 if !should_rewrap {
13745 continue;
13746 }
13747
13748 let start = Point::new(start_row, 0);
13749 let start_offset = ToOffset::to_offset(&start, &buffer);
13750 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13751 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13752 let mut first_line_delimiter = None;
13753 let mut last_line_delimiter = None;
13754 let Some(lines_without_prefixes) = selection_text
13755 .lines()
13756 .enumerate()
13757 .map(|(ix, line)| {
13758 let line_trimmed = line.trim_start();
13759 if rewrap_prefix.is_some() && ix > 0 {
13760 Ok(line_trimmed)
13761 } else if let Some(
13762 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13763 start,
13764 prefix,
13765 end,
13766 tab_size,
13767 })
13768 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13769 start,
13770 prefix,
13771 end,
13772 tab_size,
13773 }),
13774 ) = &comment_prefix
13775 {
13776 let line_trimmed = line_trimmed
13777 .strip_prefix(start.as_ref())
13778 .map(|s| {
13779 let mut indent_size = indent_size;
13780 indent_size.len -= tab_size;
13781 let indent_prefix: String = indent_size.chars().collect();
13782 first_line_delimiter = Some((indent_prefix, start));
13783 s.trim_start()
13784 })
13785 .unwrap_or(line_trimmed);
13786 let line_trimmed = line_trimmed
13787 .strip_suffix(end.as_ref())
13788 .map(|s| {
13789 last_line_delimiter = Some(end);
13790 s.trim_end()
13791 })
13792 .unwrap_or(line_trimmed);
13793 let line_trimmed = line_trimmed
13794 .strip_prefix(prefix.as_ref())
13795 .unwrap_or(line_trimmed);
13796 Ok(line_trimmed)
13797 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13798 line_trimmed.strip_prefix(prefix).with_context(|| {
13799 format!("line did not start with prefix {prefix:?}: {line:?}")
13800 })
13801 } else {
13802 line_trimmed
13803 .strip_prefix(&line_prefix.trim_start())
13804 .with_context(|| {
13805 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13806 })
13807 }
13808 })
13809 .collect::<Result<Vec<_>, _>>()
13810 .log_err()
13811 else {
13812 continue;
13813 };
13814
13815 let wrap_column = options.line_length.or(self.hard_wrap).unwrap_or_else(|| {
13816 buffer
13817 .language_settings_at(Point::new(start_row, 0), cx)
13818 .preferred_line_length as usize
13819 });
13820
13821 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13822 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13823 } else {
13824 line_prefix.clone()
13825 };
13826
13827 let wrapped_text = {
13828 let mut wrapped_text = wrap_with_prefix(
13829 line_prefix,
13830 subsequent_lines_prefix,
13831 lines_without_prefixes.join("\n"),
13832 wrap_column,
13833 tab_size,
13834 options.preserve_existing_whitespace,
13835 );
13836
13837 if let Some((indent, delimiter)) = first_line_delimiter {
13838 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13839 }
13840 if let Some(last_line) = last_line_delimiter {
13841 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13842 }
13843
13844 wrapped_text
13845 };
13846
13847 // TODO: should always use char-based diff while still supporting cursor behavior that
13848 // matches vim.
13849 let mut diff_options = DiffOptions::default();
13850 if options.override_language_settings {
13851 diff_options.max_word_diff_len = 0;
13852 diff_options.max_word_diff_line_count = 0;
13853 } else {
13854 diff_options.max_word_diff_len = usize::MAX;
13855 diff_options.max_word_diff_line_count = usize::MAX;
13856 }
13857
13858 for (old_range, new_text) in
13859 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13860 {
13861 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13862 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13863 edits.push((edit_start..edit_end, new_text));
13864 }
13865
13866 rewrapped_row_ranges.push(start_row..=end_row);
13867 }
13868
13869 self.buffer
13870 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13871 }
13872
13873 pub fn cut_common(
13874 &mut self,
13875 cut_no_selection_line: bool,
13876 window: &mut Window,
13877 cx: &mut Context<Self>,
13878 ) -> ClipboardItem {
13879 let mut text = String::new();
13880 let buffer = self.buffer.read(cx).snapshot(cx);
13881 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13882 let mut clipboard_selections = Vec::with_capacity(selections.len());
13883 {
13884 let max_point = buffer.max_point();
13885 let mut is_first = true;
13886 let mut prev_selection_was_entire_line = false;
13887 for selection in &mut selections {
13888 let is_entire_line =
13889 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13890 if is_entire_line {
13891 selection.start = Point::new(selection.start.row, 0);
13892 if !selection.is_empty() && selection.end.column == 0 {
13893 selection.end = cmp::min(max_point, selection.end);
13894 } else {
13895 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13896 }
13897 selection.goal = SelectionGoal::None;
13898 }
13899 if is_first {
13900 is_first = false;
13901 } else if !prev_selection_was_entire_line {
13902 text += "\n";
13903 }
13904 prev_selection_was_entire_line = is_entire_line;
13905 let mut len = 0;
13906 for chunk in buffer.text_for_range(selection.start..selection.end) {
13907 text.push_str(chunk);
13908 len += chunk.len();
13909 }
13910
13911 clipboard_selections.push(ClipboardSelection::for_buffer(
13912 len,
13913 is_entire_line,
13914 selection.range(),
13915 &buffer,
13916 self.project.as_ref(),
13917 cx,
13918 ));
13919 }
13920 }
13921
13922 self.transact(window, cx, |this, window, cx| {
13923 this.change_selections(Default::default(), window, cx, |s| {
13924 s.select(selections);
13925 });
13926 this.insert("", window, cx);
13927 });
13928 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13929 }
13930
13931 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13933 let item = self.cut_common(true, window, cx);
13934 cx.write_to_clipboard(item);
13935 }
13936
13937 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13938 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13939 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13940 s.move_with(&mut |snapshot, sel| {
13941 if sel.is_empty() {
13942 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13943 }
13944 if sel.is_empty() {
13945 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13946 }
13947 });
13948 });
13949 let item = self.cut_common(false, window, cx);
13950 cx.set_global(KillRing(item))
13951 }
13952
13953 pub fn kill_ring_yank(
13954 &mut self,
13955 _: &KillRingYank,
13956 window: &mut Window,
13957 cx: &mut Context<Self>,
13958 ) {
13959 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13960 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13961 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13962 (kill_ring.text().to_string(), kill_ring.metadata_json())
13963 } else {
13964 return;
13965 }
13966 } else {
13967 return;
13968 };
13969 self.do_paste(&text, metadata, false, window, cx);
13970 }
13971
13972 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13973 self.do_copy(true, cx);
13974 }
13975
13976 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13977 self.do_copy(false, cx);
13978 }
13979
13980 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13981 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13982 let buffer = self.buffer.read(cx).read(cx);
13983 let mut text = String::new();
13984 let mut clipboard_selections = Vec::with_capacity(selections.len());
13985
13986 let max_point = buffer.max_point();
13987 let mut is_first = true;
13988 for selection in &selections {
13989 let mut start = selection.start;
13990 let mut end = selection.end;
13991 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13992 let mut add_trailing_newline = false;
13993 if is_entire_line {
13994 start = Point::new(start.row, 0);
13995 let next_line_start = Point::new(end.row + 1, 0);
13996 if next_line_start <= max_point {
13997 end = next_line_start;
13998 } else {
13999 // We're on the last line without a trailing newline.
14000 // Copy to the end of the line and add a newline afterwards.
14001 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
14002 add_trailing_newline = true;
14003 }
14004 }
14005
14006 let mut trimmed_selections = Vec::new();
14007 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
14008 let row = MultiBufferRow(start.row);
14009 let first_indent = buffer.indent_size_for_line(row);
14010 if first_indent.len == 0 || start.column > first_indent.len {
14011 trimmed_selections.push(start..end);
14012 } else {
14013 trimmed_selections.push(
14014 Point::new(row.0, first_indent.len)
14015 ..Point::new(row.0, buffer.line_len(row)),
14016 );
14017 for row in start.row + 1..=end.row {
14018 let mut line_len = buffer.line_len(MultiBufferRow(row));
14019 if row == end.row {
14020 line_len = end.column;
14021 }
14022 if line_len == 0 {
14023 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
14024 continue;
14025 }
14026 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
14027 if row_indent_size.len >= first_indent.len {
14028 trimmed_selections
14029 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
14030 } else {
14031 trimmed_selections.clear();
14032 trimmed_selections.push(start..end);
14033 break;
14034 }
14035 }
14036 }
14037 } else {
14038 trimmed_selections.push(start..end);
14039 }
14040
14041 let is_multiline_trim = trimmed_selections.len() > 1;
14042 let mut selection_len: usize = 0;
14043 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
14044
14045 for trimmed_range in trimmed_selections {
14046 if is_first {
14047 is_first = false;
14048 } else if is_multiline_trim || !prev_selection_was_entire_line {
14049 text.push('\n');
14050 if is_multiline_trim {
14051 selection_len += 1;
14052 }
14053 }
14054 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
14055 text.push_str(chunk);
14056 selection_len += chunk.len();
14057 }
14058 if add_trailing_newline {
14059 text.push('\n');
14060 selection_len += 1;
14061 }
14062 }
14063
14064 clipboard_selections.push(ClipboardSelection::for_buffer(
14065 selection_len,
14066 is_entire_line,
14067 start..end,
14068 &buffer,
14069 self.project.as_ref(),
14070 cx,
14071 ));
14072 }
14073
14074 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
14075 text,
14076 clipboard_selections,
14077 ));
14078 }
14079
14080 pub fn do_paste(
14081 &mut self,
14082 text: &String,
14083 clipboard_selections: Option<Vec<ClipboardSelection>>,
14084 handle_entire_lines: bool,
14085 window: &mut Window,
14086 cx: &mut Context<Self>,
14087 ) {
14088 if self.read_only(cx) {
14089 return;
14090 }
14091
14092 self.finalize_last_transaction(cx);
14093
14094 let clipboard_text = Cow::Borrowed(text.as_str());
14095
14096 self.transact(window, cx, |this, window, cx| {
14097 let had_active_edit_prediction = this.has_active_edit_prediction();
14098 let display_map = this.display_snapshot(cx);
14099 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
14100 let cursor_offset = this
14101 .selections
14102 .last::<MultiBufferOffset>(&display_map)
14103 .head();
14104
14105 if let Some(mut clipboard_selections) = clipboard_selections {
14106 let all_selections_were_entire_line =
14107 clipboard_selections.iter().all(|s| s.is_entire_line);
14108 let first_selection_indent_column =
14109 clipboard_selections.first().map(|s| s.first_line_indent);
14110 if clipboard_selections.len() != old_selections.len() {
14111 clipboard_selections.drain(..);
14112 }
14113 let mut auto_indent_on_paste = true;
14114
14115 this.buffer.update(cx, |buffer, cx| {
14116 let snapshot = buffer.read(cx);
14117 auto_indent_on_paste = snapshot
14118 .language_settings_at(cursor_offset, cx)
14119 .auto_indent_on_paste;
14120
14121 let mut start_offset = 0;
14122 let mut edits = Vec::new();
14123 let mut original_indent_columns = Vec::new();
14124 for (ix, selection) in old_selections.iter().enumerate() {
14125 let to_insert;
14126 let entire_line;
14127 let original_indent_column;
14128 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
14129 let end_offset = start_offset + clipboard_selection.len;
14130 to_insert = &clipboard_text[start_offset..end_offset];
14131 entire_line = clipboard_selection.is_entire_line;
14132 start_offset = if entire_line {
14133 end_offset
14134 } else {
14135 end_offset + 1
14136 };
14137 original_indent_column = Some(clipboard_selection.first_line_indent);
14138 } else {
14139 to_insert = &*clipboard_text;
14140 entire_line = all_selections_were_entire_line;
14141 original_indent_column = first_selection_indent_column
14142 }
14143
14144 let (range, to_insert) =
14145 if selection.is_empty() && handle_entire_lines && entire_line {
14146 // If the corresponding selection was empty when this slice of the
14147 // clipboard text was written, then the entire line containing the
14148 // selection was copied. If this selection is also currently empty,
14149 // then paste the line before the current line of the buffer.
14150 let column = selection.start.to_point(&snapshot).column as usize;
14151 let line_start = selection.start - column;
14152 (line_start..line_start, Cow::Borrowed(to_insert))
14153 } else {
14154 let language = snapshot.language_at(selection.head());
14155 let range = selection.range();
14156 if let Some(language) = language
14157 && language.name() == "Markdown"
14158 {
14159 edit_for_markdown_paste(
14160 &snapshot,
14161 range,
14162 to_insert,
14163 url::Url::parse(to_insert).ok(),
14164 )
14165 } else {
14166 (range, Cow::Borrowed(to_insert))
14167 }
14168 };
14169
14170 edits.push((range, to_insert));
14171 original_indent_columns.push(original_indent_column);
14172 }
14173 drop(snapshot);
14174
14175 buffer.edit(
14176 edits,
14177 if auto_indent_on_paste {
14178 Some(AutoindentMode::Block {
14179 original_indent_columns,
14180 })
14181 } else {
14182 None
14183 },
14184 cx,
14185 );
14186 });
14187
14188 let selections = this
14189 .selections
14190 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
14191 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14192 } else {
14193 let url = url::Url::parse(&clipboard_text).ok();
14194
14195 let auto_indent_mode = if !clipboard_text.is_empty() {
14196 Some(AutoindentMode::Block {
14197 original_indent_columns: Vec::new(),
14198 })
14199 } else {
14200 None
14201 };
14202
14203 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
14204 let snapshot = buffer.snapshot(cx);
14205
14206 let anchors = old_selections
14207 .iter()
14208 .map(|s| {
14209 let anchor = snapshot.anchor_after(s.head());
14210 s.map(|_| anchor)
14211 })
14212 .collect::<Vec<_>>();
14213
14214 let mut edits = Vec::new();
14215
14216 // When pasting text without metadata (e.g. copied from an
14217 // external editor using multiple cursors) and the number of
14218 // lines matches the number of selections, distribute one
14219 // line per cursor instead of pasting the whole text at each.
14220 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14221 let distribute_lines =
14222 old_selections.len() > 1 && lines.len() == old_selections.len();
14223
14224 for (ix, selection) in old_selections.iter().enumerate() {
14225 let language = snapshot.language_at(selection.head());
14226 let range = selection.range();
14227
14228 let text_for_cursor: &str = if distribute_lines {
14229 lines[ix]
14230 } else {
14231 &clipboard_text
14232 };
14233
14234 let (edit_range, edit_text) = if let Some(language) = language
14235 && language.name() == "Markdown"
14236 {
14237 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14238 } else {
14239 (range, Cow::Borrowed(text_for_cursor))
14240 };
14241
14242 edits.push((edit_range, edit_text));
14243 }
14244
14245 drop(snapshot);
14246 buffer.edit(edits, auto_indent_mode, cx);
14247
14248 anchors
14249 });
14250
14251 this.change_selections(Default::default(), window, cx, |s| {
14252 s.select_anchors(selection_anchors);
14253 });
14254 }
14255
14256 // 🤔 | .. | show_in_menu |
14257 // | .. | true true
14258 // | had_edit_prediction | false true
14259
14260 let trigger_in_words =
14261 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14262
14263 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14264 });
14265 }
14266
14267 pub fn diff_clipboard_with_selection(
14268 &mut self,
14269 _: &DiffClipboardWithSelection,
14270 window: &mut Window,
14271 cx: &mut Context<Self>,
14272 ) {
14273 let selections = self
14274 .selections
14275 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14276
14277 if selections.is_empty() {
14278 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14279 return;
14280 };
14281
14282 let clipboard_text = cx.read_from_clipboard().and_then(|item| {
14283 item.entries().iter().find_map(|entry| match entry {
14284 ClipboardEntry::String(text) => Some(text.text().to_string()),
14285 _ => None,
14286 })
14287 });
14288
14289 let Some(clipboard_text) = clipboard_text else {
14290 log::warn!("Clipboard doesn't contain text.");
14291 return;
14292 };
14293
14294 window.dispatch_action(
14295 Box::new(DiffClipboardWithSelectionData {
14296 clipboard_text,
14297 editor: cx.entity(),
14298 }),
14299 cx,
14300 );
14301 }
14302
14303 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14304 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14305 if let Some(item) = cx.read_from_clipboard() {
14306 let clipboard_string = item.entries().iter().find_map(|entry| match entry {
14307 ClipboardEntry::String(s) => Some(s),
14308 _ => None,
14309 });
14310 match clipboard_string {
14311 Some(clipboard_string) => self.do_paste(
14312 clipboard_string.text(),
14313 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14314 true,
14315 window,
14316 cx,
14317 ),
14318 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14319 }
14320 }
14321 }
14322
14323 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14324 if self.read_only(cx) {
14325 return;
14326 }
14327
14328 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14329
14330 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14331 if let Some((selections, _)) =
14332 self.selection_history.transaction(transaction_id).cloned()
14333 {
14334 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14335 s.select_anchors(selections.to_vec());
14336 });
14337 } else {
14338 log::error!(
14339 "No entry in selection_history found for undo. \
14340 This may correspond to a bug where undo does not update the selection. \
14341 If this is occurring, please add details to \
14342 https://github.com/zed-industries/zed/issues/22692"
14343 );
14344 }
14345 self.request_autoscroll(Autoscroll::fit(), cx);
14346 self.unmark_text(window, cx);
14347 self.refresh_edit_prediction(true, false, window, cx);
14348 cx.emit(EditorEvent::Edited { transaction_id });
14349 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14350 }
14351 }
14352
14353 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14354 if self.read_only(cx) {
14355 return;
14356 }
14357
14358 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14359
14360 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14361 if let Some((_, Some(selections))) =
14362 self.selection_history.transaction(transaction_id).cloned()
14363 {
14364 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14365 s.select_anchors(selections.to_vec());
14366 });
14367 } else {
14368 log::error!(
14369 "No entry in selection_history found for redo. \
14370 This may correspond to a bug where undo does not update the selection. \
14371 If this is occurring, please add details to \
14372 https://github.com/zed-industries/zed/issues/22692"
14373 );
14374 }
14375 self.request_autoscroll(Autoscroll::fit(), cx);
14376 self.unmark_text(window, cx);
14377 self.refresh_edit_prediction(true, false, window, cx);
14378 cx.emit(EditorEvent::Edited { transaction_id });
14379 }
14380 }
14381
14382 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14383 self.buffer
14384 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14385 }
14386
14387 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14388 self.buffer
14389 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14390 }
14391
14392 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14394 self.change_selections(Default::default(), window, cx, |s| {
14395 s.move_with(&mut |map, selection| {
14396 let cursor = if selection.is_empty() {
14397 movement::left(map, selection.start)
14398 } else {
14399 selection.start
14400 };
14401 selection.collapse_to(cursor, SelectionGoal::None);
14402 });
14403 })
14404 }
14405
14406 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14408 self.change_selections(Default::default(), window, cx, |s| {
14409 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14410 })
14411 }
14412
14413 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14414 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14415 self.change_selections(Default::default(), window, cx, |s| {
14416 s.move_with(&mut |map, selection| {
14417 let cursor = if selection.is_empty() {
14418 movement::right(map, selection.end)
14419 } else {
14420 selection.end
14421 };
14422 selection.collapse_to(cursor, SelectionGoal::None)
14423 });
14424 })
14425 }
14426
14427 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14429 self.change_selections(Default::default(), window, cx, |s| {
14430 s.move_heads_with(&mut |map, head, _| {
14431 (movement::right(map, head), SelectionGoal::None)
14432 });
14433 });
14434 }
14435
14436 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14437 if self.take_rename(true, window, cx).is_some() {
14438 return;
14439 }
14440
14441 if self.mode.is_single_line() {
14442 cx.propagate();
14443 return;
14444 }
14445
14446 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14447
14448 let text_layout_details = &self.text_layout_details(window, cx);
14449 let selection_count = self.selections.count();
14450 let first_selection = self.selections.first_anchor();
14451
14452 self.change_selections(Default::default(), window, cx, |s| {
14453 s.move_with(&mut |map, selection| {
14454 if !selection.is_empty() {
14455 selection.goal = SelectionGoal::None;
14456 }
14457 let (cursor, goal) = movement::up(
14458 map,
14459 selection.start,
14460 selection.goal,
14461 false,
14462 text_layout_details,
14463 );
14464 selection.collapse_to(cursor, goal);
14465 });
14466 });
14467
14468 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14469 {
14470 cx.propagate();
14471 }
14472 }
14473
14474 pub fn move_up_by_lines(
14475 &mut self,
14476 action: &MoveUpByLines,
14477 window: &mut Window,
14478 cx: &mut Context<Self>,
14479 ) {
14480 if self.take_rename(true, window, cx).is_some() {
14481 return;
14482 }
14483
14484 if self.mode.is_single_line() {
14485 cx.propagate();
14486 return;
14487 }
14488
14489 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14490
14491 let text_layout_details = &self.text_layout_details(window, cx);
14492
14493 self.change_selections(Default::default(), window, cx, |s| {
14494 s.move_with(&mut |map, selection| {
14495 if !selection.is_empty() {
14496 selection.goal = SelectionGoal::None;
14497 }
14498 let (cursor, goal) = movement::up_by_rows(
14499 map,
14500 selection.start,
14501 action.lines,
14502 selection.goal,
14503 false,
14504 text_layout_details,
14505 );
14506 selection.collapse_to(cursor, goal);
14507 });
14508 })
14509 }
14510
14511 pub fn move_down_by_lines(
14512 &mut self,
14513 action: &MoveDownByLines,
14514 window: &mut Window,
14515 cx: &mut Context<Self>,
14516 ) {
14517 if self.take_rename(true, window, cx).is_some() {
14518 return;
14519 }
14520
14521 if self.mode.is_single_line() {
14522 cx.propagate();
14523 return;
14524 }
14525
14526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14527
14528 let text_layout_details = &self.text_layout_details(window, cx);
14529
14530 self.change_selections(Default::default(), window, cx, |s| {
14531 s.move_with(&mut |map, selection| {
14532 if !selection.is_empty() {
14533 selection.goal = SelectionGoal::None;
14534 }
14535 let (cursor, goal) = movement::down_by_rows(
14536 map,
14537 selection.start,
14538 action.lines,
14539 selection.goal,
14540 false,
14541 text_layout_details,
14542 );
14543 selection.collapse_to(cursor, goal);
14544 });
14545 })
14546 }
14547
14548 pub fn select_down_by_lines(
14549 &mut self,
14550 action: &SelectDownByLines,
14551 window: &mut Window,
14552 cx: &mut Context<Self>,
14553 ) {
14554 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14555 let text_layout_details = &self.text_layout_details(window, cx);
14556 self.change_selections(Default::default(), window, cx, |s| {
14557 s.move_heads_with(&mut |map, head, goal| {
14558 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14559 })
14560 })
14561 }
14562
14563 pub fn select_up_by_lines(
14564 &mut self,
14565 action: &SelectUpByLines,
14566 window: &mut Window,
14567 cx: &mut Context<Self>,
14568 ) {
14569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14570 let text_layout_details = &self.text_layout_details(window, cx);
14571 self.change_selections(Default::default(), window, cx, |s| {
14572 s.move_heads_with(&mut |map, head, goal| {
14573 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14574 })
14575 })
14576 }
14577
14578 pub fn select_page_up(
14579 &mut self,
14580 _: &SelectPageUp,
14581 window: &mut Window,
14582 cx: &mut Context<Self>,
14583 ) {
14584 let Some(row_count) = self.visible_row_count() else {
14585 return;
14586 };
14587
14588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14589
14590 let text_layout_details = &self.text_layout_details(window, cx);
14591
14592 self.change_selections(Default::default(), window, cx, |s| {
14593 s.move_heads_with(&mut |map, head, goal| {
14594 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14595 })
14596 })
14597 }
14598
14599 pub fn move_page_up(
14600 &mut self,
14601 action: &MovePageUp,
14602 window: &mut Window,
14603 cx: &mut Context<Self>,
14604 ) {
14605 if self.take_rename(true, window, cx).is_some() {
14606 return;
14607 }
14608
14609 if self
14610 .context_menu
14611 .borrow_mut()
14612 .as_mut()
14613 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14614 .unwrap_or(false)
14615 {
14616 return;
14617 }
14618
14619 if matches!(self.mode, EditorMode::SingleLine) {
14620 cx.propagate();
14621 return;
14622 }
14623
14624 let Some(row_count) = self.visible_row_count() else {
14625 return;
14626 };
14627
14628 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14629
14630 let effects = if action.center_cursor {
14631 SelectionEffects::scroll(Autoscroll::center())
14632 } else {
14633 SelectionEffects::default()
14634 };
14635
14636 let text_layout_details = &self.text_layout_details(window, cx);
14637
14638 self.change_selections(effects, window, cx, |s| {
14639 s.move_with(&mut |map, selection| {
14640 if !selection.is_empty() {
14641 selection.goal = SelectionGoal::None;
14642 }
14643 let (cursor, goal) = movement::up_by_rows(
14644 map,
14645 selection.end,
14646 row_count,
14647 selection.goal,
14648 false,
14649 text_layout_details,
14650 );
14651 selection.collapse_to(cursor, goal);
14652 });
14653 });
14654 }
14655
14656 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14657 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14658 let text_layout_details = &self.text_layout_details(window, cx);
14659 self.change_selections(Default::default(), window, cx, |s| {
14660 s.move_heads_with(&mut |map, head, goal| {
14661 movement::up(map, head, goal, false, text_layout_details)
14662 })
14663 })
14664 }
14665
14666 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14667 self.take_rename(true, window, cx);
14668
14669 if self.mode.is_single_line() {
14670 cx.propagate();
14671 return;
14672 }
14673
14674 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14675
14676 let text_layout_details = &self.text_layout_details(window, cx);
14677 let selection_count = self.selections.count();
14678 let first_selection = self.selections.first_anchor();
14679
14680 self.change_selections(Default::default(), window, cx, |s| {
14681 s.move_with(&mut |map, selection| {
14682 if !selection.is_empty() {
14683 selection.goal = SelectionGoal::None;
14684 }
14685 let (cursor, goal) = movement::down(
14686 map,
14687 selection.end,
14688 selection.goal,
14689 false,
14690 text_layout_details,
14691 );
14692 selection.collapse_to(cursor, goal);
14693 });
14694 });
14695
14696 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14697 {
14698 cx.propagate();
14699 }
14700 }
14701
14702 pub fn select_page_down(
14703 &mut self,
14704 _: &SelectPageDown,
14705 window: &mut Window,
14706 cx: &mut Context<Self>,
14707 ) {
14708 let Some(row_count) = self.visible_row_count() else {
14709 return;
14710 };
14711
14712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14713
14714 let text_layout_details = &self.text_layout_details(window, cx);
14715
14716 self.change_selections(Default::default(), window, cx, |s| {
14717 s.move_heads_with(&mut |map, head, goal| {
14718 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14719 })
14720 })
14721 }
14722
14723 pub fn move_page_down(
14724 &mut self,
14725 action: &MovePageDown,
14726 window: &mut Window,
14727 cx: &mut Context<Self>,
14728 ) {
14729 if self.take_rename(true, window, cx).is_some() {
14730 return;
14731 }
14732
14733 if self
14734 .context_menu
14735 .borrow_mut()
14736 .as_mut()
14737 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14738 .unwrap_or(false)
14739 {
14740 return;
14741 }
14742
14743 if matches!(self.mode, EditorMode::SingleLine) {
14744 cx.propagate();
14745 return;
14746 }
14747
14748 let Some(row_count) = self.visible_row_count() else {
14749 return;
14750 };
14751
14752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14753
14754 let effects = if action.center_cursor {
14755 SelectionEffects::scroll(Autoscroll::center())
14756 } else {
14757 SelectionEffects::default()
14758 };
14759
14760 let text_layout_details = &self.text_layout_details(window, cx);
14761 self.change_selections(effects, window, cx, |s| {
14762 s.move_with(&mut |map, selection| {
14763 if !selection.is_empty() {
14764 selection.goal = SelectionGoal::None;
14765 }
14766 let (cursor, goal) = movement::down_by_rows(
14767 map,
14768 selection.end,
14769 row_count,
14770 selection.goal,
14771 false,
14772 text_layout_details,
14773 );
14774 selection.collapse_to(cursor, goal);
14775 });
14776 });
14777 }
14778
14779 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14780 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14781 let text_layout_details = &self.text_layout_details(window, cx);
14782 self.change_selections(Default::default(), window, cx, |s| {
14783 s.move_heads_with(&mut |map, head, goal| {
14784 movement::down(map, head, goal, false, text_layout_details)
14785 })
14786 });
14787 }
14788
14789 pub fn context_menu_first(
14790 &mut self,
14791 _: &ContextMenuFirst,
14792 window: &mut Window,
14793 cx: &mut Context<Self>,
14794 ) {
14795 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14796 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14797 }
14798 }
14799
14800 pub fn context_menu_prev(
14801 &mut self,
14802 _: &ContextMenuPrevious,
14803 window: &mut Window,
14804 cx: &mut Context<Self>,
14805 ) {
14806 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14807 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14808 }
14809 }
14810
14811 pub fn context_menu_next(
14812 &mut self,
14813 _: &ContextMenuNext,
14814 window: &mut Window,
14815 cx: &mut Context<Self>,
14816 ) {
14817 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14818 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14819 }
14820 }
14821
14822 pub fn context_menu_last(
14823 &mut self,
14824 _: &ContextMenuLast,
14825 window: &mut Window,
14826 cx: &mut Context<Self>,
14827 ) {
14828 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14829 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14830 }
14831 }
14832
14833 pub fn signature_help_prev(
14834 &mut self,
14835 _: &SignatureHelpPrevious,
14836 _: &mut Window,
14837 cx: &mut Context<Self>,
14838 ) {
14839 if let Some(popover) = self.signature_help_state.popover_mut() {
14840 if popover.current_signature == 0 {
14841 popover.current_signature = popover.signatures.len() - 1;
14842 } else {
14843 popover.current_signature -= 1;
14844 }
14845 cx.notify();
14846 }
14847 }
14848
14849 pub fn signature_help_next(
14850 &mut self,
14851 _: &SignatureHelpNext,
14852 _: &mut Window,
14853 cx: &mut Context<Self>,
14854 ) {
14855 if let Some(popover) = self.signature_help_state.popover_mut() {
14856 if popover.current_signature + 1 == popover.signatures.len() {
14857 popover.current_signature = 0;
14858 } else {
14859 popover.current_signature += 1;
14860 }
14861 cx.notify();
14862 }
14863 }
14864
14865 pub fn move_to_previous_word_start(
14866 &mut self,
14867 _: &MoveToPreviousWordStart,
14868 window: &mut Window,
14869 cx: &mut Context<Self>,
14870 ) {
14871 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14872 self.change_selections(Default::default(), window, cx, |s| {
14873 s.move_cursors_with(&mut |map, head, _| {
14874 (
14875 movement::previous_word_start(map, head),
14876 SelectionGoal::None,
14877 )
14878 });
14879 })
14880 }
14881
14882 pub fn move_to_previous_subword_start(
14883 &mut self,
14884 _: &MoveToPreviousSubwordStart,
14885 window: &mut Window,
14886 cx: &mut Context<Self>,
14887 ) {
14888 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14889 self.change_selections(Default::default(), window, cx, |s| {
14890 s.move_cursors_with(&mut |map, head, _| {
14891 (
14892 movement::previous_subword_start(map, head),
14893 SelectionGoal::None,
14894 )
14895 });
14896 })
14897 }
14898
14899 pub fn select_to_previous_word_start(
14900 &mut self,
14901 _: &SelectToPreviousWordStart,
14902 window: &mut Window,
14903 cx: &mut Context<Self>,
14904 ) {
14905 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14906 self.change_selections(Default::default(), window, cx, |s| {
14907 s.move_heads_with(&mut |map, head, _| {
14908 (
14909 movement::previous_word_start(map, head),
14910 SelectionGoal::None,
14911 )
14912 });
14913 })
14914 }
14915
14916 pub fn select_to_previous_subword_start(
14917 &mut self,
14918 _: &SelectToPreviousSubwordStart,
14919 window: &mut Window,
14920 cx: &mut Context<Self>,
14921 ) {
14922 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14923 self.change_selections(Default::default(), window, cx, |s| {
14924 s.move_heads_with(&mut |map, head, _| {
14925 (
14926 movement::previous_subword_start(map, head),
14927 SelectionGoal::None,
14928 )
14929 });
14930 })
14931 }
14932
14933 pub fn delete_to_previous_word_start(
14934 &mut self,
14935 action: &DeleteToPreviousWordStart,
14936 window: &mut Window,
14937 cx: &mut Context<Self>,
14938 ) {
14939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14940 self.transact(window, cx, |this, window, cx| {
14941 this.select_autoclose_pair(window, cx);
14942 this.change_selections(Default::default(), window, cx, |s| {
14943 s.move_with(&mut |map, selection| {
14944 if selection.is_empty() {
14945 let mut cursor = if action.ignore_newlines {
14946 movement::previous_word_start(map, selection.head())
14947 } else {
14948 movement::previous_word_start_or_newline(map, selection.head())
14949 };
14950 cursor = movement::adjust_greedy_deletion(
14951 map,
14952 selection.head(),
14953 cursor,
14954 action.ignore_brackets,
14955 );
14956 selection.set_head(cursor, SelectionGoal::None);
14957 }
14958 });
14959 });
14960 this.insert("", window, cx);
14961 });
14962 }
14963
14964 pub fn delete_to_previous_subword_start(
14965 &mut self,
14966 action: &DeleteToPreviousSubwordStart,
14967 window: &mut Window,
14968 cx: &mut Context<Self>,
14969 ) {
14970 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14971 self.transact(window, cx, |this, window, cx| {
14972 this.select_autoclose_pair(window, cx);
14973 this.change_selections(Default::default(), window, cx, |s| {
14974 s.move_with(&mut |map, selection| {
14975 if selection.is_empty() {
14976 let mut cursor = if action.ignore_newlines {
14977 movement::previous_subword_start(map, selection.head())
14978 } else {
14979 movement::previous_subword_start_or_newline(map, selection.head())
14980 };
14981 cursor = movement::adjust_greedy_deletion(
14982 map,
14983 selection.head(),
14984 cursor,
14985 action.ignore_brackets,
14986 );
14987 selection.set_head(cursor, SelectionGoal::None);
14988 }
14989 });
14990 });
14991 this.insert("", window, cx);
14992 });
14993 }
14994
14995 pub fn move_to_next_word_end(
14996 &mut self,
14997 _: &MoveToNextWordEnd,
14998 window: &mut Window,
14999 cx: &mut Context<Self>,
15000 ) {
15001 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15002 self.change_selections(Default::default(), window, cx, |s| {
15003 s.move_cursors_with(&mut |map, head, _| {
15004 (movement::next_word_end(map, head), SelectionGoal::None)
15005 });
15006 })
15007 }
15008
15009 pub fn move_to_next_subword_end(
15010 &mut self,
15011 _: &MoveToNextSubwordEnd,
15012 window: &mut Window,
15013 cx: &mut Context<Self>,
15014 ) {
15015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15016 self.change_selections(Default::default(), window, cx, |s| {
15017 s.move_cursors_with(&mut |map, head, _| {
15018 (movement::next_subword_end(map, head), SelectionGoal::None)
15019 });
15020 })
15021 }
15022
15023 pub fn select_to_next_word_end(
15024 &mut self,
15025 _: &SelectToNextWordEnd,
15026 window: &mut Window,
15027 cx: &mut Context<Self>,
15028 ) {
15029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15030 self.change_selections(Default::default(), window, cx, |s| {
15031 s.move_heads_with(&mut |map, head, _| {
15032 (movement::next_word_end(map, head), SelectionGoal::None)
15033 });
15034 })
15035 }
15036
15037 pub fn select_to_next_subword_end(
15038 &mut self,
15039 _: &SelectToNextSubwordEnd,
15040 window: &mut Window,
15041 cx: &mut Context<Self>,
15042 ) {
15043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15044 self.change_selections(Default::default(), window, cx, |s| {
15045 s.move_heads_with(&mut |map, head, _| {
15046 (movement::next_subword_end(map, head), SelectionGoal::None)
15047 });
15048 })
15049 }
15050
15051 pub fn delete_to_next_word_end(
15052 &mut self,
15053 action: &DeleteToNextWordEnd,
15054 window: &mut Window,
15055 cx: &mut Context<Self>,
15056 ) {
15057 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15058 self.transact(window, cx, |this, window, cx| {
15059 this.change_selections(Default::default(), window, cx, |s| {
15060 s.move_with(&mut |map, selection| {
15061 if selection.is_empty() {
15062 let mut cursor = if action.ignore_newlines {
15063 movement::next_word_end(map, selection.head())
15064 } else {
15065 movement::next_word_end_or_newline(map, selection.head())
15066 };
15067 cursor = movement::adjust_greedy_deletion(
15068 map,
15069 selection.head(),
15070 cursor,
15071 action.ignore_brackets,
15072 );
15073 selection.set_head(cursor, SelectionGoal::None);
15074 }
15075 });
15076 });
15077 this.insert("", window, cx);
15078 });
15079 }
15080
15081 pub fn delete_to_next_subword_end(
15082 &mut self,
15083 action: &DeleteToNextSubwordEnd,
15084 window: &mut Window,
15085 cx: &mut Context<Self>,
15086 ) {
15087 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15088 self.transact(window, cx, |this, window, cx| {
15089 this.change_selections(Default::default(), window, cx, |s| {
15090 s.move_with(&mut |map, selection| {
15091 if selection.is_empty() {
15092 let mut cursor = if action.ignore_newlines {
15093 movement::next_subword_end(map, selection.head())
15094 } else {
15095 movement::next_subword_end_or_newline(map, selection.head())
15096 };
15097 cursor = movement::adjust_greedy_deletion(
15098 map,
15099 selection.head(),
15100 cursor,
15101 action.ignore_brackets,
15102 );
15103 selection.set_head(cursor, SelectionGoal::None);
15104 }
15105 });
15106 });
15107 this.insert("", window, cx);
15108 });
15109 }
15110
15111 pub fn move_to_beginning_of_line(
15112 &mut self,
15113 action: &MoveToBeginningOfLine,
15114 window: &mut Window,
15115 cx: &mut Context<Self>,
15116 ) {
15117 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15119 self.change_selections(Default::default(), window, cx, |s| {
15120 s.move_cursors_with(&mut |map, head, _| {
15121 (
15122 movement::indented_line_beginning(
15123 map,
15124 head,
15125 action.stop_at_soft_wraps,
15126 stop_at_indent,
15127 ),
15128 SelectionGoal::None,
15129 )
15130 });
15131 })
15132 }
15133
15134 pub fn select_to_beginning_of_line(
15135 &mut self,
15136 action: &SelectToBeginningOfLine,
15137 window: &mut Window,
15138 cx: &mut Context<Self>,
15139 ) {
15140 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15141 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15142 self.change_selections(Default::default(), window, cx, |s| {
15143 s.move_heads_with(&mut |map, head, _| {
15144 (
15145 movement::indented_line_beginning(
15146 map,
15147 head,
15148 action.stop_at_soft_wraps,
15149 stop_at_indent,
15150 ),
15151 SelectionGoal::None,
15152 )
15153 });
15154 });
15155 }
15156
15157 pub fn delete_to_beginning_of_line(
15158 &mut self,
15159 action: &DeleteToBeginningOfLine,
15160 window: &mut Window,
15161 cx: &mut Context<Self>,
15162 ) {
15163 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15164 self.transact(window, cx, |this, window, cx| {
15165 this.change_selections(Default::default(), window, cx, |s| {
15166 s.move_with(&mut |_, selection| {
15167 selection.reversed = true;
15168 });
15169 });
15170
15171 this.select_to_beginning_of_line(
15172 &SelectToBeginningOfLine {
15173 stop_at_soft_wraps: false,
15174 stop_at_indent: action.stop_at_indent,
15175 },
15176 window,
15177 cx,
15178 );
15179 this.backspace(&Backspace, window, cx);
15180 });
15181 }
15182
15183 pub fn move_to_end_of_line(
15184 &mut self,
15185 action: &MoveToEndOfLine,
15186 window: &mut Window,
15187 cx: &mut Context<Self>,
15188 ) {
15189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15190 self.change_selections(Default::default(), window, cx, |s| {
15191 s.move_cursors_with(&mut |map, head, _| {
15192 (
15193 movement::line_end(map, head, action.stop_at_soft_wraps),
15194 SelectionGoal::None,
15195 )
15196 });
15197 })
15198 }
15199
15200 pub fn select_to_end_of_line(
15201 &mut self,
15202 action: &SelectToEndOfLine,
15203 window: &mut Window,
15204 cx: &mut Context<Self>,
15205 ) {
15206 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15207 self.change_selections(Default::default(), window, cx, |s| {
15208 s.move_heads_with(&mut |map, head, _| {
15209 (
15210 movement::line_end(map, head, action.stop_at_soft_wraps),
15211 SelectionGoal::None,
15212 )
15213 });
15214 })
15215 }
15216
15217 pub fn delete_to_end_of_line(
15218 &mut self,
15219 _: &DeleteToEndOfLine,
15220 window: &mut Window,
15221 cx: &mut Context<Self>,
15222 ) {
15223 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15224 self.transact(window, cx, |this, window, cx| {
15225 this.select_to_end_of_line(
15226 &SelectToEndOfLine {
15227 stop_at_soft_wraps: false,
15228 },
15229 window,
15230 cx,
15231 );
15232 this.delete(&Delete, window, cx);
15233 });
15234 }
15235
15236 pub fn cut_to_end_of_line(
15237 &mut self,
15238 action: &CutToEndOfLine,
15239 window: &mut Window,
15240 cx: &mut Context<Self>,
15241 ) {
15242 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15243 self.transact(window, cx, |this, window, cx| {
15244 this.select_to_end_of_line(
15245 &SelectToEndOfLine {
15246 stop_at_soft_wraps: false,
15247 },
15248 window,
15249 cx,
15250 );
15251 if !action.stop_at_newlines {
15252 this.change_selections(Default::default(), window, cx, |s| {
15253 s.move_with(&mut |_, sel| {
15254 if sel.is_empty() {
15255 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15256 }
15257 });
15258 });
15259 }
15260 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15261 let item = this.cut_common(false, window, cx);
15262 cx.write_to_clipboard(item);
15263 });
15264 }
15265
15266 pub fn move_to_start_of_paragraph(
15267 &mut self,
15268 _: &MoveToStartOfParagraph,
15269 window: &mut Window,
15270 cx: &mut Context<Self>,
15271 ) {
15272 if matches!(self.mode, EditorMode::SingleLine) {
15273 cx.propagate();
15274 return;
15275 }
15276 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15277 self.change_selections(Default::default(), window, cx, |s| {
15278 s.move_with(&mut |map, selection| {
15279 selection.collapse_to(
15280 movement::start_of_paragraph(map, selection.head(), 1),
15281 SelectionGoal::None,
15282 )
15283 });
15284 })
15285 }
15286
15287 pub fn move_to_end_of_paragraph(
15288 &mut self,
15289 _: &MoveToEndOfParagraph,
15290 window: &mut Window,
15291 cx: &mut Context<Self>,
15292 ) {
15293 if matches!(self.mode, EditorMode::SingleLine) {
15294 cx.propagate();
15295 return;
15296 }
15297 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15298 self.change_selections(Default::default(), window, cx, |s| {
15299 s.move_with(&mut |map, selection| {
15300 selection.collapse_to(
15301 movement::end_of_paragraph(map, selection.head(), 1),
15302 SelectionGoal::None,
15303 )
15304 });
15305 })
15306 }
15307
15308 pub fn select_to_start_of_paragraph(
15309 &mut self,
15310 _: &SelectToStartOfParagraph,
15311 window: &mut Window,
15312 cx: &mut Context<Self>,
15313 ) {
15314 if matches!(self.mode, EditorMode::SingleLine) {
15315 cx.propagate();
15316 return;
15317 }
15318 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15319 self.change_selections(Default::default(), window, cx, |s| {
15320 s.move_heads_with(&mut |map, head, _| {
15321 (
15322 movement::start_of_paragraph(map, head, 1),
15323 SelectionGoal::None,
15324 )
15325 });
15326 })
15327 }
15328
15329 pub fn select_to_end_of_paragraph(
15330 &mut self,
15331 _: &SelectToEndOfParagraph,
15332 window: &mut Window,
15333 cx: &mut Context<Self>,
15334 ) {
15335 if matches!(self.mode, EditorMode::SingleLine) {
15336 cx.propagate();
15337 return;
15338 }
15339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15340 self.change_selections(Default::default(), window, cx, |s| {
15341 s.move_heads_with(&mut |map, head, _| {
15342 (
15343 movement::end_of_paragraph(map, head, 1),
15344 SelectionGoal::None,
15345 )
15346 });
15347 })
15348 }
15349
15350 pub fn move_to_start_of_excerpt(
15351 &mut self,
15352 _: &MoveToStartOfExcerpt,
15353 window: &mut Window,
15354 cx: &mut Context<Self>,
15355 ) {
15356 if matches!(self.mode, EditorMode::SingleLine) {
15357 cx.propagate();
15358 return;
15359 }
15360 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15361 self.change_selections(Default::default(), window, cx, |s| {
15362 s.move_with(&mut |map, selection| {
15363 selection.collapse_to(
15364 movement::start_of_excerpt(
15365 map,
15366 selection.head(),
15367 workspace::searchable::Direction::Prev,
15368 ),
15369 SelectionGoal::None,
15370 )
15371 });
15372 })
15373 }
15374
15375 pub fn move_to_start_of_next_excerpt(
15376 &mut self,
15377 _: &MoveToStartOfNextExcerpt,
15378 window: &mut Window,
15379 cx: &mut Context<Self>,
15380 ) {
15381 if matches!(self.mode, EditorMode::SingleLine) {
15382 cx.propagate();
15383 return;
15384 }
15385
15386 self.change_selections(Default::default(), window, cx, |s| {
15387 s.move_with(&mut |map, selection| {
15388 selection.collapse_to(
15389 movement::start_of_excerpt(
15390 map,
15391 selection.head(),
15392 workspace::searchable::Direction::Next,
15393 ),
15394 SelectionGoal::None,
15395 )
15396 });
15397 })
15398 }
15399
15400 pub fn move_to_end_of_excerpt(
15401 &mut self,
15402 _: &MoveToEndOfExcerpt,
15403 window: &mut Window,
15404 cx: &mut Context<Self>,
15405 ) {
15406 if matches!(self.mode, EditorMode::SingleLine) {
15407 cx.propagate();
15408 return;
15409 }
15410 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15411 self.change_selections(Default::default(), window, cx, |s| {
15412 s.move_with(&mut |map, selection| {
15413 selection.collapse_to(
15414 movement::end_of_excerpt(
15415 map,
15416 selection.head(),
15417 workspace::searchable::Direction::Next,
15418 ),
15419 SelectionGoal::None,
15420 )
15421 });
15422 })
15423 }
15424
15425 pub fn move_to_end_of_previous_excerpt(
15426 &mut self,
15427 _: &MoveToEndOfPreviousExcerpt,
15428 window: &mut Window,
15429 cx: &mut Context<Self>,
15430 ) {
15431 if matches!(self.mode, EditorMode::SingleLine) {
15432 cx.propagate();
15433 return;
15434 }
15435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15436 self.change_selections(Default::default(), window, cx, |s| {
15437 s.move_with(&mut |map, selection| {
15438 selection.collapse_to(
15439 movement::end_of_excerpt(
15440 map,
15441 selection.head(),
15442 workspace::searchable::Direction::Prev,
15443 ),
15444 SelectionGoal::None,
15445 )
15446 });
15447 })
15448 }
15449
15450 pub fn select_to_start_of_excerpt(
15451 &mut self,
15452 _: &SelectToStartOfExcerpt,
15453 window: &mut Window,
15454 cx: &mut Context<Self>,
15455 ) {
15456 if matches!(self.mode, EditorMode::SingleLine) {
15457 cx.propagate();
15458 return;
15459 }
15460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15461 self.change_selections(Default::default(), window, cx, |s| {
15462 s.move_heads_with(&mut |map, head, _| {
15463 (
15464 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15465 SelectionGoal::None,
15466 )
15467 });
15468 })
15469 }
15470
15471 pub fn select_to_start_of_next_excerpt(
15472 &mut self,
15473 _: &SelectToStartOfNextExcerpt,
15474 window: &mut Window,
15475 cx: &mut Context<Self>,
15476 ) {
15477 if matches!(self.mode, EditorMode::SingleLine) {
15478 cx.propagate();
15479 return;
15480 }
15481 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15482 self.change_selections(Default::default(), window, cx, |s| {
15483 s.move_heads_with(&mut |map, head, _| {
15484 (
15485 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15486 SelectionGoal::None,
15487 )
15488 });
15489 })
15490 }
15491
15492 pub fn select_to_end_of_excerpt(
15493 &mut self,
15494 _: &SelectToEndOfExcerpt,
15495 window: &mut Window,
15496 cx: &mut Context<Self>,
15497 ) {
15498 if matches!(self.mode, EditorMode::SingleLine) {
15499 cx.propagate();
15500 return;
15501 }
15502 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15503 self.change_selections(Default::default(), window, cx, |s| {
15504 s.move_heads_with(&mut |map, head, _| {
15505 (
15506 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15507 SelectionGoal::None,
15508 )
15509 });
15510 })
15511 }
15512
15513 pub fn select_to_end_of_previous_excerpt(
15514 &mut self,
15515 _: &SelectToEndOfPreviousExcerpt,
15516 window: &mut Window,
15517 cx: &mut Context<Self>,
15518 ) {
15519 if matches!(self.mode, EditorMode::SingleLine) {
15520 cx.propagate();
15521 return;
15522 }
15523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15524 self.change_selections(Default::default(), window, cx, |s| {
15525 s.move_heads_with(&mut |map, head, _| {
15526 (
15527 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15528 SelectionGoal::None,
15529 )
15530 });
15531 })
15532 }
15533
15534 pub fn move_to_beginning(
15535 &mut self,
15536 _: &MoveToBeginning,
15537 window: &mut Window,
15538 cx: &mut Context<Self>,
15539 ) {
15540 if matches!(self.mode, EditorMode::SingleLine) {
15541 cx.propagate();
15542 return;
15543 }
15544 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15545 self.change_selections(Default::default(), window, cx, |s| {
15546 s.select_ranges(vec![Anchor::Min..Anchor::Min]);
15547 });
15548 }
15549
15550 pub fn select_to_beginning(
15551 &mut self,
15552 _: &SelectToBeginning,
15553 window: &mut Window,
15554 cx: &mut Context<Self>,
15555 ) {
15556 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15557 selection.set_head(Point::zero(), SelectionGoal::None);
15558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15559 self.change_selections(Default::default(), window, cx, |s| {
15560 s.select(vec![selection]);
15561 });
15562 }
15563
15564 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15565 if matches!(self.mode, EditorMode::SingleLine) {
15566 cx.propagate();
15567 return;
15568 }
15569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15570 let cursor = self.buffer.read(cx).read(cx).len();
15571 self.change_selections(Default::default(), window, cx, |s| {
15572 s.select_ranges(vec![cursor..cursor])
15573 });
15574 }
15575
15576 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15577 self.nav_history = nav_history;
15578 }
15579
15580 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15581 self.nav_history.as_ref()
15582 }
15583
15584 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15585 self.push_to_nav_history(
15586 self.selections.newest_anchor().head(),
15587 None,
15588 false,
15589 true,
15590 cx,
15591 );
15592 }
15593
15594 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15595 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15596 let buffer = self.buffer.read(cx).read(cx);
15597 let cursor_position = cursor_anchor.to_point(&buffer);
15598 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15599 let scroll_top_row = scroll_anchor.top_row(&buffer);
15600 drop(buffer);
15601
15602 NavigationData {
15603 cursor_anchor,
15604 cursor_position,
15605 scroll_anchor,
15606 scroll_top_row,
15607 }
15608 }
15609
15610 fn navigation_entry(
15611 &self,
15612 cursor_anchor: Anchor,
15613 cx: &mut Context<Self>,
15614 ) -> Option<NavigationEntry> {
15615 let Some(history) = self.nav_history.clone() else {
15616 return None;
15617 };
15618 let data = self.navigation_data(cursor_anchor, cx);
15619 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15620 }
15621
15622 fn push_to_nav_history(
15623 &mut self,
15624 cursor_anchor: Anchor,
15625 new_position: Option<Point>,
15626 is_deactivate: bool,
15627 always: bool,
15628 cx: &mut Context<Self>,
15629 ) {
15630 let data = self.navigation_data(cursor_anchor, cx);
15631 if let Some(nav_history) = self.nav_history.as_mut() {
15632 if let Some(new_position) = new_position {
15633 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15634 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15635 return;
15636 }
15637 }
15638
15639 let cursor_row = data.cursor_position.row;
15640 nav_history.push(Some(data), Some(cursor_row), cx);
15641 cx.emit(EditorEvent::PushedToNavHistory {
15642 anchor: cursor_anchor,
15643 is_deactivate,
15644 })
15645 }
15646 }
15647
15648 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15649 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15650 let buffer = self.buffer.read(cx).snapshot(cx);
15651 let mut selection = self
15652 .selections
15653 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15654 selection.set_head(buffer.len(), SelectionGoal::None);
15655 self.change_selections(Default::default(), window, cx, |s| {
15656 s.select(vec![selection]);
15657 });
15658 }
15659
15660 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15662 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15663 s.select_ranges(vec![Anchor::Min..Anchor::Max]);
15664 });
15665 }
15666
15667 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15669 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15670 let mut selections = self.selections.all::<Point>(&display_map);
15671 let max_point = display_map.buffer_snapshot().max_point();
15672 for selection in &mut selections {
15673 let rows = selection.spanned_rows(true, &display_map);
15674 selection.start = Point::new(rows.start.0, 0);
15675 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15676 selection.reversed = false;
15677 }
15678 self.change_selections(Default::default(), window, cx, |s| {
15679 s.select(selections);
15680 });
15681 }
15682
15683 pub fn split_selection_into_lines(
15684 &mut self,
15685 action: &SplitSelectionIntoLines,
15686 window: &mut Window,
15687 cx: &mut Context<Self>,
15688 ) {
15689 let selections = self
15690 .selections
15691 .all::<Point>(&self.display_snapshot(cx))
15692 .into_iter()
15693 .map(|selection| selection.start..selection.end)
15694 .collect::<Vec<_>>();
15695 self.unfold_ranges(&selections, true, false, cx);
15696
15697 let mut new_selection_ranges = Vec::new();
15698 {
15699 let buffer = self.buffer.read(cx).read(cx);
15700 for selection in selections {
15701 for row in selection.start.row..selection.end.row {
15702 let line_start = Point::new(row, 0);
15703 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15704
15705 if action.keep_selections {
15706 // Keep the selection range for each line
15707 let selection_start = if row == selection.start.row {
15708 selection.start
15709 } else {
15710 line_start
15711 };
15712 new_selection_ranges.push(selection_start..line_end);
15713 } else {
15714 // Collapse to cursor at end of line
15715 new_selection_ranges.push(line_end..line_end);
15716 }
15717 }
15718
15719 let is_multiline_selection = selection.start.row != selection.end.row;
15720 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15721 // so this action feels more ergonomic when paired with other selection operations
15722 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15723 if !should_skip_last {
15724 if action.keep_selections {
15725 if is_multiline_selection {
15726 let line_start = Point::new(selection.end.row, 0);
15727 new_selection_ranges.push(line_start..selection.end);
15728 } else {
15729 new_selection_ranges.push(selection.start..selection.end);
15730 }
15731 } else {
15732 new_selection_ranges.push(selection.end..selection.end);
15733 }
15734 }
15735 }
15736 }
15737 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15738 s.select_ranges(new_selection_ranges);
15739 });
15740 }
15741
15742 pub fn add_selection_above(
15743 &mut self,
15744 action: &AddSelectionAbove,
15745 window: &mut Window,
15746 cx: &mut Context<Self>,
15747 ) {
15748 self.add_selection(true, action.skip_soft_wrap, window, cx);
15749 }
15750
15751 pub fn add_selection_below(
15752 &mut self,
15753 action: &AddSelectionBelow,
15754 window: &mut Window,
15755 cx: &mut Context<Self>,
15756 ) {
15757 self.add_selection(false, action.skip_soft_wrap, window, cx);
15758 }
15759
15760 fn add_selection(
15761 &mut self,
15762 above: bool,
15763 skip_soft_wrap: bool,
15764 window: &mut Window,
15765 cx: &mut Context<Self>,
15766 ) {
15767 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15768
15769 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15770 let all_selections = self.selections.all::<Point>(&display_map);
15771 let text_layout_details = self.text_layout_details(window, cx);
15772
15773 let (mut columnar_selections, new_selections_to_columnarize) = {
15774 if let Some(state) = self.add_selections_state.as_ref() {
15775 let columnar_selection_ids: HashSet<_> = state
15776 .groups
15777 .iter()
15778 .flat_map(|group| group.stack.iter())
15779 .copied()
15780 .collect();
15781
15782 all_selections
15783 .into_iter()
15784 .partition(|s| columnar_selection_ids.contains(&s.id))
15785 } else {
15786 (Vec::new(), all_selections)
15787 }
15788 };
15789
15790 let mut state = self
15791 .add_selections_state
15792 .take()
15793 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15794
15795 for selection in new_selections_to_columnarize {
15796 let range = selection.display_range(&display_map).sorted();
15797 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15798 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15799 let positions = start_x.min(end_x)..start_x.max(end_x);
15800 let mut stack = Vec::new();
15801 for row in range.start.row().0..=range.end.row().0 {
15802 if let Some(selection) = self.selections.build_columnar_selection(
15803 &display_map,
15804 DisplayRow(row),
15805 &positions,
15806 selection.reversed,
15807 &text_layout_details,
15808 ) {
15809 stack.push(selection.id);
15810 columnar_selections.push(selection);
15811 }
15812 }
15813 if !stack.is_empty() {
15814 if above {
15815 stack.reverse();
15816 }
15817 state.groups.push(AddSelectionsGroup { above, stack });
15818 }
15819 }
15820
15821 let mut final_selections = Vec::new();
15822 let end_row = if above {
15823 DisplayRow(0)
15824 } else {
15825 display_map.max_point().row()
15826 };
15827
15828 // When `skip_soft_wrap` is true, we use UTF-16 columns instead of pixel
15829 // positions to place new selections, so we need to keep track of the
15830 // column range of the oldest selection in each group, because
15831 // intermediate selections may have been clamped to shorter lines.
15832 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15833 let mut map = HashMap::default();
15834 for group in state.groups.iter() {
15835 if let Some(oldest_id) = group.stack.first() {
15836 if let Some(oldest_selection) =
15837 columnar_selections.iter().find(|s| s.id == *oldest_id)
15838 {
15839 let snapshot = display_map.buffer_snapshot();
15840 let start_col =
15841 snapshot.point_to_point_utf16(oldest_selection.start).column;
15842 let end_col = snapshot.point_to_point_utf16(oldest_selection.end).column;
15843 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15844 for id in &group.stack {
15845 map.insert(*id, goal_columns.clone());
15846 }
15847 }
15848 }
15849 }
15850 map
15851 } else {
15852 HashMap::default()
15853 };
15854
15855 let mut last_added_item_per_group = HashMap::default();
15856 for group in state.groups.iter_mut() {
15857 if let Some(last_id) = group.stack.last() {
15858 last_added_item_per_group.insert(*last_id, group);
15859 }
15860 }
15861
15862 for selection in columnar_selections {
15863 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15864 if above == group.above {
15865 let range = selection.display_range(&display_map).sorted();
15866 debug_assert_eq!(range.start.row(), range.end.row());
15867 let row = range.start.row();
15868 let positions =
15869 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15870 Pixels::from(start)..Pixels::from(end)
15871 } else {
15872 let start_x =
15873 display_map.x_for_display_point(range.start, &text_layout_details);
15874 let end_x =
15875 display_map.x_for_display_point(range.end, &text_layout_details);
15876 start_x.min(end_x)..start_x.max(end_x)
15877 };
15878
15879 let maybe_new_selection = if skip_soft_wrap {
15880 let goal_columns = goal_columns_by_selection_id
15881 .remove(&selection.id)
15882 .unwrap_or_else(|| {
15883 let snapshot = display_map.buffer_snapshot();
15884 let start_col =
15885 snapshot.point_to_point_utf16(selection.start).column;
15886 let end_col = snapshot.point_to_point_utf16(selection.end).column;
15887 start_col.min(end_col)..start_col.max(end_col)
15888 });
15889 self.selections.find_next_columnar_selection_by_buffer_row(
15890 &display_map,
15891 row,
15892 end_row,
15893 above,
15894 &goal_columns,
15895 selection.reversed,
15896 &text_layout_details,
15897 )
15898 } else {
15899 self.selections.find_next_columnar_selection_by_display_row(
15900 &display_map,
15901 row,
15902 end_row,
15903 above,
15904 &positions,
15905 selection.reversed,
15906 &text_layout_details,
15907 )
15908 };
15909
15910 if let Some(new_selection) = maybe_new_selection {
15911 group.stack.push(new_selection.id);
15912 if above {
15913 final_selections.push(new_selection);
15914 final_selections.push(selection);
15915 } else {
15916 final_selections.push(selection);
15917 final_selections.push(new_selection);
15918 }
15919 } else {
15920 final_selections.push(selection);
15921 }
15922 } else {
15923 group.stack.pop();
15924 }
15925 } else {
15926 final_selections.push(selection);
15927 }
15928 }
15929
15930 self.change_selections(Default::default(), window, cx, |s| {
15931 s.select(final_selections);
15932 });
15933
15934 let final_selection_ids: HashSet<_> = self
15935 .selections
15936 .all::<Point>(&display_map)
15937 .iter()
15938 .map(|s| s.id)
15939 .collect();
15940 state.groups.retain_mut(|group| {
15941 // selections might get merged above so we remove invalid items from stacks
15942 group.stack.retain(|id| final_selection_ids.contains(id));
15943
15944 // single selection in stack can be treated as initial state
15945 group.stack.len() > 1
15946 });
15947
15948 if !state.groups.is_empty() {
15949 self.add_selections_state = Some(state);
15950 }
15951 }
15952
15953 pub fn insert_snippet_at_selections(
15954 &mut self,
15955 action: &InsertSnippet,
15956 window: &mut Window,
15957 cx: &mut Context<Self>,
15958 ) {
15959 self.try_insert_snippet_at_selections(action, window, cx)
15960 .log_err();
15961 }
15962
15963 fn try_insert_snippet_at_selections(
15964 &mut self,
15965 action: &InsertSnippet,
15966 window: &mut Window,
15967 cx: &mut Context<Self>,
15968 ) -> Result<()> {
15969 let insertion_ranges = self
15970 .selections
15971 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15972 .into_iter()
15973 .map(|selection| selection.range())
15974 .collect_vec();
15975
15976 let snippet = if let Some(snippet_body) = &action.snippet {
15977 if action.language.is_none() && action.name.is_none() {
15978 Snippet::parse(snippet_body)?
15979 } else {
15980 bail!("`snippet` is mutually exclusive with `language` and `name`")
15981 }
15982 } else if let Some(name) = &action.name {
15983 let project = self.project().context("no project")?;
15984 let snippet_store = project.read(cx).snippets().read(cx);
15985 let snippet = snippet_store
15986 .snippets_for(action.language.clone(), cx)
15987 .into_iter()
15988 .find(|snippet| snippet.name == *name)
15989 .context("snippet not found")?;
15990 Snippet::parse(&snippet.body)?
15991 } else {
15992 // todo(andrew): open modal to select snippet
15993 bail!("`name` or `snippet` is required")
15994 };
15995
15996 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15997 }
15998
15999 fn select_match_ranges(
16000 &mut self,
16001 range: Range<MultiBufferOffset>,
16002 reversed: bool,
16003 replace_newest: bool,
16004 auto_scroll: Option<Autoscroll>,
16005 window: &mut Window,
16006 cx: &mut Context<Editor>,
16007 ) {
16008 self.unfold_ranges(
16009 std::slice::from_ref(&range),
16010 false,
16011 auto_scroll.is_some(),
16012 cx,
16013 );
16014 let effects = if let Some(scroll) = auto_scroll {
16015 SelectionEffects::scroll(scroll)
16016 } else {
16017 SelectionEffects::no_scroll()
16018 };
16019 self.change_selections(effects, window, cx, |s| {
16020 if replace_newest {
16021 s.delete(s.newest_anchor().id);
16022 }
16023 if reversed {
16024 s.insert_range(range.end..range.start);
16025 } else {
16026 s.insert_range(range);
16027 }
16028 });
16029 }
16030
16031 pub fn select_next_match_internal(
16032 &mut self,
16033 display_map: &DisplaySnapshot,
16034 replace_newest: bool,
16035 autoscroll: Option<Autoscroll>,
16036 window: &mut Window,
16037 cx: &mut Context<Self>,
16038 ) -> Result<()> {
16039 let buffer = display_map.buffer_snapshot();
16040 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16041 if let Some(mut select_next_state) = self.select_next_state.take() {
16042 let query = &select_next_state.query;
16043 if !select_next_state.done {
16044 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16045 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16046 let mut next_selected_range = None;
16047
16048 let bytes_after_last_selection =
16049 buffer.bytes_in_range(last_selection.end..buffer.len());
16050 let bytes_before_first_selection =
16051 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
16052 let query_matches = query
16053 .stream_find_iter(bytes_after_last_selection)
16054 .map(|result| (last_selection.end, result))
16055 .chain(
16056 query
16057 .stream_find_iter(bytes_before_first_selection)
16058 .map(|result| (MultiBufferOffset(0), result)),
16059 );
16060
16061 for (start_offset, query_match) in query_matches {
16062 let query_match = query_match.unwrap(); // can only fail due to I/O
16063 let offset_range =
16064 start_offset + query_match.start()..start_offset + query_match.end();
16065
16066 if !select_next_state.wordwise
16067 || (!buffer.is_inside_word(offset_range.start, None)
16068 && !buffer.is_inside_word(offset_range.end, None))
16069 {
16070 let idx = selections
16071 .partition_point(|selection| selection.end <= offset_range.start);
16072 let overlaps = selections
16073 .get(idx)
16074 .map_or(false, |selection| selection.start < offset_range.end);
16075
16076 if !overlaps {
16077 next_selected_range = Some(offset_range);
16078 break;
16079 }
16080 }
16081 }
16082
16083 if let Some(next_selected_range) = next_selected_range {
16084 self.select_match_ranges(
16085 next_selected_range,
16086 last_selection.reversed,
16087 replace_newest,
16088 autoscroll,
16089 window,
16090 cx,
16091 );
16092 } else {
16093 select_next_state.done = true;
16094 }
16095 }
16096
16097 self.select_next_state = Some(select_next_state);
16098 } else {
16099 let mut only_carets = true;
16100 let mut same_text_selected = true;
16101 let mut selected_text = None;
16102
16103 let mut selections_iter = selections.iter().peekable();
16104 while let Some(selection) = selections_iter.next() {
16105 if selection.start != selection.end {
16106 only_carets = false;
16107 }
16108
16109 if same_text_selected {
16110 if selected_text.is_none() {
16111 selected_text =
16112 Some(buffer.text_for_range(selection.range()).collect::<String>());
16113 }
16114
16115 if let Some(next_selection) = selections_iter.peek() {
16116 if next_selection.len() == selection.len() {
16117 let next_selected_text = buffer
16118 .text_for_range(next_selection.range())
16119 .collect::<String>();
16120 if Some(next_selected_text) != selected_text {
16121 same_text_selected = false;
16122 selected_text = None;
16123 }
16124 } else {
16125 same_text_selected = false;
16126 selected_text = None;
16127 }
16128 }
16129 }
16130 }
16131
16132 if only_carets {
16133 for selection in &mut selections {
16134 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16135 selection.start = word_range.start;
16136 selection.end = word_range.end;
16137 selection.goal = SelectionGoal::None;
16138 selection.reversed = false;
16139 self.select_match_ranges(
16140 selection.start..selection.end,
16141 selection.reversed,
16142 replace_newest,
16143 autoscroll,
16144 window,
16145 cx,
16146 );
16147 }
16148
16149 if selections.len() == 1 {
16150 let selection = selections
16151 .last()
16152 .expect("ensured that there's only one selection");
16153 let query = buffer
16154 .text_for_range(selection.start..selection.end)
16155 .collect::<String>();
16156 let is_empty = query.is_empty();
16157 let select_state = SelectNextState {
16158 query: self.build_query(&[query], cx)?,
16159 wordwise: true,
16160 done: is_empty,
16161 };
16162 self.select_next_state = Some(select_state);
16163 } else {
16164 self.select_next_state = None;
16165 }
16166 } else if let Some(selected_text) = selected_text {
16167 self.select_next_state = Some(SelectNextState {
16168 query: self.build_query(&[selected_text], cx)?,
16169 wordwise: false,
16170 done: false,
16171 });
16172 self.select_next_match_internal(
16173 display_map,
16174 replace_newest,
16175 autoscroll,
16176 window,
16177 cx,
16178 )?;
16179 }
16180 }
16181 Ok(())
16182 }
16183
16184 pub fn select_all_matches(
16185 &mut self,
16186 _action: &SelectAllMatches,
16187 window: &mut Window,
16188 cx: &mut Context<Self>,
16189 ) -> Result<()> {
16190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16191
16192 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16193
16194 self.select_next_match_internal(&display_map, false, None, window, cx)?;
16195 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
16196 else {
16197 return Ok(());
16198 };
16199
16200 let mut new_selections = Vec::new();
16201 let initial_selection = self.selections.oldest::<MultiBufferOffset>(&display_map);
16202 let reversed = initial_selection.reversed;
16203 let buffer = display_map.buffer_snapshot();
16204 let query_matches = select_next_state
16205 .query
16206 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
16207
16208 for query_match in query_matches.into_iter() {
16209 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
16210 let offset_range = if reversed {
16211 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
16212 } else {
16213 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
16214 };
16215
16216 let is_partial_word_match = select_next_state.wordwise
16217 && (buffer.is_inside_word(offset_range.start, None)
16218 || buffer.is_inside_word(offset_range.end, None));
16219
16220 let is_initial_selection = MultiBufferOffset(query_match.start())
16221 == initial_selection.start
16222 && MultiBufferOffset(query_match.end()) == initial_selection.end;
16223
16224 if !is_partial_word_match && !is_initial_selection {
16225 new_selections.push(offset_range);
16226 }
16227 }
16228
16229 // Ensure that the initial range is the last selection, as
16230 // `MutableSelectionsCollection::select_ranges` makes the last selection
16231 // the newest selection, which the editor then relies on as the primary
16232 // cursor for scroll targeting. Without this, the last match would then
16233 // be automatically focused when the user started editing the selected
16234 // matches.
16235 let initial_directed_range = if reversed {
16236 initial_selection.end..initial_selection.start
16237 } else {
16238 initial_selection.start..initial_selection.end
16239 };
16240 new_selections.push(initial_directed_range);
16241
16242 select_next_state.done = true;
16243 self.unfold_ranges(&new_selections, false, false, cx);
16244 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16245 selections.select_ranges(new_selections)
16246 });
16247
16248 Ok(())
16249 }
16250
16251 pub fn select_next(
16252 &mut self,
16253 action: &SelectNext,
16254 window: &mut Window,
16255 cx: &mut Context<Self>,
16256 ) -> Result<()> {
16257 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16258 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16259 self.select_next_match_internal(
16260 &display_map,
16261 action.replace_newest,
16262 Some(Autoscroll::newest()),
16263 window,
16264 cx,
16265 )
16266 }
16267
16268 pub fn select_previous(
16269 &mut self,
16270 action: &SelectPrevious,
16271 window: &mut Window,
16272 cx: &mut Context<Self>,
16273 ) -> Result<()> {
16274 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16275 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16276 let buffer = display_map.buffer_snapshot();
16277 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16278 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16279 let query = &select_prev_state.query;
16280 if !select_prev_state.done {
16281 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16282 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16283 let mut next_selected_range = None;
16284 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16285 let bytes_before_last_selection =
16286 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16287 let bytes_after_first_selection =
16288 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16289 let query_matches = query
16290 .stream_find_iter(bytes_before_last_selection)
16291 .map(|result| (last_selection.start, result))
16292 .chain(
16293 query
16294 .stream_find_iter(bytes_after_first_selection)
16295 .map(|result| (buffer.len(), result)),
16296 );
16297 for (end_offset, query_match) in query_matches {
16298 let query_match = query_match.unwrap(); // can only fail due to I/O
16299 let offset_range =
16300 end_offset - query_match.end()..end_offset - query_match.start();
16301
16302 if !select_prev_state.wordwise
16303 || (!buffer.is_inside_word(offset_range.start, None)
16304 && !buffer.is_inside_word(offset_range.end, None))
16305 {
16306 next_selected_range = Some(offset_range);
16307 break;
16308 }
16309 }
16310
16311 if let Some(next_selected_range) = next_selected_range {
16312 self.select_match_ranges(
16313 next_selected_range,
16314 last_selection.reversed,
16315 action.replace_newest,
16316 Some(Autoscroll::newest()),
16317 window,
16318 cx,
16319 );
16320 } else {
16321 select_prev_state.done = true;
16322 }
16323 }
16324
16325 self.select_prev_state = Some(select_prev_state);
16326 } else {
16327 let mut only_carets = true;
16328 let mut same_text_selected = true;
16329 let mut selected_text = None;
16330
16331 let mut selections_iter = selections.iter().peekable();
16332 while let Some(selection) = selections_iter.next() {
16333 if selection.start != selection.end {
16334 only_carets = false;
16335 }
16336
16337 if same_text_selected {
16338 if selected_text.is_none() {
16339 selected_text =
16340 Some(buffer.text_for_range(selection.range()).collect::<String>());
16341 }
16342
16343 if let Some(next_selection) = selections_iter.peek() {
16344 if next_selection.len() == selection.len() {
16345 let next_selected_text = buffer
16346 .text_for_range(next_selection.range())
16347 .collect::<String>();
16348 if Some(next_selected_text) != selected_text {
16349 same_text_selected = false;
16350 selected_text = None;
16351 }
16352 } else {
16353 same_text_selected = false;
16354 selected_text = None;
16355 }
16356 }
16357 }
16358 }
16359
16360 if only_carets {
16361 for selection in &mut selections {
16362 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16363 selection.start = word_range.start;
16364 selection.end = word_range.end;
16365 selection.goal = SelectionGoal::None;
16366 selection.reversed = false;
16367 self.select_match_ranges(
16368 selection.start..selection.end,
16369 selection.reversed,
16370 action.replace_newest,
16371 Some(Autoscroll::newest()),
16372 window,
16373 cx,
16374 );
16375 }
16376 if selections.len() == 1 {
16377 let selection = selections
16378 .last()
16379 .expect("ensured that there's only one selection");
16380 let query = buffer
16381 .text_for_range(selection.start..selection.end)
16382 .collect::<String>();
16383 let is_empty = query.is_empty();
16384 let select_state = SelectNextState {
16385 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16386 wordwise: true,
16387 done: is_empty,
16388 };
16389 self.select_prev_state = Some(select_state);
16390 } else {
16391 self.select_prev_state = None;
16392 }
16393 } else if let Some(selected_text) = selected_text {
16394 self.select_prev_state = Some(SelectNextState {
16395 query: self
16396 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16397 wordwise: false,
16398 done: false,
16399 });
16400 self.select_previous(action, window, cx)?;
16401 }
16402 }
16403 Ok(())
16404 }
16405
16406 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16407 /// setting the case sensitivity based on the global
16408 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16409 /// editor's settings.
16410 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16411 where
16412 I: IntoIterator<Item = P>,
16413 P: AsRef<[u8]>,
16414 {
16415 let case_sensitive = self
16416 .select_next_is_case_sensitive
16417 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16418
16419 let mut builder = AhoCorasickBuilder::new();
16420 builder.ascii_case_insensitive(!case_sensitive);
16421 builder.build(patterns)
16422 }
16423
16424 pub fn find_next_match(
16425 &mut self,
16426 _: &FindNextMatch,
16427 window: &mut Window,
16428 cx: &mut Context<Self>,
16429 ) -> Result<()> {
16430 let selections = self.selections.disjoint_anchors_arc();
16431 match selections.first() {
16432 Some(first) if selections.len() >= 2 => {
16433 self.change_selections(Default::default(), window, cx, |s| {
16434 s.select_ranges([first.range()]);
16435 });
16436 }
16437 _ => self.select_next(
16438 &SelectNext {
16439 replace_newest: true,
16440 },
16441 window,
16442 cx,
16443 )?,
16444 }
16445 Ok(())
16446 }
16447
16448 pub fn find_previous_match(
16449 &mut self,
16450 _: &FindPreviousMatch,
16451 window: &mut Window,
16452 cx: &mut Context<Self>,
16453 ) -> Result<()> {
16454 let selections = self.selections.disjoint_anchors_arc();
16455 match selections.last() {
16456 Some(last) if selections.len() >= 2 => {
16457 self.change_selections(Default::default(), window, cx, |s| {
16458 s.select_ranges([last.range()]);
16459 });
16460 }
16461 _ => self.select_previous(
16462 &SelectPrevious {
16463 replace_newest: true,
16464 },
16465 window,
16466 cx,
16467 )?,
16468 }
16469 Ok(())
16470 }
16471
16472 pub fn toggle_block_comments(
16473 &mut self,
16474 _: &ToggleBlockComments,
16475 window: &mut Window,
16476 cx: &mut Context<Self>,
16477 ) {
16478 if self.read_only(cx) {
16479 return;
16480 }
16481 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16482 self.transact(window, cx, |this, _window, cx| {
16483 let mut selections = this
16484 .selections
16485 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16486 let mut edits = Vec::new();
16487 let snapshot = this.buffer.read(cx).read(cx);
16488 let empty_str: Arc<str> = Arc::default();
16489 let mut markers_inserted = Vec::new();
16490
16491 for selection in &mut selections {
16492 let start_point = selection.start;
16493 let end_point = selection.end;
16494
16495 let Some(language) =
16496 snapshot.language_scope_at(Point::new(start_point.row, start_point.column))
16497 else {
16498 continue;
16499 };
16500
16501 let Some(BlockCommentConfig {
16502 start: comment_start,
16503 end: comment_end,
16504 ..
16505 }) = language.block_comment()
16506 else {
16507 continue;
16508 };
16509
16510 let prefix_needle = comment_start.trim_end().as_bytes();
16511 let suffix_needle = comment_end.trim_start().as_bytes();
16512
16513 // Collect full lines spanning the selection as the search region
16514 let region_start = Point::new(start_point.row, 0);
16515 let region_end = Point::new(
16516 end_point.row,
16517 snapshot.line_len(MultiBufferRow(end_point.row)),
16518 );
16519 let region_bytes: Vec<u8> = snapshot
16520 .bytes_in_range(region_start..region_end)
16521 .flatten()
16522 .copied()
16523 .collect();
16524
16525 let region_start_offset = snapshot.point_to_offset(region_start);
16526 let start_byte = snapshot.point_to_offset(start_point) - region_start_offset;
16527 let end_byte = snapshot.point_to_offset(end_point) - region_start_offset;
16528
16529 let mut is_commented = false;
16530 let mut prefix_range = start_point..start_point;
16531 let mut suffix_range = end_point..end_point;
16532
16533 // Find rightmost /* at or before the selection end
16534 if let Some(prefix_pos) = region_bytes[..end_byte.min(region_bytes.len())]
16535 .windows(prefix_needle.len())
16536 .rposition(|w| w == prefix_needle)
16537 {
16538 let after_prefix = prefix_pos + prefix_needle.len();
16539
16540 // Find the first */ after that /*
16541 if let Some(suffix_pos) = region_bytes[after_prefix..]
16542 .windows(suffix_needle.len())
16543 .position(|w| w == suffix_needle)
16544 .map(|p| p + after_prefix)
16545 {
16546 let suffix_end = suffix_pos + suffix_needle.len();
16547
16548 // Case 1: /* ... */ surrounds the selection
16549 let markers_surround = prefix_pos <= start_byte
16550 && suffix_end >= end_byte
16551 && start_byte < suffix_end;
16552
16553 // Case 2: selection contains /* ... */ (only whitespace padding)
16554 let selection_contains = start_byte <= prefix_pos
16555 && suffix_end <= end_byte
16556 && region_bytes[start_byte..prefix_pos]
16557 .iter()
16558 .all(|&b| b.is_ascii_whitespace())
16559 && region_bytes[suffix_end..end_byte]
16560 .iter()
16561 .all(|&b| b.is_ascii_whitespace());
16562
16563 if markers_surround || selection_contains {
16564 is_commented = true;
16565 let prefix_pt =
16566 snapshot.offset_to_point(region_start_offset + prefix_pos);
16567 let suffix_pt =
16568 snapshot.offset_to_point(region_start_offset + suffix_pos);
16569 prefix_range = prefix_pt
16570 ..Point::new(
16571 prefix_pt.row,
16572 prefix_pt.column + prefix_needle.len() as u32,
16573 );
16574 suffix_range = suffix_pt
16575 ..Point::new(
16576 suffix_pt.row,
16577 suffix_pt.column + suffix_needle.len() as u32,
16578 );
16579 }
16580 }
16581 }
16582
16583 if is_commented {
16584 // Also remove the space after /* and before */
16585 if snapshot
16586 .bytes_in_range(prefix_range.end..snapshot.max_point())
16587 .flatten()
16588 .next()
16589 == Some(&b' ')
16590 {
16591 prefix_range.end.column += 1;
16592 }
16593 if suffix_range.start.column > 0 {
16594 let before =
16595 Point::new(suffix_range.start.row, suffix_range.start.column - 1);
16596 if snapshot
16597 .bytes_in_range(before..suffix_range.start)
16598 .flatten()
16599 .next()
16600 == Some(&b' ')
16601 {
16602 suffix_range.start.column -= 1;
16603 }
16604 }
16605
16606 edits.push((prefix_range, empty_str.clone()));
16607 edits.push((suffix_range, empty_str.clone()));
16608 } else {
16609 let prefix: Arc<str> = if comment_start.ends_with(' ') {
16610 comment_start.clone()
16611 } else {
16612 format!("{} ", comment_start).into()
16613 };
16614 let suffix: Arc<str> = if comment_end.starts_with(' ') {
16615 comment_end.clone()
16616 } else {
16617 format!(" {}", comment_end).into()
16618 };
16619
16620 edits.push((start_point..start_point, prefix.clone()));
16621 edits.push((end_point..end_point, suffix.clone()));
16622 markers_inserted.push((
16623 selection.id,
16624 prefix.len(),
16625 suffix.len(),
16626 selection.is_empty(),
16627 end_point.row,
16628 ));
16629 }
16630 }
16631
16632 drop(snapshot);
16633 this.buffer.update(cx, |buffer, cx| {
16634 buffer.edit(edits, None, cx);
16635 });
16636
16637 let mut selections = this
16638 .selections
16639 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16640 for selection in &mut selections {
16641 if let Some((_, prefix_len, suffix_len, was_empty, suffix_row)) = markers_inserted
16642 .iter()
16643 .find(|(id, _, _, _, _)| *id == selection.id)
16644 {
16645 if *was_empty {
16646 selection.start.column = selection
16647 .start
16648 .column
16649 .saturating_sub((*prefix_len + *suffix_len) as u32);
16650 } else {
16651 selection.start.column =
16652 selection.start.column.saturating_sub(*prefix_len as u32);
16653 if selection.end.row == *suffix_row {
16654 selection.end.column += *suffix_len as u32;
16655 }
16656 }
16657 }
16658 }
16659 this.change_selections(Default::default(), _window, cx, |s| s.select(selections));
16660 });
16661 }
16662
16663 pub fn toggle_comments(
16664 &mut self,
16665 action: &ToggleComments,
16666 window: &mut Window,
16667 cx: &mut Context<Self>,
16668 ) {
16669 if self.read_only(cx) {
16670 return;
16671 }
16672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16673 let text_layout_details = &self.text_layout_details(window, cx);
16674 self.transact(window, cx, |this, window, cx| {
16675 let mut selections = this
16676 .selections
16677 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16678 let mut edits = Vec::new();
16679 let mut selection_edit_ranges = Vec::new();
16680 let mut last_toggled_row = None;
16681 let snapshot = this.buffer.read(cx).read(cx);
16682 let empty_str: Arc<str> = Arc::default();
16683 let mut suffixes_inserted = Vec::new();
16684 let ignore_indent = action.ignore_indent;
16685
16686 fn comment_prefix_range(
16687 snapshot: &MultiBufferSnapshot,
16688 row: MultiBufferRow,
16689 comment_prefix: &str,
16690 comment_prefix_whitespace: &str,
16691 ignore_indent: bool,
16692 ) -> Range<Point> {
16693 let indent_size = if ignore_indent {
16694 0
16695 } else {
16696 snapshot.indent_size_for_line(row).len
16697 };
16698
16699 let start = Point::new(row.0, indent_size);
16700
16701 let mut line_bytes = snapshot
16702 .bytes_in_range(start..snapshot.max_point())
16703 .flatten()
16704 .copied();
16705
16706 // If this line currently begins with the line comment prefix, then record
16707 // the range containing the prefix.
16708 if line_bytes
16709 .by_ref()
16710 .take(comment_prefix.len())
16711 .eq(comment_prefix.bytes())
16712 {
16713 // Include any whitespace that matches the comment prefix.
16714 let matching_whitespace_len = line_bytes
16715 .zip(comment_prefix_whitespace.bytes())
16716 .take_while(|(a, b)| a == b)
16717 .count() as u32;
16718 let end = Point::new(
16719 start.row,
16720 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16721 );
16722 start..end
16723 } else {
16724 start..start
16725 }
16726 }
16727
16728 fn comment_suffix_range(
16729 snapshot: &MultiBufferSnapshot,
16730 row: MultiBufferRow,
16731 comment_suffix: &str,
16732 comment_suffix_has_leading_space: bool,
16733 ) -> Range<Point> {
16734 let end = Point::new(row.0, snapshot.line_len(row));
16735 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16736
16737 let mut line_end_bytes = snapshot
16738 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16739 .flatten()
16740 .copied();
16741
16742 let leading_space_len = if suffix_start_column > 0
16743 && line_end_bytes.next() == Some(b' ')
16744 && comment_suffix_has_leading_space
16745 {
16746 1
16747 } else {
16748 0
16749 };
16750
16751 // If this line currently begins with the line comment prefix, then record
16752 // the range containing the prefix.
16753 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16754 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16755 start..end
16756 } else {
16757 end..end
16758 }
16759 }
16760
16761 // TODO: Handle selections that cross excerpts
16762 for selection in &mut selections {
16763 let start_column = snapshot
16764 .indent_size_for_line(MultiBufferRow(selection.start.row))
16765 .len;
16766 let language = if let Some(language) =
16767 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16768 {
16769 language
16770 } else {
16771 continue;
16772 };
16773
16774 selection_edit_ranges.clear();
16775
16776 // If multiple selections contain a given row, avoid processing that
16777 // row more than once.
16778 let mut start_row = MultiBufferRow(selection.start.row);
16779 if last_toggled_row == Some(start_row) {
16780 start_row = start_row.next_row();
16781 }
16782 let end_row =
16783 if selection.end.row > selection.start.row && selection.end.column == 0 {
16784 MultiBufferRow(selection.end.row - 1)
16785 } else {
16786 MultiBufferRow(selection.end.row)
16787 };
16788 last_toggled_row = Some(end_row);
16789
16790 if start_row > end_row {
16791 continue;
16792 }
16793
16794 // If the language has line comments, toggle those.
16795 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16796
16797 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16798 if ignore_indent {
16799 full_comment_prefixes = full_comment_prefixes
16800 .into_iter()
16801 .map(|s| Arc::from(s.trim_end()))
16802 .collect();
16803 }
16804
16805 if !full_comment_prefixes.is_empty() {
16806 let first_prefix = full_comment_prefixes
16807 .first()
16808 .expect("prefixes is non-empty");
16809 let prefix_trimmed_lengths = full_comment_prefixes
16810 .iter()
16811 .map(|p| p.trim_end_matches(' ').len())
16812 .collect::<SmallVec<[usize; 4]>>();
16813
16814 let mut all_selection_lines_are_comments = true;
16815
16816 for row in start_row.0..=end_row.0 {
16817 let row = MultiBufferRow(row);
16818 if start_row < end_row && snapshot.is_line_blank(row) {
16819 continue;
16820 }
16821
16822 let prefix_range = full_comment_prefixes
16823 .iter()
16824 .zip(prefix_trimmed_lengths.iter().copied())
16825 .map(|(prefix, trimmed_prefix_len)| {
16826 comment_prefix_range(
16827 snapshot.deref(),
16828 row,
16829 &prefix[..trimmed_prefix_len],
16830 &prefix[trimmed_prefix_len..],
16831 ignore_indent,
16832 )
16833 })
16834 .max_by_key(|range| range.end.column - range.start.column)
16835 .expect("prefixes is non-empty");
16836
16837 if prefix_range.is_empty() {
16838 all_selection_lines_are_comments = false;
16839 }
16840
16841 selection_edit_ranges.push(prefix_range);
16842 }
16843
16844 if all_selection_lines_are_comments {
16845 edits.extend(
16846 selection_edit_ranges
16847 .iter()
16848 .cloned()
16849 .map(|range| (range, empty_str.clone())),
16850 );
16851 } else {
16852 let min_column = selection_edit_ranges
16853 .iter()
16854 .map(|range| range.start.column)
16855 .min()
16856 .unwrap_or(0);
16857 edits.extend(selection_edit_ranges.iter().map(|range| {
16858 let position = Point::new(range.start.row, min_column);
16859 (position..position, first_prefix.clone())
16860 }));
16861 }
16862 } else if let Some(BlockCommentConfig {
16863 start: full_comment_prefix,
16864 end: comment_suffix,
16865 ..
16866 }) = language.block_comment()
16867 {
16868 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16869 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16870 let prefix_range = comment_prefix_range(
16871 snapshot.deref(),
16872 start_row,
16873 comment_prefix,
16874 comment_prefix_whitespace,
16875 ignore_indent,
16876 );
16877 let suffix_range = comment_suffix_range(
16878 snapshot.deref(),
16879 end_row,
16880 comment_suffix.trim_start_matches(' '),
16881 comment_suffix.starts_with(' '),
16882 );
16883
16884 if prefix_range.is_empty() || suffix_range.is_empty() {
16885 edits.push((
16886 prefix_range.start..prefix_range.start,
16887 full_comment_prefix.clone(),
16888 ));
16889 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16890 suffixes_inserted.push((end_row, comment_suffix.len()));
16891 } else {
16892 edits.push((prefix_range, empty_str.clone()));
16893 edits.push((suffix_range, empty_str.clone()));
16894 }
16895 } else {
16896 continue;
16897 }
16898 }
16899
16900 drop(snapshot);
16901 this.buffer.update(cx, |buffer, cx| {
16902 buffer.edit(edits, None, cx);
16903 });
16904
16905 // Adjust selections so that they end before any comment suffixes that
16906 // were inserted.
16907 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16908 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16909 let snapshot = this.buffer.read(cx).read(cx);
16910 for selection in &mut selections {
16911 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16912 match row.cmp(&MultiBufferRow(selection.end.row)) {
16913 Ordering::Less => {
16914 suffixes_inserted.next();
16915 continue;
16916 }
16917 Ordering::Greater => break,
16918 Ordering::Equal => {
16919 if selection.end.column == snapshot.line_len(row) {
16920 if selection.is_empty() {
16921 selection.start.column -= suffix_len as u32;
16922 }
16923 selection.end.column -= suffix_len as u32;
16924 }
16925 break;
16926 }
16927 }
16928 }
16929 }
16930
16931 drop(snapshot);
16932 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16933
16934 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16935 let selections_on_single_row = selections.windows(2).all(|selections| {
16936 selections[0].start.row == selections[1].start.row
16937 && selections[0].end.row == selections[1].end.row
16938 && selections[0].start.row == selections[0].end.row
16939 });
16940 let selections_selecting = selections
16941 .iter()
16942 .any(|selection| selection.start != selection.end);
16943 let advance_downwards = action.advance_downwards
16944 && selections_on_single_row
16945 && !selections_selecting
16946 && !matches!(this.mode, EditorMode::SingleLine);
16947
16948 if advance_downwards {
16949 let snapshot = this.buffer.read(cx).snapshot(cx);
16950
16951 this.change_selections(Default::default(), window, cx, |s| {
16952 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16953 let mut point = display_point.to_point(display_snapshot);
16954 point.row += 1;
16955 point = snapshot.clip_point(point, Bias::Left);
16956 let display_point = point.to_display_point(display_snapshot);
16957 let goal = SelectionGoal::HorizontalPosition(
16958 display_snapshot
16959 .x_for_display_point(display_point, text_layout_details)
16960 .into(),
16961 );
16962 (display_point, goal)
16963 })
16964 });
16965 }
16966 });
16967 }
16968
16969 pub fn select_enclosing_symbol(
16970 &mut self,
16971 _: &SelectEnclosingSymbol,
16972 window: &mut Window,
16973 cx: &mut Context<Self>,
16974 ) {
16975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16976
16977 let buffer = self.buffer.read(cx).snapshot(cx);
16978 let old_selections = self
16979 .selections
16980 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16981 .into_boxed_slice();
16982
16983 fn update_selection(
16984 selection: &Selection<MultiBufferOffset>,
16985 buffer_snap: &MultiBufferSnapshot,
16986 ) -> Option<Selection<MultiBufferOffset>> {
16987 let cursor = selection.head();
16988 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16989 for symbol in symbols.iter().rev() {
16990 let start = symbol.range.start.to_offset(buffer_snap);
16991 let end = symbol.range.end.to_offset(buffer_snap);
16992 let new_range = start..end;
16993 if start < selection.start || end > selection.end {
16994 return Some(Selection {
16995 id: selection.id,
16996 start: new_range.start,
16997 end: new_range.end,
16998 goal: SelectionGoal::None,
16999 reversed: selection.reversed,
17000 });
17001 }
17002 }
17003 None
17004 }
17005
17006 let mut selected_larger_symbol = false;
17007 let new_selections = old_selections
17008 .iter()
17009 .map(|selection| match update_selection(selection, &buffer) {
17010 Some(new_selection) => {
17011 if new_selection.range() != selection.range() {
17012 selected_larger_symbol = true;
17013 }
17014 new_selection
17015 }
17016 None => selection.clone(),
17017 })
17018 .collect::<Vec<_>>();
17019
17020 if selected_larger_symbol {
17021 self.change_selections(Default::default(), window, cx, |s| {
17022 s.select(new_selections);
17023 });
17024 }
17025 }
17026
17027 pub fn select_larger_syntax_node(
17028 &mut self,
17029 _: &SelectLargerSyntaxNode,
17030 window: &mut Window,
17031 cx: &mut Context<Self>,
17032 ) {
17033 let Some(visible_row_count) = self.visible_row_count() else {
17034 return;
17035 };
17036 let old_selections: Box<[_]> = self
17037 .selections
17038 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17039 .into();
17040 if old_selections.is_empty() {
17041 return;
17042 }
17043
17044 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17045
17046 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17047 let buffer = self.buffer.read(cx).snapshot(cx);
17048
17049 let mut selected_larger_node = false;
17050 let mut new_selections = old_selections
17051 .iter()
17052 .map(|selection| {
17053 let old_range = selection.start..selection.end;
17054
17055 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
17056 // manually select word at selection
17057 if ["string_content", "inline"].contains(&node.kind()) {
17058 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
17059 // ignore if word is already selected
17060 if !word_range.is_empty() && old_range != word_range {
17061 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
17062 // only select word if start and end point belongs to same word
17063 if word_range == last_word_range {
17064 selected_larger_node = true;
17065 return Selection {
17066 id: selection.id,
17067 start: word_range.start,
17068 end: word_range.end,
17069 goal: SelectionGoal::None,
17070 reversed: selection.reversed,
17071 };
17072 }
17073 }
17074 }
17075 }
17076
17077 let mut new_range = old_range.clone();
17078 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
17079 new_range = range;
17080 if !node.is_named() {
17081 continue;
17082 }
17083 if !display_map.intersects_fold(new_range.start)
17084 && !display_map.intersects_fold(new_range.end)
17085 {
17086 break;
17087 }
17088 }
17089
17090 selected_larger_node |= new_range != old_range;
17091 Selection {
17092 id: selection.id,
17093 start: new_range.start,
17094 end: new_range.end,
17095 goal: SelectionGoal::None,
17096 reversed: selection.reversed,
17097 }
17098 })
17099 .collect::<Vec<_>>();
17100
17101 if !selected_larger_node {
17102 return; // don't put this call in the history
17103 }
17104
17105 // scroll based on transformation done to the last selection created by the user
17106 let (last_old, last_new) = old_selections
17107 .last()
17108 .zip(new_selections.last().cloned())
17109 .expect("old_selections isn't empty");
17110
17111 let is_selection_reversed = if new_selections.len() == 1 {
17112 let should_be_reversed = last_old.start != last_new.start;
17113 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
17114 should_be_reversed
17115 } else {
17116 last_new.reversed
17117 };
17118
17119 if selected_larger_node {
17120 self.select_syntax_node_history.disable_clearing = true;
17121 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17122 s.select(new_selections.clone());
17123 });
17124 self.select_syntax_node_history.disable_clearing = false;
17125 }
17126
17127 let start_row = last_new.start.to_display_point(&display_map).row().0;
17128 let end_row = last_new.end.to_display_point(&display_map).row().0;
17129 let selection_height = end_row - start_row + 1;
17130 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
17131
17132 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
17133 let scroll_behavior = if fits_on_the_screen {
17134 self.request_autoscroll(Autoscroll::fit(), cx);
17135 SelectSyntaxNodeScrollBehavior::FitSelection
17136 } else if is_selection_reversed {
17137 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
17138 SelectSyntaxNodeScrollBehavior::CursorTop
17139 } else {
17140 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
17141 SelectSyntaxNodeScrollBehavior::CursorBottom
17142 };
17143
17144 let old_selections: Box<[Selection<Anchor>]> = old_selections
17145 .iter()
17146 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
17147 .collect();
17148 self.select_syntax_node_history.push((
17149 old_selections,
17150 scroll_behavior,
17151 is_selection_reversed,
17152 ));
17153 }
17154
17155 pub fn select_smaller_syntax_node(
17156 &mut self,
17157 _: &SelectSmallerSyntaxNode,
17158 window: &mut Window,
17159 cx: &mut Context<Self>,
17160 ) {
17161 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17162
17163 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
17164 self.select_syntax_node_history.pop()
17165 {
17166 if let Some(selection) = selections.last_mut() {
17167 selection.reversed = is_selection_reversed;
17168 }
17169
17170 let snapshot = self.buffer.read(cx).snapshot(cx);
17171 let selections: Vec<Selection<MultiBufferOffset>> = selections
17172 .iter()
17173 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
17174 .collect();
17175
17176 self.select_syntax_node_history.disable_clearing = true;
17177 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17178 s.select(selections);
17179 });
17180 self.select_syntax_node_history.disable_clearing = false;
17181
17182 match scroll_behavior {
17183 SelectSyntaxNodeScrollBehavior::CursorTop => {
17184 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
17185 }
17186 SelectSyntaxNodeScrollBehavior::FitSelection => {
17187 self.request_autoscroll(Autoscroll::fit(), cx);
17188 }
17189 SelectSyntaxNodeScrollBehavior::CursorBottom => {
17190 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
17191 }
17192 }
17193 }
17194 }
17195
17196 pub fn unwrap_syntax_node(
17197 &mut self,
17198 _: &UnwrapSyntaxNode,
17199 window: &mut Window,
17200 cx: &mut Context<Self>,
17201 ) {
17202 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17203
17204 let buffer = self.buffer.read(cx).snapshot(cx);
17205 let selections = self
17206 .selections
17207 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17208 .into_iter()
17209 // subtracting the offset requires sorting
17210 .sorted_by_key(|i| i.start);
17211
17212 let full_edits = selections
17213 .into_iter()
17214 .filter_map(|selection| {
17215 let child = if selection.is_empty()
17216 && let Some((_, ancestor_range)) =
17217 buffer.syntax_ancestor(selection.start..selection.end)
17218 {
17219 ancestor_range
17220 } else {
17221 selection.range()
17222 };
17223
17224 let mut parent = child.clone();
17225 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
17226 parent = ancestor_range;
17227 if parent.start < child.start || parent.end > child.end {
17228 break;
17229 }
17230 }
17231
17232 if parent == child {
17233 return None;
17234 }
17235 let text = buffer.text_for_range(child).collect::<String>();
17236 Some((selection.id, parent, text))
17237 })
17238 .collect::<Vec<_>>();
17239 if full_edits.is_empty() {
17240 return;
17241 }
17242
17243 self.transact(window, cx, |this, window, cx| {
17244 this.buffer.update(cx, |buffer, cx| {
17245 buffer.edit(
17246 full_edits
17247 .iter()
17248 .map(|(_, p, t)| (p.clone(), t.clone()))
17249 .collect::<Vec<_>>(),
17250 None,
17251 cx,
17252 );
17253 });
17254 this.change_selections(Default::default(), window, cx, |s| {
17255 let mut offset = 0;
17256 let mut selections = vec![];
17257 for (id, parent, text) in full_edits {
17258 let start = parent.start - offset;
17259 offset += (parent.end - parent.start) - text.len();
17260 selections.push(Selection {
17261 id,
17262 start,
17263 end: start + text.len(),
17264 reversed: false,
17265 goal: Default::default(),
17266 });
17267 }
17268 s.select(selections);
17269 });
17270 });
17271 }
17272
17273 pub fn select_next_syntax_node(
17274 &mut self,
17275 _: &SelectNextSyntaxNode,
17276 window: &mut Window,
17277 cx: &mut Context<Self>,
17278 ) {
17279 let old_selections = self.selections.all_anchors(&self.display_snapshot(cx));
17280 if old_selections.is_empty() {
17281 return;
17282 }
17283
17284 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17285
17286 let buffer = self.buffer.read(cx).snapshot(cx);
17287 let mut selected_sibling = false;
17288
17289 let new_selections = old_selections
17290 .iter()
17291 .map(|selection| {
17292 let old_range =
17293 selection.start.to_offset(&buffer)..selection.end.to_offset(&buffer);
17294 if let Some(results) = buffer.map_excerpt_ranges(
17295 old_range,
17296 |buf, _excerpt_range, input_buffer_range| {
17297 let Some(node) = buf.syntax_next_sibling(input_buffer_range) else {
17298 return Vec::new();
17299 };
17300 vec![(
17301 BufferOffset(node.byte_range().start)
17302 ..BufferOffset(node.byte_range().end),
17303 (),
17304 )]
17305 },
17306 ) && let [(new_range, _)] = results.as_slice()
17307 {
17308 selected_sibling = true;
17309 let new_range =
17310 buffer.anchor_after(new_range.start)..buffer.anchor_before(new_range.end);
17311 Selection {
17312 id: selection.id,
17313 start: new_range.start,
17314 end: new_range.end,
17315 goal: SelectionGoal::None,
17316 reversed: selection.reversed,
17317 }
17318 } else {
17319 selection.clone()
17320 }
17321 })
17322 .collect::<Vec<_>>();
17323
17324 if selected_sibling {
17325 self.change_selections(
17326 SelectionEffects::scroll(Autoscroll::fit()),
17327 window,
17328 cx,
17329 |s| {
17330 s.select(new_selections);
17331 },
17332 );
17333 }
17334 }
17335
17336 pub fn select_prev_syntax_node(
17337 &mut self,
17338 _: &SelectPreviousSyntaxNode,
17339 window: &mut Window,
17340 cx: &mut Context<Self>,
17341 ) {
17342 let old_selections: Arc<[_]> = self.selections.all_anchors(&self.display_snapshot(cx));
17343
17344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17345
17346 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
17347 let mut selected_sibling = false;
17348
17349 let new_selections = old_selections
17350 .iter()
17351 .map(|selection| {
17352 let old_range = selection.start.to_offset(&multibuffer_snapshot)
17353 ..selection.end.to_offset(&multibuffer_snapshot);
17354 if let Some(results) = multibuffer_snapshot.map_excerpt_ranges(
17355 old_range,
17356 |buf, _excerpt_range, input_buffer_range| {
17357 let Some(node) = buf.syntax_prev_sibling(input_buffer_range) else {
17358 return Vec::new();
17359 };
17360 vec![(
17361 BufferOffset(node.byte_range().start)
17362 ..BufferOffset(node.byte_range().end),
17363 (),
17364 )]
17365 },
17366 ) && let [(new_range, _)] = results.as_slice()
17367 {
17368 selected_sibling = true;
17369 let new_range = multibuffer_snapshot.anchor_after(new_range.start)
17370 ..multibuffer_snapshot.anchor_before(new_range.end);
17371 Selection {
17372 id: selection.id,
17373 start: new_range.start,
17374 end: new_range.end,
17375 goal: SelectionGoal::None,
17376 reversed: selection.reversed,
17377 }
17378 } else {
17379 selection.clone()
17380 }
17381 })
17382 .collect::<Vec<_>>();
17383
17384 if selected_sibling {
17385 self.change_selections(
17386 SelectionEffects::scroll(Autoscroll::fit()),
17387 window,
17388 cx,
17389 |s| {
17390 s.select(new_selections);
17391 },
17392 );
17393 }
17394 }
17395
17396 pub fn move_to_start_of_larger_syntax_node(
17397 &mut self,
17398 _: &MoveToStartOfLargerSyntaxNode,
17399 window: &mut Window,
17400 cx: &mut Context<Self>,
17401 ) {
17402 self.move_cursors_to_syntax_nodes(window, cx, false);
17403 }
17404
17405 pub fn move_to_end_of_larger_syntax_node(
17406 &mut self,
17407 _: &MoveToEndOfLargerSyntaxNode,
17408 window: &mut Window,
17409 cx: &mut Context<Self>,
17410 ) {
17411 self.move_cursors_to_syntax_nodes(window, cx, true);
17412 }
17413
17414 fn find_syntax_node_boundary(
17415 &self,
17416 selection_pos: MultiBufferOffset,
17417 move_to_end: bool,
17418 display_map: &DisplaySnapshot,
17419 buffer: &MultiBufferSnapshot,
17420 ) -> MultiBufferOffset {
17421 let old_range = selection_pos..selection_pos;
17422 let mut new_pos = selection_pos;
17423 let mut search_range = old_range;
17424 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17425 search_range = range.clone();
17426 if !node.is_named()
17427 || display_map.intersects_fold(range.start)
17428 || display_map.intersects_fold(range.end)
17429 // If cursor is already at the end of the syntax node, continue searching
17430 || (move_to_end && range.end == selection_pos)
17431 // If cursor is already at the start of the syntax node, continue searching
17432 || (!move_to_end && range.start == selection_pos)
17433 {
17434 continue;
17435 }
17436
17437 // If we found a string_content node, find the largest parent that is still string_content
17438 // Enables us to skip to the end of strings without taking multiple steps inside the string
17439 let (_, final_range) = if node.kind() == "string_content" {
17440 let mut current_node = node;
17441 let mut current_range = range;
17442 while let Some((parent, parent_range)) =
17443 buffer.syntax_ancestor(current_range.clone())
17444 {
17445 if parent.kind() == "string_content" {
17446 current_node = parent;
17447 current_range = parent_range;
17448 } else {
17449 break;
17450 }
17451 }
17452
17453 (current_node, current_range)
17454 } else {
17455 (node, range)
17456 };
17457
17458 new_pos = if move_to_end {
17459 final_range.end
17460 } else {
17461 final_range.start
17462 };
17463
17464 break;
17465 }
17466
17467 new_pos
17468 }
17469
17470 fn move_cursors_to_syntax_nodes(
17471 &mut self,
17472 window: &mut Window,
17473 cx: &mut Context<Self>,
17474 move_to_end: bool,
17475 ) -> bool {
17476 let old_selections: Box<[_]> = self
17477 .selections
17478 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17479 .into();
17480 if old_selections.is_empty() {
17481 return false;
17482 }
17483
17484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17485
17486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17487 let buffer = self.buffer.read(cx).snapshot(cx);
17488
17489 let mut any_cursor_moved = false;
17490 let new_selections = old_selections
17491 .iter()
17492 .map(|selection| {
17493 if !selection.is_empty() {
17494 return selection.clone();
17495 }
17496
17497 let selection_pos = selection.head();
17498 let new_pos = self.find_syntax_node_boundary(
17499 selection_pos,
17500 move_to_end,
17501 &display_map,
17502 &buffer,
17503 );
17504
17505 any_cursor_moved |= new_pos != selection_pos;
17506
17507 Selection {
17508 id: selection.id,
17509 start: new_pos,
17510 end: new_pos,
17511 goal: SelectionGoal::None,
17512 reversed: false,
17513 }
17514 })
17515 .collect::<Vec<_>>();
17516
17517 self.change_selections(Default::default(), window, cx, |s| {
17518 s.select(new_selections);
17519 });
17520 self.request_autoscroll(Autoscroll::newest(), cx);
17521
17522 any_cursor_moved
17523 }
17524
17525 pub fn select_to_start_of_larger_syntax_node(
17526 &mut self,
17527 _: &SelectToStartOfLargerSyntaxNode,
17528 window: &mut Window,
17529 cx: &mut Context<Self>,
17530 ) {
17531 self.select_to_syntax_nodes(window, cx, false);
17532 }
17533
17534 pub fn select_to_end_of_larger_syntax_node(
17535 &mut self,
17536 _: &SelectToEndOfLargerSyntaxNode,
17537 window: &mut Window,
17538 cx: &mut Context<Self>,
17539 ) {
17540 self.select_to_syntax_nodes(window, cx, true);
17541 }
17542
17543 fn select_to_syntax_nodes(
17544 &mut self,
17545 window: &mut Window,
17546 cx: &mut Context<Self>,
17547 move_to_end: bool,
17548 ) {
17549 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17550
17551 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17552 let buffer = self.buffer.read(cx).snapshot(cx);
17553 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17554
17555 let new_selections = old_selections
17556 .iter()
17557 .map(|selection| {
17558 let new_pos = self.find_syntax_node_boundary(
17559 selection.head(),
17560 move_to_end,
17561 &display_map,
17562 &buffer,
17563 );
17564
17565 let mut new_selection = selection.clone();
17566 new_selection.set_head(new_pos, SelectionGoal::None);
17567 new_selection
17568 })
17569 .collect::<Vec<_>>();
17570
17571 self.change_selections(Default::default(), window, cx, |s| {
17572 s.select(new_selections);
17573 });
17574 }
17575
17576 pub fn move_to_enclosing_bracket(
17577 &mut self,
17578 _: &MoveToEnclosingBracket,
17579 window: &mut Window,
17580 cx: &mut Context<Self>,
17581 ) {
17582 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17583 self.change_selections(Default::default(), window, cx, |s| {
17584 s.move_offsets_with(&mut |snapshot, selection| {
17585 let Some(enclosing_bracket_ranges) =
17586 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17587 else {
17588 return;
17589 };
17590
17591 let mut best_length = usize::MAX;
17592 let mut best_inside = false;
17593 let mut best_in_bracket_range = false;
17594 let mut best_destination = None;
17595 for (open, close) in enclosing_bracket_ranges {
17596 let close = close.to_inclusive();
17597 let length = *close.end() - open.start;
17598 let inside = selection.start >= open.end && selection.end <= *close.start();
17599 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17600 || close.contains(&selection.head());
17601
17602 // If best is next to a bracket and current isn't, skip
17603 if !in_bracket_range && best_in_bracket_range {
17604 continue;
17605 }
17606
17607 // Prefer smaller lengths unless best is inside and current isn't
17608 if length > best_length && (best_inside || !inside) {
17609 continue;
17610 }
17611
17612 best_length = length;
17613 best_inside = inside;
17614 best_in_bracket_range = in_bracket_range;
17615 best_destination = Some(
17616 if close.contains(&selection.start) && close.contains(&selection.end) {
17617 if inside { open.end } else { open.start }
17618 } else if inside {
17619 *close.start()
17620 } else {
17621 *close.end()
17622 },
17623 );
17624 }
17625
17626 if let Some(destination) = best_destination {
17627 selection.collapse_to(destination, SelectionGoal::None);
17628 }
17629 })
17630 });
17631 }
17632
17633 pub fn undo_selection(
17634 &mut self,
17635 _: &UndoSelection,
17636 window: &mut Window,
17637 cx: &mut Context<Self>,
17638 ) {
17639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17640 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17641 self.selection_history.mode = SelectionHistoryMode::Undoing;
17642 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17643 this.end_selection(window, cx);
17644 this.change_selections(
17645 SelectionEffects::scroll(Autoscroll::newest()),
17646 window,
17647 cx,
17648 |s| s.select_anchors(entry.selections.to_vec()),
17649 );
17650 });
17651 self.selection_history.mode = SelectionHistoryMode::Normal;
17652
17653 self.select_next_state = entry.select_next_state;
17654 self.select_prev_state = entry.select_prev_state;
17655 self.add_selections_state = entry.add_selections_state;
17656 }
17657 }
17658
17659 pub fn redo_selection(
17660 &mut self,
17661 _: &RedoSelection,
17662 window: &mut Window,
17663 cx: &mut Context<Self>,
17664 ) {
17665 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17666 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17667 self.selection_history.mode = SelectionHistoryMode::Redoing;
17668 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17669 this.end_selection(window, cx);
17670 this.change_selections(
17671 SelectionEffects::scroll(Autoscroll::newest()),
17672 window,
17673 cx,
17674 |s| s.select_anchors(entry.selections.to_vec()),
17675 );
17676 });
17677 self.selection_history.mode = SelectionHistoryMode::Normal;
17678
17679 self.select_next_state = entry.select_next_state;
17680 self.select_prev_state = entry.select_prev_state;
17681 self.add_selections_state = entry.add_selections_state;
17682 }
17683 }
17684
17685 pub fn expand_excerpts(
17686 &mut self,
17687 action: &ExpandExcerpts,
17688 _: &mut Window,
17689 cx: &mut Context<Self>,
17690 ) {
17691 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17692 }
17693
17694 pub fn expand_excerpts_down(
17695 &mut self,
17696 action: &ExpandExcerptsDown,
17697 _: &mut Window,
17698 cx: &mut Context<Self>,
17699 ) {
17700 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17701 }
17702
17703 pub fn expand_excerpts_up(
17704 &mut self,
17705 action: &ExpandExcerptsUp,
17706 _: &mut Window,
17707 cx: &mut Context<Self>,
17708 ) {
17709 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17710 }
17711
17712 pub fn expand_excerpts_for_direction(
17713 &mut self,
17714 lines: u32,
17715 direction: ExpandExcerptDirection,
17716 cx: &mut Context<Self>,
17717 ) {
17718 let selections = self.selections.disjoint_anchors_arc();
17719
17720 let lines = if lines == 0 {
17721 EditorSettings::get_global(cx).expand_excerpt_lines
17722 } else {
17723 lines
17724 };
17725
17726 let snapshot = self.buffer.read(cx).snapshot(cx);
17727 let excerpt_anchors = selections
17728 .iter()
17729 .flat_map(|selection| {
17730 snapshot
17731 .range_to_buffer_ranges(selection.range())
17732 .into_iter()
17733 .filter_map(|(buffer_snapshot, range, _)| {
17734 snapshot.anchor_in_excerpt(buffer_snapshot.anchor_after(range.start))
17735 })
17736 })
17737 .collect::<Vec<_>>();
17738
17739 if self.delegate_expand_excerpts {
17740 cx.emit(EditorEvent::ExpandExcerptsRequested {
17741 excerpt_anchors,
17742 lines,
17743 direction,
17744 });
17745 return;
17746 }
17747
17748 self.buffer.update(cx, |buffer, cx| {
17749 buffer.expand_excerpts(excerpt_anchors, lines, direction, cx)
17750 })
17751 }
17752
17753 pub(crate) fn expand_excerpt(
17754 &mut self,
17755 excerpt_anchor: Anchor,
17756 direction: ExpandExcerptDirection,
17757 window: &mut Window,
17758 cx: &mut Context<Self>,
17759 ) {
17760 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17761
17762 if self.delegate_expand_excerpts {
17763 cx.emit(EditorEvent::ExpandExcerptsRequested {
17764 excerpt_anchors: vec![excerpt_anchor],
17765 lines: lines_to_expand,
17766 direction,
17767 });
17768 return;
17769 }
17770
17771 let current_scroll_position = self.scroll_position(cx);
17772 let mut scroll = None;
17773
17774 if direction == ExpandExcerptDirection::Down {
17775 let multi_buffer = self.buffer.read(cx);
17776 let snapshot = multi_buffer.snapshot(cx);
17777 if let Some((buffer_snapshot, excerpt_range)) =
17778 snapshot.excerpt_containing(excerpt_anchor..excerpt_anchor)
17779 {
17780 let excerpt_end_row =
17781 Point::from_anchor(&excerpt_range.context.end, &buffer_snapshot).row;
17782 let last_row = buffer_snapshot.max_point().row;
17783 let lines_below = last_row.saturating_sub(excerpt_end_row);
17784 if lines_below >= lines_to_expand {
17785 scroll = Some(
17786 current_scroll_position
17787 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17788 );
17789 }
17790 }
17791 }
17792 if direction == ExpandExcerptDirection::Up
17793 && self
17794 .buffer
17795 .read(cx)
17796 .snapshot(cx)
17797 .excerpt_before(excerpt_anchor)
17798 .is_none()
17799 {
17800 scroll = Some(current_scroll_position);
17801 }
17802
17803 self.buffer.update(cx, |buffer, cx| {
17804 buffer.expand_excerpts([excerpt_anchor], lines_to_expand, direction, cx)
17805 });
17806
17807 if let Some(new_scroll_position) = scroll {
17808 self.set_scroll_position(new_scroll_position, window, cx);
17809 }
17810 }
17811
17812 pub fn go_to_singleton_buffer_point(
17813 &mut self,
17814 point: Point,
17815 window: &mut Window,
17816 cx: &mut Context<Self>,
17817 ) {
17818 self.go_to_singleton_buffer_range(point..point, window, cx);
17819 }
17820
17821 pub fn go_to_singleton_buffer_range(
17822 &mut self,
17823 range: Range<Point>,
17824 window: &mut Window,
17825 cx: &mut Context<Self>,
17826 ) {
17827 let multibuffer = self.buffer().read(cx);
17828 if !multibuffer.is_singleton() {
17829 return;
17830 };
17831 let anchor_range = range.to_anchors(&multibuffer.snapshot(cx));
17832 self.change_selections(
17833 SelectionEffects::default().nav_history(true),
17834 window,
17835 cx,
17836 |s| s.select_anchor_ranges([anchor_range]),
17837 );
17838 }
17839
17840 pub fn go_to_diagnostic(
17841 &mut self,
17842 action: &GoToDiagnostic,
17843 window: &mut Window,
17844 cx: &mut Context<Self>,
17845 ) {
17846 if !self.diagnostics_enabled() {
17847 return;
17848 }
17849 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17850 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17851 }
17852
17853 pub fn go_to_prev_diagnostic(
17854 &mut self,
17855 action: &GoToPreviousDiagnostic,
17856 window: &mut Window,
17857 cx: &mut Context<Self>,
17858 ) {
17859 if !self.diagnostics_enabled() {
17860 return;
17861 }
17862 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17863 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17864 }
17865
17866 pub fn go_to_diagnostic_impl(
17867 &mut self,
17868 direction: Direction,
17869 severity: GoToDiagnosticSeverityFilter,
17870 window: &mut Window,
17871 cx: &mut Context<Self>,
17872 ) {
17873 let buffer = self.buffer.read(cx).snapshot(cx);
17874 let selection = self
17875 .selections
17876 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17877
17878 let mut active_group_id = None;
17879 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17880 && active_group.active_range.start.to_offset(&buffer) == selection.start
17881 {
17882 active_group_id = Some(active_group.group_id);
17883 }
17884
17885 fn filtered<'a>(
17886 severity: GoToDiagnosticSeverityFilter,
17887 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17888 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17889 diagnostics
17890 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17891 .filter(|entry| entry.range.start != entry.range.end)
17892 .filter(|entry| !entry.diagnostic.is_unnecessary)
17893 }
17894
17895 let before = filtered(
17896 severity,
17897 buffer
17898 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17899 .filter(|entry| entry.range.start <= selection.start),
17900 );
17901 let after = filtered(
17902 severity,
17903 buffer
17904 .diagnostics_in_range(selection.start..buffer.len())
17905 .filter(|entry| entry.range.start >= selection.start),
17906 );
17907
17908 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17909 if direction == Direction::Prev {
17910 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17911 {
17912 for diagnostic in prev_diagnostics.into_iter().rev() {
17913 if diagnostic.range.start != selection.start
17914 || active_group_id
17915 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17916 {
17917 found = Some(diagnostic);
17918 break 'outer;
17919 }
17920 }
17921 }
17922 } else {
17923 for diagnostic in after.chain(before) {
17924 if diagnostic.range.start != selection.start
17925 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17926 {
17927 found = Some(diagnostic);
17928 break;
17929 }
17930 }
17931 }
17932 let Some(next_diagnostic) = found else {
17933 return;
17934 };
17935
17936 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17937 let Some((buffer_anchor, _)) = buffer.anchor_to_buffer_anchor(next_diagnostic_start) else {
17938 return;
17939 };
17940 let buffer_id = buffer_anchor.buffer_id;
17941 let snapshot = self.snapshot(window, cx);
17942 if snapshot.intersects_fold(next_diagnostic.range.start) {
17943 self.unfold_ranges(
17944 std::slice::from_ref(&next_diagnostic.range),
17945 true,
17946 false,
17947 cx,
17948 );
17949 }
17950 self.change_selections(Default::default(), window, cx, |s| {
17951 s.select_ranges(vec![
17952 next_diagnostic.range.start..next_diagnostic.range.start,
17953 ])
17954 });
17955 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17956 self.refresh_edit_prediction(false, true, window, cx);
17957 }
17958
17959 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17961 let snapshot = self.snapshot(window, cx);
17962 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17963 self.go_to_hunk_before_or_after_position(
17964 &snapshot,
17965 selection.head(),
17966 Direction::Next,
17967 true,
17968 window,
17969 cx,
17970 );
17971 }
17972
17973 pub fn go_to_hunk_before_or_after_position(
17974 &mut self,
17975 snapshot: &EditorSnapshot,
17976 position: Point,
17977 direction: Direction,
17978 wrap_around: bool,
17979 window: &mut Window,
17980 cx: &mut Context<Editor>,
17981 ) {
17982 let row = if direction == Direction::Next {
17983 self.hunk_after_position(snapshot, position, wrap_around)
17984 .map(|hunk| hunk.row_range.start)
17985 } else {
17986 self.hunk_before_position(snapshot, position, wrap_around)
17987 };
17988
17989 if let Some(row) = row {
17990 let destination = Point::new(row.0, 0);
17991 let autoscroll = Autoscroll::center();
17992
17993 self.unfold_ranges(&[destination..destination], false, false, cx);
17994 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17995 s.select_ranges([destination..destination]);
17996 });
17997 }
17998 }
17999
18000 fn hunk_after_position(
18001 &mut self,
18002 snapshot: &EditorSnapshot,
18003 position: Point,
18004 wrap_around: bool,
18005 ) -> Option<MultiBufferDiffHunk> {
18006 let result = snapshot
18007 .buffer_snapshot()
18008 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18009 .find(|hunk| hunk.row_range.start.0 > position.row);
18010
18011 if wrap_around {
18012 result.or_else(|| {
18013 snapshot
18014 .buffer_snapshot()
18015 .diff_hunks_in_range(Point::zero()..position)
18016 .find(|hunk| hunk.row_range.end.0 < position.row)
18017 })
18018 } else {
18019 result
18020 }
18021 }
18022
18023 fn go_to_prev_hunk(
18024 &mut self,
18025 _: &GoToPreviousHunk,
18026 window: &mut Window,
18027 cx: &mut Context<Self>,
18028 ) {
18029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
18030 let snapshot = self.snapshot(window, cx);
18031 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
18032 self.go_to_hunk_before_or_after_position(
18033 &snapshot,
18034 selection.head(),
18035 Direction::Prev,
18036 true,
18037 window,
18038 cx,
18039 );
18040 }
18041
18042 fn hunk_before_position(
18043 &mut self,
18044 snapshot: &EditorSnapshot,
18045 position: Point,
18046 wrap_around: bool,
18047 ) -> Option<MultiBufferRow> {
18048 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
18049
18050 if wrap_around {
18051 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
18052 } else {
18053 result
18054 }
18055 }
18056
18057 fn go_to_next_change(
18058 &mut self,
18059 _: &GoToNextChange,
18060 window: &mut Window,
18061 cx: &mut Context<Self>,
18062 ) {
18063 if let Some(selections) = self
18064 .change_list
18065 .next_change(1, Direction::Next)
18066 .map(|s| s.to_vec())
18067 {
18068 self.change_selections(Default::default(), window, cx, |s| {
18069 let map = s.display_snapshot();
18070 s.select_display_ranges(selections.iter().map(|a| {
18071 let point = a.to_display_point(&map);
18072 point..point
18073 }))
18074 })
18075 }
18076 }
18077
18078 fn go_to_previous_change(
18079 &mut self,
18080 _: &GoToPreviousChange,
18081 window: &mut Window,
18082 cx: &mut Context<Self>,
18083 ) {
18084 if let Some(selections) = self
18085 .change_list
18086 .next_change(1, Direction::Prev)
18087 .map(|s| s.to_vec())
18088 {
18089 self.change_selections(Default::default(), window, cx, |s| {
18090 let map = s.display_snapshot();
18091 s.select_display_ranges(selections.iter().map(|a| {
18092 let point = a.to_display_point(&map);
18093 point..point
18094 }))
18095 })
18096 }
18097 }
18098
18099 pub fn go_to_next_document_highlight(
18100 &mut self,
18101 _: &GoToNextDocumentHighlight,
18102 window: &mut Window,
18103 cx: &mut Context<Self>,
18104 ) {
18105 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
18106 }
18107
18108 pub fn go_to_prev_document_highlight(
18109 &mut self,
18110 _: &GoToPreviousDocumentHighlight,
18111 window: &mut Window,
18112 cx: &mut Context<Self>,
18113 ) {
18114 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
18115 }
18116
18117 pub fn go_to_document_highlight_before_or_after_position(
18118 &mut self,
18119 direction: Direction,
18120 window: &mut Window,
18121 cx: &mut Context<Editor>,
18122 ) {
18123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
18124 let snapshot = self.snapshot(window, cx);
18125 let buffer = &snapshot.buffer_snapshot();
18126 let position = self
18127 .selections
18128 .newest::<Point>(&snapshot.display_snapshot)
18129 .head();
18130 let anchor_position = buffer.anchor_after(position);
18131
18132 // Get all document highlights (both read and write)
18133 let mut all_highlights = Vec::new();
18134
18135 if let Some((_, read_highlights)) = self
18136 .background_highlights
18137 .get(&HighlightKey::DocumentHighlightRead)
18138 {
18139 all_highlights.extend(read_highlights.iter());
18140 }
18141
18142 if let Some((_, write_highlights)) = self
18143 .background_highlights
18144 .get(&HighlightKey::DocumentHighlightWrite)
18145 {
18146 all_highlights.extend(write_highlights.iter());
18147 }
18148
18149 if all_highlights.is_empty() {
18150 return;
18151 }
18152
18153 // Sort highlights by position
18154 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
18155
18156 let target_highlight = match direction {
18157 Direction::Next => {
18158 // Find the first highlight after the current position
18159 all_highlights
18160 .iter()
18161 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
18162 }
18163 Direction::Prev => {
18164 // Find the last highlight before the current position
18165 all_highlights
18166 .iter()
18167 .rev()
18168 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
18169 }
18170 };
18171
18172 if let Some(highlight) = target_highlight {
18173 let destination = highlight.start.to_point(buffer);
18174 let autoscroll = Autoscroll::center();
18175
18176 self.unfold_ranges(&[destination..destination], false, false, cx);
18177 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18178 s.select_ranges([destination..destination]);
18179 });
18180 }
18181 }
18182
18183 fn go_to_line<T: 'static>(
18184 &mut self,
18185 position: Anchor,
18186 highlight_color: Option<Hsla>,
18187 window: &mut Window,
18188 cx: &mut Context<Self>,
18189 ) {
18190 let snapshot = self.snapshot(window, cx).display_snapshot;
18191 let position = position.to_point(&snapshot.buffer_snapshot());
18192 let start = snapshot
18193 .buffer_snapshot()
18194 .clip_point(Point::new(position.row, 0), Bias::Left);
18195 let end = start + Point::new(1, 0);
18196 let start = snapshot.buffer_snapshot().anchor_before(start);
18197 let end = snapshot.buffer_snapshot().anchor_before(end);
18198
18199 self.highlight_rows::<T>(
18200 start..end,
18201 highlight_color
18202 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
18203 Default::default(),
18204 cx,
18205 );
18206
18207 if self.buffer.read(cx).is_singleton() {
18208 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
18209 }
18210 }
18211
18212 pub fn go_to_definition(
18213 &mut self,
18214 _: &GoToDefinition,
18215 window: &mut Window,
18216 cx: &mut Context<Self>,
18217 ) -> Task<Result<Navigated>> {
18218 let definition =
18219 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
18220 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
18221 cx.spawn_in(window, async move |editor, cx| {
18222 if definition.await? == Navigated::Yes {
18223 return Ok(Navigated::Yes);
18224 }
18225 match fallback_strategy {
18226 GoToDefinitionFallback::None => Ok(Navigated::No),
18227 GoToDefinitionFallback::FindAllReferences => {
18228 match editor.update_in(cx, |editor, window, cx| {
18229 editor.find_all_references(&FindAllReferences::default(), window, cx)
18230 })? {
18231 Some(references) => references.await,
18232 None => Ok(Navigated::No),
18233 }
18234 }
18235 }
18236 })
18237 }
18238
18239 pub fn go_to_declaration(
18240 &mut self,
18241 _: &GoToDeclaration,
18242 window: &mut Window,
18243 cx: &mut Context<Self>,
18244 ) -> Task<Result<Navigated>> {
18245 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
18246 }
18247
18248 pub fn go_to_declaration_split(
18249 &mut self,
18250 _: &GoToDeclaration,
18251 window: &mut Window,
18252 cx: &mut Context<Self>,
18253 ) -> Task<Result<Navigated>> {
18254 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
18255 }
18256
18257 pub fn go_to_implementation(
18258 &mut self,
18259 _: &GoToImplementation,
18260 window: &mut Window,
18261 cx: &mut Context<Self>,
18262 ) -> Task<Result<Navigated>> {
18263 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
18264 }
18265
18266 pub fn go_to_implementation_split(
18267 &mut self,
18268 _: &GoToImplementationSplit,
18269 window: &mut Window,
18270 cx: &mut Context<Self>,
18271 ) -> Task<Result<Navigated>> {
18272 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
18273 }
18274
18275 pub fn go_to_type_definition(
18276 &mut self,
18277 _: &GoToTypeDefinition,
18278 window: &mut Window,
18279 cx: &mut Context<Self>,
18280 ) -> Task<Result<Navigated>> {
18281 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
18282 }
18283
18284 pub fn go_to_definition_split(
18285 &mut self,
18286 _: &GoToDefinitionSplit,
18287 window: &mut Window,
18288 cx: &mut Context<Self>,
18289 ) -> Task<Result<Navigated>> {
18290 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
18291 }
18292
18293 pub fn go_to_type_definition_split(
18294 &mut self,
18295 _: &GoToTypeDefinitionSplit,
18296 window: &mut Window,
18297 cx: &mut Context<Self>,
18298 ) -> Task<Result<Navigated>> {
18299 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18300 }
18301
18302 fn go_to_definition_of_kind(
18303 &mut self,
18304 kind: GotoDefinitionKind,
18305 split: bool,
18306 window: &mut Window,
18307 cx: &mut Context<Self>,
18308 ) -> Task<Result<Navigated>> {
18309 let Some(provider) = self.semantics_provider.clone() else {
18310 return Task::ready(Ok(Navigated::No));
18311 };
18312 let head = self
18313 .selections
18314 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18315 .head();
18316 let buffer = self.buffer.read(cx);
18317 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18318 return Task::ready(Ok(Navigated::No));
18319 };
18320 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18321 return Task::ready(Ok(Navigated::No));
18322 };
18323
18324 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18325
18326 cx.spawn_in(window, async move |editor, cx| {
18327 let Some(definitions) = definitions.await? else {
18328 return Ok(Navigated::No);
18329 };
18330 let navigated = editor
18331 .update_in(cx, |editor, window, cx| {
18332 editor.navigate_to_hover_links(
18333 Some(kind),
18334 definitions
18335 .into_iter()
18336 .filter(|location| {
18337 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18338 })
18339 .map(HoverLink::Text)
18340 .collect::<Vec<_>>(),
18341 nav_entry,
18342 split,
18343 window,
18344 cx,
18345 )
18346 })?
18347 .await?;
18348 anyhow::Ok(navigated)
18349 })
18350 }
18351
18352 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18353 let selection = self.selections.newest_anchor();
18354 let head = selection.head();
18355 let tail = selection.tail();
18356
18357 let Some((buffer, start_position)) =
18358 self.buffer.read(cx).text_anchor_for_position(head, cx)
18359 else {
18360 return;
18361 };
18362
18363 let end_position = if head != tail {
18364 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18365 return;
18366 };
18367 Some(pos)
18368 } else {
18369 None
18370 };
18371
18372 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18373 let url = if let Some(end_pos) = end_position {
18374 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18375 } else {
18376 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18377 };
18378
18379 if let Some(url) = url {
18380 cx.update(|window, cx| {
18381 if parse_zed_link(&url, cx).is_some() {
18382 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18383 } else {
18384 cx.open_url(&url);
18385 }
18386 })?;
18387 }
18388
18389 anyhow::Ok(())
18390 });
18391
18392 url_finder.detach();
18393 }
18394
18395 pub fn open_selected_filename(
18396 &mut self,
18397 _: &OpenSelectedFilename,
18398 window: &mut Window,
18399 cx: &mut Context<Self>,
18400 ) {
18401 let Some(workspace) = self.workspace() else {
18402 return;
18403 };
18404
18405 let position = self.selections.newest_anchor().head();
18406
18407 let Some((buffer, buffer_position)) =
18408 self.buffer.read(cx).text_anchor_for_position(position, cx)
18409 else {
18410 return;
18411 };
18412
18413 let project = self.project.clone();
18414
18415 cx.spawn_in(window, async move |_, cx| {
18416 let result = find_file(&buffer, project, buffer_position, cx).await;
18417
18418 if let Some((_, path)) = result {
18419 workspace
18420 .update_in(cx, |workspace, window, cx| {
18421 workspace.open_resolved_path(path, window, cx)
18422 })?
18423 .await?;
18424 }
18425 anyhow::Ok(())
18426 })
18427 .detach();
18428 }
18429
18430 pub(crate) fn navigate_to_hover_links(
18431 &mut self,
18432 kind: Option<GotoDefinitionKind>,
18433 definitions: Vec<HoverLink>,
18434 origin: Option<NavigationEntry>,
18435 split: bool,
18436 window: &mut Window,
18437 cx: &mut Context<Editor>,
18438 ) -> Task<Result<Navigated>> {
18439 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18440 let mut first_url_or_file = None;
18441 let definitions: Vec<_> = definitions
18442 .into_iter()
18443 .filter_map(|def| match def {
18444 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18445 HoverLink::InlayHint(lsp_location, server_id) => {
18446 let computation =
18447 self.compute_target_location(lsp_location, server_id, window, cx);
18448 Some(cx.background_spawn(computation))
18449 }
18450 HoverLink::Url(url) => {
18451 first_url_or_file = Some(Either::Left(url));
18452 None
18453 }
18454 HoverLink::File(path) => {
18455 first_url_or_file = Some(Either::Right(path));
18456 None
18457 }
18458 })
18459 .collect();
18460
18461 let workspace = self.workspace();
18462
18463 let excerpt_context_lines = multi_buffer::excerpt_context_lines(cx);
18464 cx.spawn_in(window, async move |editor, cx| {
18465 let locations: Vec<Location> = future::join_all(definitions)
18466 .await
18467 .into_iter()
18468 .filter_map(|location| location.transpose())
18469 .collect::<Result<_>>()
18470 .context("location tasks")?;
18471 let mut locations = cx.update(|_, cx| {
18472 locations
18473 .into_iter()
18474 .map(|location| {
18475 let buffer = location.buffer.read(cx);
18476 (location.buffer, location.range.to_point(buffer))
18477 })
18478 .into_group_map()
18479 })?;
18480 let mut num_locations = 0;
18481 for ranges in locations.values_mut() {
18482 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18483 ranges.dedup();
18484 // Merge overlapping or contained ranges. After sorting by
18485 // (start, Reverse(end)), we can merge in a single pass:
18486 // if the next range starts before the current one ends,
18487 // extend the current range's end if needed.
18488 let mut i = 0;
18489 while i + 1 < ranges.len() {
18490 if ranges[i + 1].start <= ranges[i].end {
18491 let merged_end = ranges[i].end.max(ranges[i + 1].end);
18492 ranges[i].end = merged_end;
18493 ranges.remove(i + 1);
18494 } else {
18495 i += 1;
18496 }
18497 }
18498 let fits_in_one_excerpt = ranges
18499 .iter()
18500 .tuple_windows()
18501 .all(|(a, b)| b.start.row - a.end.row <= 2 * excerpt_context_lines);
18502 num_locations += if fits_in_one_excerpt { 1 } else { ranges.len() };
18503 }
18504
18505 if num_locations > 1 {
18506 let tab_kind = match kind {
18507 Some(GotoDefinitionKind::Implementation) => "Implementations",
18508 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18509 Some(GotoDefinitionKind::Declaration) => "Declarations",
18510 Some(GotoDefinitionKind::Type) => "Types",
18511 };
18512 let title = editor
18513 .update_in(cx, |_, _, cx| {
18514 let target = locations
18515 .iter()
18516 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18517 .map(|(buffer, location)| {
18518 buffer
18519 .read(cx)
18520 .text_for_range(location.clone())
18521 .collect::<String>()
18522 })
18523 .filter(|text| !text.contains('\n'))
18524 .unique()
18525 .take(3)
18526 .join(", ");
18527 if target.is_empty() {
18528 tab_kind.to_owned()
18529 } else {
18530 format!("{tab_kind} for {target}")
18531 }
18532 })
18533 .context("buffer title")?;
18534
18535 let Some(workspace) = workspace else {
18536 return Ok(Navigated::No);
18537 };
18538
18539 let opened = workspace
18540 .update_in(cx, |workspace, window, cx| {
18541 let allow_preview = PreviewTabsSettings::get_global(cx)
18542 .enable_preview_multibuffer_from_code_navigation;
18543 if let Some((target_editor, target_pane)) =
18544 Self::open_locations_in_multibuffer(
18545 workspace,
18546 locations,
18547 title,
18548 split,
18549 allow_preview,
18550 MultibufferSelectionMode::First,
18551 window,
18552 cx,
18553 )
18554 {
18555 // We create our own nav history instead of using
18556 // `target_editor.nav_history` because `nav_history`
18557 // seems to be populated asynchronously when an item
18558 // is added to a pane
18559 let mut nav_history = target_pane
18560 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18561 target_editor.update(cx, |editor, cx| {
18562 let nav_data = editor
18563 .navigation_data(editor.selections.newest_anchor().head(), cx);
18564 let target =
18565 Some(nav_history.navigation_entry(Some(
18566 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18567 )));
18568 nav_history.push_tag(origin, target);
18569 })
18570 }
18571 })
18572 .is_ok();
18573
18574 anyhow::Ok(Navigated::from_bool(opened))
18575 } else if num_locations == 0 {
18576 // If there is one url or file, open it directly
18577 match first_url_or_file {
18578 Some(Either::Left(url)) => {
18579 cx.update(|window, cx| {
18580 if parse_zed_link(&url, cx).is_some() {
18581 window
18582 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18583 } else {
18584 cx.open_url(&url);
18585 }
18586 })?;
18587 Ok(Navigated::Yes)
18588 }
18589 Some(Either::Right(path)) => {
18590 // TODO(andrew): respect preview tab settings
18591 // `enable_keep_preview_on_code_navigation` and
18592 // `enable_preview_file_from_code_navigation`
18593 let Some(workspace) = workspace else {
18594 return Ok(Navigated::No);
18595 };
18596 workspace
18597 .update_in(cx, |workspace, window, cx| {
18598 workspace.open_resolved_path(path, window, cx)
18599 })?
18600 .await?;
18601 Ok(Navigated::Yes)
18602 }
18603 None => Ok(Navigated::No),
18604 }
18605 } else {
18606 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18607
18608 editor.update_in(cx, |editor, window, cx| {
18609 let target_ranges = target_ranges
18610 .into_iter()
18611 .map(|r| editor.range_for_match(&r))
18612 .map(collapse_multiline_range)
18613 .collect::<Vec<_>>();
18614 if !split
18615 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18616 {
18617 let multibuffer = editor.buffer.read(cx);
18618 let target_ranges = target_ranges
18619 .into_iter()
18620 .filter_map(|r| {
18621 let start = multibuffer.buffer_point_to_anchor(
18622 &target_buffer,
18623 r.start,
18624 cx,
18625 )?;
18626 let end = multibuffer.buffer_point_to_anchor(
18627 &target_buffer,
18628 r.end,
18629 cx,
18630 )?;
18631 Some(start..end)
18632 })
18633 .collect::<Vec<_>>();
18634 if target_ranges.is_empty() {
18635 return Navigated::No;
18636 }
18637
18638 editor.change_selections(
18639 SelectionEffects::default().nav_history(true),
18640 window,
18641 cx,
18642 |s| s.select_anchor_ranges(target_ranges),
18643 );
18644
18645 let target =
18646 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18647 if let Some(mut nav_history) = editor.nav_history.clone() {
18648 nav_history.push_tag(origin, target);
18649 }
18650 } else {
18651 let Some(workspace) = workspace else {
18652 return Navigated::No;
18653 };
18654 let pane = workspace.read(cx).active_pane().clone();
18655 window.defer(cx, move |window, cx| {
18656 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18657 workspace.update(cx, |workspace, cx| {
18658 let pane = if split {
18659 workspace.adjacent_pane(window, cx)
18660 } else {
18661 workspace.active_pane().clone()
18662 };
18663
18664 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18665 let keep_old_preview = preview_tabs_settings
18666 .enable_keep_preview_on_code_navigation;
18667 let allow_new_preview = preview_tabs_settings
18668 .enable_preview_file_from_code_navigation;
18669
18670 let editor = workspace.open_project_item(
18671 pane.clone(),
18672 target_buffer.clone(),
18673 true,
18674 true,
18675 keep_old_preview,
18676 allow_new_preview,
18677 window,
18678 cx,
18679 );
18680 (editor, pane)
18681 });
18682 // We create our own nav history instead of using
18683 // `target_editor.nav_history` because `nav_history`
18684 // seems to be populated asynchronously when an item
18685 // is added to a pane
18686 let mut nav_history = target_pane
18687 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18688 target_editor.update(cx, |target_editor, cx| {
18689 // When selecting a definition in a different buffer, disable the nav history
18690 // to avoid creating a history entry at the previous cursor location.
18691 pane.update(cx, |pane, _| pane.disable_history());
18692
18693 let multibuffer = target_editor.buffer.read(cx);
18694 let Some(target_buffer) = multibuffer.as_singleton() else {
18695 return Navigated::No;
18696 };
18697 let target_ranges = target_ranges
18698 .into_iter()
18699 .filter_map(|r| {
18700 let start = multibuffer.buffer_point_to_anchor(
18701 &target_buffer,
18702 r.start,
18703 cx,
18704 )?;
18705 let end = multibuffer.buffer_point_to_anchor(
18706 &target_buffer,
18707 r.end,
18708 cx,
18709 )?;
18710 Some(start..end)
18711 })
18712 .collect::<Vec<_>>();
18713 if target_ranges.is_empty() {
18714 return Navigated::No;
18715 }
18716
18717 target_editor.change_selections(
18718 SelectionEffects::default().nav_history(true),
18719 window,
18720 cx,
18721 |s| s.select_anchor_ranges(target_ranges),
18722 );
18723
18724 let nav_data = target_editor.navigation_data(
18725 target_editor.selections.newest_anchor().head(),
18726 cx,
18727 );
18728 let target =
18729 Some(nav_history.navigation_entry(Some(
18730 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18731 )));
18732 nav_history.push_tag(origin, target);
18733 pane.update(cx, |pane, _| pane.enable_history());
18734 Navigated::Yes
18735 });
18736 });
18737 }
18738 Navigated::Yes
18739 })
18740 }
18741 })
18742 }
18743
18744 fn compute_target_location(
18745 &self,
18746 lsp_location: lsp::Location,
18747 server_id: LanguageServerId,
18748 window: &mut Window,
18749 cx: &mut Context<Self>,
18750 ) -> Task<anyhow::Result<Option<Location>>> {
18751 let Some(project) = self.project.clone() else {
18752 return Task::ready(Ok(None));
18753 };
18754
18755 cx.spawn_in(window, async move |editor, cx| {
18756 let location_task = editor.update(cx, |_, cx| {
18757 project.update(cx, |project, cx| {
18758 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18759 })
18760 })?;
18761 let location = Some({
18762 let target_buffer_handle = location_task.await.context("open local buffer")?;
18763 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18764 let target_start = target_buffer
18765 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18766 let target_end = target_buffer
18767 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18768 target_buffer.anchor_after(target_start)
18769 ..target_buffer.anchor_before(target_end)
18770 });
18771 Location {
18772 buffer: target_buffer_handle,
18773 range,
18774 }
18775 });
18776 Ok(location)
18777 })
18778 }
18779
18780 fn go_to_next_reference(
18781 &mut self,
18782 _: &GoToNextReference,
18783 window: &mut Window,
18784 cx: &mut Context<Self>,
18785 ) {
18786 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18787 if let Some(task) = task {
18788 task.detach();
18789 };
18790 }
18791
18792 fn go_to_prev_reference(
18793 &mut self,
18794 _: &GoToPreviousReference,
18795 window: &mut Window,
18796 cx: &mut Context<Self>,
18797 ) {
18798 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18799 if let Some(task) = task {
18800 task.detach();
18801 };
18802 }
18803
18804 fn go_to_symbol_by_offset(
18805 &mut self,
18806 window: &mut Window,
18807 cx: &mut Context<Self>,
18808 offset: i8,
18809 ) -> Task<Result<()>> {
18810 let editor_snapshot = self.snapshot(window, cx);
18811
18812 // We don't care about multi-buffer symbols
18813 if !editor_snapshot.is_singleton() {
18814 return Task::ready(Ok(()));
18815 }
18816
18817 let cursor_offset = self
18818 .selections
18819 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18820 .head();
18821
18822 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18823 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18824 let buffer = ed.buffer.read(cx).as_singleton()?;
18825 Some(buffer.read(cx).remote_id())
18826 }) else {
18827 return Ok(());
18828 };
18829
18830 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18831 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18832
18833 let multi_snapshot = editor_snapshot.buffer();
18834 let buffer_range = |range: &Range<_>| {
18835 Some(
18836 multi_snapshot
18837 .buffer_anchor_range_to_anchor_range(range.clone())?
18838 .to_offset(multi_snapshot),
18839 )
18840 };
18841
18842 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18843 let current_idx = outline_items
18844 .iter()
18845 .enumerate()
18846 .filter_map(|(idx, item)| {
18847 // Find the closest outline item by distance between outline text and cursor location
18848 let source_range = buffer_range(&item.source_range_for_text)?;
18849 let distance_to_closest_endpoint = cmp::min(
18850 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18851 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18852 );
18853
18854 let item_towards_offset =
18855 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18856 == (offset as isize).signum();
18857
18858 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18859
18860 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18861 // we should not already be within the outline's source range. We then pick the closest outline
18862 // item.
18863 (item_towards_offset && !source_range_contains_cursor)
18864 .then_some((distance_to_closest_endpoint, idx))
18865 })
18866 .min()
18867 .map(|(_, idx)| idx);
18868
18869 let Some(idx) = current_idx else {
18870 return;
18871 };
18872
18873 let Some(range) = buffer_range(&outline_items[idx].source_range_for_text) else {
18874 return;
18875 };
18876 let selection = [range.start..range.start];
18877
18878 let _ = editor
18879 .update(acx, |editor, ecx| {
18880 editor.change_selections(
18881 SelectionEffects::scroll(Autoscroll::newest()),
18882 window,
18883 ecx,
18884 |s| s.select_ranges(selection),
18885 );
18886 })
18887 .ok();
18888 })?;
18889
18890 Ok(())
18891 })
18892 }
18893
18894 fn go_to_next_symbol(
18895 &mut self,
18896 _: &GoToNextSymbol,
18897 window: &mut Window,
18898 cx: &mut Context<Self>,
18899 ) {
18900 self.go_to_symbol_by_offset(window, cx, 1).detach();
18901 }
18902
18903 fn go_to_previous_symbol(
18904 &mut self,
18905 _: &GoToPreviousSymbol,
18906 window: &mut Window,
18907 cx: &mut Context<Self>,
18908 ) {
18909 self.go_to_symbol_by_offset(window, cx, -1).detach();
18910 }
18911
18912 pub fn go_to_reference_before_or_after_position(
18913 &mut self,
18914 direction: Direction,
18915 count: usize,
18916 window: &mut Window,
18917 cx: &mut Context<Self>,
18918 ) -> Option<Task<Result<()>>> {
18919 let selection = self.selections.newest_anchor();
18920 let head = selection.head();
18921
18922 let multi_buffer = self.buffer.read(cx);
18923
18924 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18925 let workspace = self.workspace()?;
18926 let project = workspace.read(cx).project().clone();
18927 let references =
18928 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18929 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18930 let Some(locations) = references.await? else {
18931 return Ok(());
18932 };
18933
18934 if locations.is_empty() {
18935 // totally normal - the cursor may be on something which is not
18936 // a symbol (e.g. a keyword)
18937 log::info!("no references found under cursor");
18938 return Ok(());
18939 }
18940
18941 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18942
18943 let (locations, current_location_index) =
18944 multi_buffer.update(cx, |multi_buffer, cx| {
18945 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18946 let mut locations = locations
18947 .into_iter()
18948 .filter_map(|loc| {
18949 let start = multi_buffer_snapshot.anchor_in_excerpt(loc.range.start)?;
18950 let end = multi_buffer_snapshot.anchor_in_excerpt(loc.range.end)?;
18951 Some(start..end)
18952 })
18953 .collect::<Vec<_>>();
18954 // There is an O(n) implementation, but given this list will be
18955 // small (usually <100 items), the extra O(log(n)) factor isn't
18956 // worth the (surprisingly large amount of) extra complexity.
18957 locations
18958 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18959
18960 let head_offset = head.to_offset(&multi_buffer_snapshot);
18961
18962 let current_location_index = locations.iter().position(|loc| {
18963 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18964 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18965 });
18966
18967 (locations, current_location_index)
18968 });
18969
18970 let Some(current_location_index) = current_location_index else {
18971 // This indicates something has gone wrong, because we already
18972 // handle the "no references" case above
18973 log::error!(
18974 "failed to find current reference under cursor. Total references: {}",
18975 locations.len()
18976 );
18977 return Ok(());
18978 };
18979
18980 let destination_location_index = match direction {
18981 Direction::Next => (current_location_index + count) % locations.len(),
18982 Direction::Prev => {
18983 (current_location_index + locations.len() - count % locations.len())
18984 % locations.len()
18985 }
18986 };
18987
18988 // TODO(cameron): is this needed?
18989 // the thinking is to avoid "jumping to the current location" (avoid
18990 // polluting "jumplist" in vim terms)
18991 if current_location_index == destination_location_index {
18992 return Ok(());
18993 }
18994
18995 let Range { start, end } = locations[destination_location_index];
18996
18997 editor.update_in(cx, |editor, window, cx| {
18998 let effects = SelectionEffects::default();
18999
19000 editor.unfold_ranges(&[start..end], false, false, cx);
19001 editor.change_selections(effects, window, cx, |s| {
19002 s.select_ranges([start..start]);
19003 });
19004 })?;
19005
19006 Ok(())
19007 }))
19008 }
19009
19010 pub fn find_all_references(
19011 &mut self,
19012 action: &FindAllReferences,
19013 window: &mut Window,
19014 cx: &mut Context<Self>,
19015 ) -> Option<Task<Result<Navigated>>> {
19016 let always_open_multibuffer = action.always_open_multibuffer;
19017 let selection = self.selections.newest_anchor();
19018 let multi_buffer = self.buffer.read(cx);
19019 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19020 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
19021 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
19022 let head = selection_offset.head();
19023
19024 let head_anchor = multi_buffer_snapshot.anchor_at(
19025 head,
19026 if head < selection_offset.tail() {
19027 Bias::Right
19028 } else {
19029 Bias::Left
19030 },
19031 );
19032
19033 match self
19034 .find_all_references_task_sources
19035 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
19036 {
19037 Ok(_) => {
19038 log::info!(
19039 "Ignoring repeated FindAllReferences invocation with the position of already running task"
19040 );
19041 return None;
19042 }
19043 Err(i) => {
19044 self.find_all_references_task_sources.insert(i, head_anchor);
19045 }
19046 }
19047
19048 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
19049 let workspace = self.workspace()?;
19050 let project = workspace.read(cx).project().clone();
19051 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
19052 Some(cx.spawn_in(window, async move |editor, cx| {
19053 let _cleanup = cx.on_drop(&editor, move |editor, _| {
19054 if let Ok(i) = editor
19055 .find_all_references_task_sources
19056 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
19057 {
19058 editor.find_all_references_task_sources.remove(i);
19059 }
19060 });
19061
19062 let Some(locations) = references.await? else {
19063 return anyhow::Ok(Navigated::No);
19064 };
19065 let mut locations = cx.update(|_, cx| {
19066 locations
19067 .into_iter()
19068 .map(|location| {
19069 let buffer = location.buffer.read(cx);
19070 (location.buffer, location.range.to_point(buffer))
19071 })
19072 // if special-casing the single-match case, remove ranges
19073 // that intersect current selection
19074 .filter(|(location_buffer, location)| {
19075 if always_open_multibuffer || &buffer != location_buffer {
19076 return true;
19077 }
19078
19079 !location.contains_inclusive(&selection_point.range())
19080 })
19081 .into_group_map()
19082 })?;
19083 if locations.is_empty() {
19084 return anyhow::Ok(Navigated::No);
19085 }
19086 for ranges in locations.values_mut() {
19087 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
19088 ranges.dedup();
19089 }
19090 let mut num_locations = 0;
19091 for ranges in locations.values_mut() {
19092 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
19093 ranges.dedup();
19094 num_locations += ranges.len();
19095 }
19096
19097 if num_locations == 1 && !always_open_multibuffer {
19098 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
19099 let target_range = target_ranges.first().unwrap().clone();
19100
19101 return editor.update_in(cx, |editor, window, cx| {
19102 let range = target_range.to_point(target_buffer.read(cx));
19103 let range = editor.range_for_match(&range);
19104 let range = range.start..range.start;
19105
19106 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
19107 editor.go_to_singleton_buffer_range(range, window, cx);
19108 } else {
19109 let pane = workspace.read(cx).active_pane().clone();
19110 window.defer(cx, move |window, cx| {
19111 let target_editor: Entity<Self> =
19112 workspace.update(cx, |workspace, cx| {
19113 let pane = workspace.active_pane().clone();
19114
19115 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
19116 let keep_old_preview = preview_tabs_settings
19117 .enable_keep_preview_on_code_navigation;
19118 let allow_new_preview = preview_tabs_settings
19119 .enable_preview_file_from_code_navigation;
19120
19121 workspace.open_project_item(
19122 pane,
19123 target_buffer.clone(),
19124 true,
19125 true,
19126 keep_old_preview,
19127 allow_new_preview,
19128 window,
19129 cx,
19130 )
19131 });
19132 target_editor.update(cx, |target_editor, cx| {
19133 // When selecting a definition in a different buffer, disable the nav history
19134 // to avoid creating a history entry at the previous cursor location.
19135 pane.update(cx, |pane, _| pane.disable_history());
19136 target_editor.go_to_singleton_buffer_range(range, window, cx);
19137 pane.update(cx, |pane, _| pane.enable_history());
19138 });
19139 });
19140 }
19141 Navigated::No
19142 });
19143 }
19144
19145 workspace.update_in(cx, |workspace, window, cx| {
19146 let target = locations
19147 .iter()
19148 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
19149 .map(|(buffer, location)| {
19150 buffer
19151 .read(cx)
19152 .text_for_range(location.clone())
19153 .collect::<String>()
19154 })
19155 .filter(|text| !text.contains('\n'))
19156 .unique()
19157 .take(3)
19158 .join(", ");
19159 let title = if target.is_empty() {
19160 "References".to_owned()
19161 } else {
19162 format!("References to {target}")
19163 };
19164 let allow_preview = PreviewTabsSettings::get_global(cx)
19165 .enable_preview_multibuffer_from_code_navigation;
19166 Self::open_locations_in_multibuffer(
19167 workspace,
19168 locations,
19169 title,
19170 false,
19171 allow_preview,
19172 MultibufferSelectionMode::First,
19173 window,
19174 cx,
19175 );
19176 Navigated::Yes
19177 })
19178 }))
19179 }
19180
19181 /// Opens a multibuffer with the given project locations in it.
19182 pub fn open_locations_in_multibuffer(
19183 workspace: &mut Workspace,
19184 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
19185 title: String,
19186 split: bool,
19187 allow_preview: bool,
19188 multibuffer_selection_mode: MultibufferSelectionMode,
19189 window: &mut Window,
19190 cx: &mut Context<Workspace>,
19191 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
19192 if locations.is_empty() {
19193 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
19194 return None;
19195 }
19196
19197 let capability = workspace.project().read(cx).capability();
19198 let mut ranges = <Vec<Range<Anchor>>>::new();
19199
19200 // a key to find existing multibuffer editors with the same set of locations
19201 // to prevent us from opening more and more multibuffer tabs for searches and the like
19202 let mut key = (title.clone(), vec![]);
19203 let excerpt_buffer = cx.new(|cx| {
19204 let key = &mut key.1;
19205 let mut multibuffer = MultiBuffer::new(capability);
19206 for (buffer, mut ranges_for_buffer) in locations {
19207 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
19208 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
19209 multibuffer.set_excerpts_for_path(
19210 PathKey::for_buffer(&buffer, cx),
19211 buffer.clone(),
19212 ranges_for_buffer.clone(),
19213 multibuffer_context_lines(cx),
19214 cx,
19215 );
19216 let snapshot = multibuffer.snapshot(cx);
19217 let buffer_snapshot = buffer.read(cx).snapshot();
19218 ranges.extend(ranges_for_buffer.into_iter().filter_map(|range| {
19219 let text_range = buffer_snapshot.anchor_range_inside(range);
19220 let start = snapshot.anchor_in_buffer(text_range.start)?;
19221 let end = snapshot.anchor_in_buffer(text_range.end)?;
19222 Some(start..end)
19223 }))
19224 }
19225
19226 multibuffer.with_title(title)
19227 });
19228 let existing = workspace.active_pane().update(cx, |pane, cx| {
19229 pane.items()
19230 .filter_map(|item| item.downcast::<Editor>())
19231 .find(|editor| {
19232 editor
19233 .read(cx)
19234 .lookup_key
19235 .as_ref()
19236 .and_then(|it| {
19237 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
19238 })
19239 .is_some_and(|it| *it == key)
19240 })
19241 });
19242 let was_existing = existing.is_some();
19243 let editor = existing.unwrap_or_else(|| {
19244 cx.new(|cx| {
19245 let mut editor = Editor::for_multibuffer(
19246 excerpt_buffer,
19247 Some(workspace.project().clone()),
19248 window,
19249 cx,
19250 );
19251 editor.lookup_key = Some(Box::new(key));
19252 editor
19253 })
19254 });
19255 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
19256 MultibufferSelectionMode::First => {
19257 if let Some(first_range) = ranges.first() {
19258 editor.change_selections(
19259 SelectionEffects::no_scroll(),
19260 window,
19261 cx,
19262 |selections| {
19263 selections.clear_disjoint();
19264 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
19265 },
19266 );
19267 }
19268 editor.highlight_background(
19269 HighlightKey::Editor,
19270 &ranges,
19271 |_, theme| theme.colors().editor_highlighted_line_background,
19272 cx,
19273 );
19274 }
19275 MultibufferSelectionMode::All => {
19276 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19277 selections.clear_disjoint();
19278 selections.select_anchor_ranges(ranges);
19279 });
19280 }
19281 });
19282
19283 let item = Box::new(editor.clone());
19284
19285 let pane = if split {
19286 workspace.adjacent_pane(window, cx)
19287 } else {
19288 workspace.active_pane().clone()
19289 };
19290 let activate_pane = split;
19291
19292 let mut destination_index = None;
19293 pane.update(cx, |pane, cx| {
19294 if allow_preview && !was_existing {
19295 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
19296 }
19297 if was_existing && !allow_preview {
19298 pane.unpreview_item_if_preview(item.item_id());
19299 }
19300 pane.add_item(item, activate_pane, true, destination_index, window, cx);
19301 });
19302
19303 Some((editor, pane))
19304 }
19305
19306 pub fn rename(
19307 &mut self,
19308 _: &Rename,
19309 window: &mut Window,
19310 cx: &mut Context<Self>,
19311 ) -> Option<Task<Result<()>>> {
19312 use language::ToOffset as _;
19313
19314 let provider = self.semantics_provider.clone()?;
19315 let selection = self.selections.newest_anchor().clone();
19316 let (cursor_buffer, cursor_buffer_position) = self
19317 .buffer
19318 .read(cx)
19319 .text_anchor_for_position(selection.head(), cx)?;
19320 let (tail_buffer, cursor_buffer_position_end) = self
19321 .buffer
19322 .read(cx)
19323 .text_anchor_for_position(selection.tail(), cx)?;
19324 if tail_buffer != cursor_buffer {
19325 return None;
19326 }
19327
19328 let snapshot = cursor_buffer.read(cx).snapshot();
19329 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
19330 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
19331 let prepare_rename = provider.range_for_rename(&cursor_buffer, cursor_buffer_position, cx);
19332 drop(snapshot);
19333
19334 Some(cx.spawn_in(window, async move |this, cx| {
19335 let rename_range = prepare_rename.await?;
19336 if let Some(rename_range) = rename_range {
19337 this.update_in(cx, |this, window, cx| {
19338 let snapshot = cursor_buffer.read(cx).snapshot();
19339 let rename_buffer_range = rename_range.to_offset(&snapshot);
19340 let cursor_offset_in_rename_range =
19341 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
19342 let cursor_offset_in_rename_range_end =
19343 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
19344
19345 this.take_rename(false, window, cx);
19346 let buffer = this.buffer.read(cx).read(cx);
19347 let cursor_offset = selection.head().to_offset(&buffer);
19348 let rename_start =
19349 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
19350 let rename_end = rename_start + rename_buffer_range.len();
19351 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
19352 let mut old_highlight_id = None;
19353 let old_name: Arc<str> = buffer
19354 .chunks(
19355 rename_start..rename_end,
19356 LanguageAwareStyling {
19357 tree_sitter: true,
19358 diagnostics: true,
19359 },
19360 )
19361 .map(|chunk| {
19362 if old_highlight_id.is_none() {
19363 old_highlight_id = chunk.syntax_highlight_id;
19364 }
19365 chunk.text
19366 })
19367 .collect::<String>()
19368 .into();
19369
19370 drop(buffer);
19371
19372 // Position the selection in the rename editor so that it matches the current selection.
19373 this.show_local_selections = false;
19374 let rename_editor = cx.new(|cx| {
19375 let mut editor = Editor::single_line(window, cx);
19376 editor.buffer.update(cx, |buffer, cx| {
19377 buffer.edit(
19378 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
19379 None,
19380 cx,
19381 )
19382 });
19383 let cursor_offset_in_rename_range =
19384 MultiBufferOffset(cursor_offset_in_rename_range);
19385 let cursor_offset_in_rename_range_end =
19386 MultiBufferOffset(cursor_offset_in_rename_range_end);
19387 let rename_selection_range = match cursor_offset_in_rename_range
19388 .cmp(&cursor_offset_in_rename_range_end)
19389 {
19390 Ordering::Equal => {
19391 editor.select_all(&SelectAll, window, cx);
19392 return editor;
19393 }
19394 Ordering::Less => {
19395 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
19396 }
19397 Ordering::Greater => {
19398 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
19399 }
19400 };
19401 if rename_selection_range.end.0 > old_name.len() {
19402 editor.select_all(&SelectAll, window, cx);
19403 } else {
19404 editor.change_selections(Default::default(), window, cx, |s| {
19405 s.select_ranges([rename_selection_range]);
19406 });
19407 }
19408 editor
19409 });
19410 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19411 if e == &EditorEvent::Focused {
19412 cx.emit(EditorEvent::FocusedIn)
19413 }
19414 })
19415 .detach();
19416
19417 let write_highlights =
19418 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19419 let read_highlights =
19420 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19421 let ranges = write_highlights
19422 .iter()
19423 .flat_map(|(_, ranges)| ranges.iter())
19424 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19425 .cloned()
19426 .collect();
19427
19428 this.highlight_text(
19429 HighlightKey::Rename,
19430 ranges,
19431 HighlightStyle {
19432 fade_out: Some(0.6),
19433 ..Default::default()
19434 },
19435 cx,
19436 );
19437 let rename_focus_handle = rename_editor.focus_handle(cx);
19438 window.focus(&rename_focus_handle, cx);
19439 let block_id = this.insert_blocks(
19440 [BlockProperties {
19441 style: BlockStyle::Flex,
19442 placement: BlockPlacement::Below(range.start),
19443 height: Some(1),
19444 render: Arc::new({
19445 let rename_editor = rename_editor.clone();
19446 move |cx: &mut BlockContext| {
19447 let mut text_style = cx.editor_style.text.clone();
19448 if let Some(highlight_style) = old_highlight_id
19449 .and_then(|h| cx.editor_style.syntax.get(h).cloned())
19450 {
19451 text_style = text_style.highlight(highlight_style);
19452 }
19453 div()
19454 .block_mouse_except_scroll()
19455 .pl(cx.anchor_x)
19456 .child(EditorElement::new(
19457 &rename_editor,
19458 EditorStyle {
19459 background: cx.theme().system().transparent,
19460 local_player: cx.editor_style.local_player,
19461 text: text_style,
19462 scrollbar_width: cx.editor_style.scrollbar_width,
19463 syntax: cx.editor_style.syntax.clone(),
19464 status: cx.editor_style.status.clone(),
19465 inlay_hints_style: HighlightStyle {
19466 font_weight: Some(FontWeight::BOLD),
19467 ..make_inlay_hints_style(cx.app)
19468 },
19469 edit_prediction_styles: make_suggestion_styles(
19470 cx.app,
19471 ),
19472 ..EditorStyle::default()
19473 },
19474 ))
19475 .into_any_element()
19476 }
19477 }),
19478 priority: 0,
19479 }],
19480 Some(Autoscroll::fit()),
19481 cx,
19482 )[0];
19483 this.pending_rename = Some(RenameState {
19484 range,
19485 old_name,
19486 editor: rename_editor,
19487 block_id,
19488 });
19489 })?;
19490 }
19491
19492 Ok(())
19493 }))
19494 }
19495
19496 pub fn confirm_rename(
19497 &mut self,
19498 _: &ConfirmRename,
19499 window: &mut Window,
19500 cx: &mut Context<Self>,
19501 ) -> Option<Task<Result<()>>> {
19502 let rename = self.take_rename(false, window, cx)?;
19503 let workspace = self.workspace()?.downgrade();
19504 let (buffer, start) = self
19505 .buffer
19506 .read(cx)
19507 .text_anchor_for_position(rename.range.start, cx)?;
19508 let (end_buffer, _) = self
19509 .buffer
19510 .read(cx)
19511 .text_anchor_for_position(rename.range.end, cx)?;
19512 if buffer != end_buffer {
19513 return None;
19514 }
19515
19516 let old_name = rename.old_name;
19517 let new_name = rename.editor.read(cx).text(cx);
19518
19519 let rename = self.semantics_provider.as_ref()?.perform_rename(
19520 &buffer,
19521 start,
19522 new_name.clone(),
19523 cx,
19524 )?;
19525
19526 Some(cx.spawn_in(window, async move |editor, cx| {
19527 let project_transaction = rename.await?;
19528 Self::open_project_transaction(
19529 &editor,
19530 workspace,
19531 project_transaction,
19532 format!("Rename: {} → {}", old_name, new_name),
19533 cx,
19534 )
19535 .await?;
19536
19537 editor.update(cx, |editor, cx| {
19538 editor.refresh_document_highlights(cx);
19539 })?;
19540 Ok(())
19541 }))
19542 }
19543
19544 fn take_rename(
19545 &mut self,
19546 moving_cursor: bool,
19547 window: &mut Window,
19548 cx: &mut Context<Self>,
19549 ) -> Option<RenameState> {
19550 let rename = self.pending_rename.take()?;
19551 if rename.editor.focus_handle(cx).is_focused(window) {
19552 window.focus(&self.focus_handle, cx);
19553 }
19554
19555 self.remove_blocks(
19556 [rename.block_id].into_iter().collect(),
19557 Some(Autoscroll::fit()),
19558 cx,
19559 );
19560 self.clear_highlights(HighlightKey::Rename, cx);
19561 self.show_local_selections = true;
19562
19563 if moving_cursor {
19564 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19565 editor
19566 .selections
19567 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19568 .head()
19569 });
19570
19571 // Update the selection to match the position of the selection inside
19572 // the rename editor.
19573 let snapshot = self.buffer.read(cx).read(cx);
19574 let rename_range = rename.range.to_offset(&snapshot);
19575 let cursor_in_editor = snapshot
19576 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19577 .min(rename_range.end);
19578 drop(snapshot);
19579
19580 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19581 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19582 });
19583 } else {
19584 self.refresh_document_highlights(cx);
19585 }
19586
19587 Some(rename)
19588 }
19589
19590 pub fn pending_rename(&self) -> Option<&RenameState> {
19591 self.pending_rename.as_ref()
19592 }
19593
19594 fn format(
19595 &mut self,
19596 _: &Format,
19597 window: &mut Window,
19598 cx: &mut Context<Self>,
19599 ) -> Option<Task<Result<()>>> {
19600 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19601
19602 let project = match &self.project {
19603 Some(project) => project.clone(),
19604 None => return None,
19605 };
19606
19607 Some(self.perform_format(
19608 project,
19609 FormatTrigger::Manual,
19610 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19611 window,
19612 cx,
19613 ))
19614 }
19615
19616 fn format_selections(
19617 &mut self,
19618 _: &FormatSelections,
19619 window: &mut Window,
19620 cx: &mut Context<Self>,
19621 ) -> Option<Task<Result<()>>> {
19622 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19623
19624 let project = match &self.project {
19625 Some(project) => project.clone(),
19626 None => return None,
19627 };
19628
19629 let ranges = self
19630 .selections
19631 .all_adjusted(&self.display_snapshot(cx))
19632 .into_iter()
19633 .map(|selection| selection.range())
19634 .collect_vec();
19635
19636 Some(self.perform_format(
19637 project,
19638 FormatTrigger::Manual,
19639 FormatTarget::Ranges(ranges),
19640 window,
19641 cx,
19642 ))
19643 }
19644
19645 fn perform_format(
19646 &mut self,
19647 project: Entity<Project>,
19648 trigger: FormatTrigger,
19649 target: FormatTarget,
19650 window: &mut Window,
19651 cx: &mut Context<Self>,
19652 ) -> Task<Result<()>> {
19653 let buffer = self.buffer.clone();
19654 let (buffers, target) = match target {
19655 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19656 FormatTarget::Ranges(selection_ranges) => {
19657 let multi_buffer = buffer.read(cx);
19658 let snapshot = multi_buffer.read(cx);
19659 let mut buffers = HashSet::default();
19660 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19661 BTreeMap::new();
19662 for selection_range in selection_ranges {
19663 for (buffer_snapshot, buffer_range, _) in
19664 snapshot.range_to_buffer_ranges(selection_range.start..selection_range.end)
19665 {
19666 let buffer_id = buffer_snapshot.remote_id();
19667 let start = buffer_snapshot.anchor_before(buffer_range.start);
19668 let end = buffer_snapshot.anchor_after(buffer_range.end);
19669 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19670 buffer_id_to_ranges
19671 .entry(buffer_id)
19672 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19673 .or_insert_with(|| vec![start..end]);
19674 }
19675 }
19676 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19677 }
19678 };
19679
19680 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19681 let selections_prev = transaction_id_prev
19682 .and_then(|transaction_id_prev| {
19683 // default to selections as they were after the last edit, if we have them,
19684 // instead of how they are now.
19685 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19686 // will take you back to where you made the last edit, instead of staying where you scrolled
19687 self.selection_history
19688 .transaction(transaction_id_prev)
19689 .map(|t| t.0.clone())
19690 })
19691 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19692
19693 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19694 let format = project.update(cx, |project, cx| {
19695 project.format(buffers, target, true, trigger, cx)
19696 });
19697
19698 cx.spawn_in(window, async move |editor, cx| {
19699 let transaction = futures::select_biased! {
19700 transaction = format.log_err().fuse() => transaction,
19701 () = timeout => {
19702 log::warn!("timed out waiting for formatting");
19703 None
19704 }
19705 };
19706
19707 buffer.update(cx, |buffer, cx| {
19708 if let Some(transaction) = transaction
19709 && !buffer.is_singleton()
19710 {
19711 buffer.push_transaction(&transaction.0, cx);
19712 }
19713 cx.notify();
19714 });
19715
19716 if let Some(transaction_id_now) =
19717 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19718 {
19719 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19720 if has_new_transaction {
19721 editor
19722 .update(cx, |editor, _| {
19723 editor
19724 .selection_history
19725 .insert_transaction(transaction_id_now, selections_prev);
19726 })
19727 .ok();
19728 }
19729 }
19730
19731 Ok(())
19732 })
19733 }
19734
19735 fn organize_imports(
19736 &mut self,
19737 _: &OrganizeImports,
19738 window: &mut Window,
19739 cx: &mut Context<Self>,
19740 ) -> Option<Task<Result<()>>> {
19741 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19742 let project = match &self.project {
19743 Some(project) => project.clone(),
19744 None => return None,
19745 };
19746 Some(self.perform_code_action_kind(
19747 project,
19748 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19749 window,
19750 cx,
19751 ))
19752 }
19753
19754 fn perform_code_action_kind(
19755 &mut self,
19756 project: Entity<Project>,
19757 kind: CodeActionKind,
19758 window: &mut Window,
19759 cx: &mut Context<Self>,
19760 ) -> Task<Result<()>> {
19761 let buffer = self.buffer.clone();
19762 let buffers = buffer.read(cx).all_buffers();
19763 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19764 let apply_action = project.update(cx, |project, cx| {
19765 project.apply_code_action_kind(buffers, kind, true, cx)
19766 });
19767 cx.spawn_in(window, async move |_, cx| {
19768 let transaction = futures::select_biased! {
19769 () = timeout => {
19770 log::warn!("timed out waiting for executing code action");
19771 None
19772 }
19773 transaction = apply_action.log_err().fuse() => transaction,
19774 };
19775 buffer.update(cx, |buffer, cx| {
19776 // check if we need this
19777 if let Some(transaction) = transaction
19778 && !buffer.is_singleton()
19779 {
19780 buffer.push_transaction(&transaction.0, cx);
19781 }
19782 cx.notify();
19783 });
19784 Ok(())
19785 })
19786 }
19787
19788 pub fn restart_language_server(
19789 &mut self,
19790 _: &RestartLanguageServer,
19791 _: &mut Window,
19792 cx: &mut Context<Self>,
19793 ) {
19794 if let Some(project) = self.project.clone() {
19795 self.buffer.update(cx, |multi_buffer, cx| {
19796 project.update(cx, |project, cx| {
19797 project.restart_language_servers_for_buffers(
19798 multi_buffer.all_buffers().into_iter().collect(),
19799 HashSet::default(),
19800 cx,
19801 );
19802 });
19803 })
19804 }
19805 }
19806
19807 pub fn stop_language_server(
19808 &mut self,
19809 _: &StopLanguageServer,
19810 _: &mut Window,
19811 cx: &mut Context<Self>,
19812 ) {
19813 if let Some(project) = self.project.clone() {
19814 self.buffer.update(cx, |multi_buffer, cx| {
19815 project.update(cx, |project, cx| {
19816 project.stop_language_servers_for_buffers(
19817 multi_buffer.all_buffers().into_iter().collect(),
19818 HashSet::default(),
19819 cx,
19820 );
19821 });
19822 });
19823 }
19824 }
19825
19826 fn cancel_language_server_work(
19827 workspace: &mut Workspace,
19828 _: &actions::CancelLanguageServerWork,
19829 _: &mut Window,
19830 cx: &mut Context<Workspace>,
19831 ) {
19832 let project = workspace.project();
19833 let buffers = workspace
19834 .active_item(cx)
19835 .and_then(|item| item.act_as::<Editor>(cx))
19836 .map_or(HashSet::default(), |editor| {
19837 editor.read(cx).buffer.read(cx).all_buffers()
19838 });
19839 project.update(cx, |project, cx| {
19840 project.cancel_language_server_work_for_buffers(buffers, cx);
19841 });
19842 }
19843
19844 fn show_character_palette(
19845 &mut self,
19846 _: &ShowCharacterPalette,
19847 window: &mut Window,
19848 _: &mut Context<Self>,
19849 ) {
19850 window.show_character_palette();
19851 }
19852
19853 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19854 if !self.diagnostics_enabled() {
19855 return;
19856 }
19857
19858 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19859 let buffer = self.buffer.read(cx).snapshot(cx);
19860 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19861 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19862 let is_valid = buffer
19863 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19864 .any(|entry| {
19865 entry.diagnostic.is_primary
19866 && !entry.range.is_empty()
19867 && entry.range.start == primary_range_start
19868 && entry.diagnostic.message == active_diagnostics.active_message
19869 });
19870
19871 if !is_valid {
19872 self.dismiss_diagnostics(cx);
19873 }
19874 }
19875 }
19876
19877 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19878 match &self.active_diagnostics {
19879 ActiveDiagnostic::Group(group) => Some(group),
19880 _ => None,
19881 }
19882 }
19883
19884 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19885 if !self.diagnostics_enabled() {
19886 return;
19887 }
19888 self.dismiss_diagnostics(cx);
19889 self.active_diagnostics = ActiveDiagnostic::All;
19890 }
19891
19892 fn activate_diagnostics(
19893 &mut self,
19894 buffer_id: BufferId,
19895 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19896 window: &mut Window,
19897 cx: &mut Context<Self>,
19898 ) {
19899 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19900 return;
19901 }
19902 self.dismiss_diagnostics(cx);
19903 let snapshot = self.snapshot(window, cx);
19904 let buffer = self.buffer.read(cx).snapshot(cx);
19905 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19906 return;
19907 };
19908
19909 let diagnostic_group = buffer
19910 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19911 .collect::<Vec<_>>();
19912
19913 let language_registry = self
19914 .project()
19915 .map(|project| project.read(cx).languages().clone());
19916
19917 let blocks = renderer.render_group(
19918 diagnostic_group,
19919 buffer_id,
19920 snapshot,
19921 cx.weak_entity(),
19922 language_registry,
19923 cx,
19924 );
19925
19926 let blocks = self.display_map.update(cx, |display_map, cx| {
19927 display_map.insert_blocks(blocks, cx).into_iter().collect()
19928 });
19929 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19930 active_range: buffer.anchor_before(diagnostic.range.start)
19931 ..buffer.anchor_after(diagnostic.range.end),
19932 active_message: diagnostic.diagnostic.message.clone(),
19933 group_id: diagnostic.diagnostic.group_id,
19934 blocks,
19935 });
19936 cx.notify();
19937 }
19938
19939 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19940 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19941 return;
19942 };
19943
19944 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19945 if let ActiveDiagnostic::Group(group) = prev {
19946 self.display_map.update(cx, |display_map, cx| {
19947 display_map.remove_blocks(group.blocks, cx);
19948 });
19949 cx.notify();
19950 }
19951 }
19952
19953 /// Disable inline diagnostics rendering for this editor.
19954 pub fn disable_inline_diagnostics(&mut self) {
19955 self.inline_diagnostics_enabled = false;
19956 self.inline_diagnostics_update = Task::ready(());
19957 self.inline_diagnostics.clear();
19958 }
19959
19960 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19961 self.diagnostics_enabled = false;
19962 self.dismiss_diagnostics(cx);
19963 self.inline_diagnostics_update = Task::ready(());
19964 self.inline_diagnostics.clear();
19965 }
19966
19967 pub fn disable_word_completions(&mut self) {
19968 self.word_completions_enabled = false;
19969 }
19970
19971 pub fn diagnostics_enabled(&self) -> bool {
19972 self.diagnostics_enabled && self.lsp_data_enabled()
19973 }
19974
19975 pub fn inline_diagnostics_enabled(&self) -> bool {
19976 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19977 }
19978
19979 pub fn show_inline_diagnostics(&self) -> bool {
19980 self.show_inline_diagnostics
19981 }
19982
19983 pub fn toggle_inline_diagnostics(
19984 &mut self,
19985 _: &ToggleInlineDiagnostics,
19986 window: &mut Window,
19987 cx: &mut Context<Editor>,
19988 ) {
19989 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19990 self.refresh_inline_diagnostics(false, window, cx);
19991 }
19992
19993 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19994 self.diagnostics_max_severity = severity;
19995 self.display_map.update(cx, |display_map, _| {
19996 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19997 });
19998 }
19999
20000 pub fn toggle_diagnostics(
20001 &mut self,
20002 _: &ToggleDiagnostics,
20003 window: &mut Window,
20004 cx: &mut Context<Editor>,
20005 ) {
20006 if !self.diagnostics_enabled() {
20007 return;
20008 }
20009
20010 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
20011 EditorSettings::get_global(cx)
20012 .diagnostics_max_severity
20013 .filter(|severity| severity != &DiagnosticSeverity::Off)
20014 .unwrap_or(DiagnosticSeverity::Hint)
20015 } else {
20016 DiagnosticSeverity::Off
20017 };
20018 self.set_max_diagnostics_severity(new_severity, cx);
20019 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
20020 self.active_diagnostics = ActiveDiagnostic::None;
20021 self.inline_diagnostics_update = Task::ready(());
20022 self.inline_diagnostics.clear();
20023 } else {
20024 self.refresh_inline_diagnostics(false, window, cx);
20025 }
20026
20027 cx.notify();
20028 }
20029
20030 pub fn toggle_minimap(
20031 &mut self,
20032 _: &ToggleMinimap,
20033 window: &mut Window,
20034 cx: &mut Context<Editor>,
20035 ) {
20036 if self.supports_minimap(cx) {
20037 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
20038 }
20039 }
20040
20041 fn refresh_inline_diagnostics(
20042 &mut self,
20043 debounce: bool,
20044 window: &mut Window,
20045 cx: &mut Context<Self>,
20046 ) {
20047 let max_severity = ProjectSettings::get_global(cx)
20048 .diagnostics
20049 .inline
20050 .max_severity
20051 .unwrap_or(self.diagnostics_max_severity);
20052
20053 if !self.inline_diagnostics_enabled()
20054 || !self.diagnostics_enabled()
20055 || !self.show_inline_diagnostics
20056 || max_severity == DiagnosticSeverity::Off
20057 {
20058 self.inline_diagnostics_update = Task::ready(());
20059 self.inline_diagnostics.clear();
20060 return;
20061 }
20062
20063 let debounce_ms = ProjectSettings::get_global(cx)
20064 .diagnostics
20065 .inline
20066 .update_debounce_ms;
20067 let debounce = if debounce && debounce_ms > 0 {
20068 Some(Duration::from_millis(debounce_ms))
20069 } else {
20070 None
20071 };
20072 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
20073 if let Some(debounce) = debounce {
20074 cx.background_executor().timer(debounce).await;
20075 }
20076 let Some(snapshot) = editor.upgrade().map(|editor| {
20077 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
20078 }) else {
20079 return;
20080 };
20081
20082 let new_inline_diagnostics = cx
20083 .background_spawn(async move {
20084 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
20085 for diagnostic_entry in
20086 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
20087 {
20088 let message = diagnostic_entry
20089 .diagnostic
20090 .message
20091 .split_once('\n')
20092 .map(|(line, _)| line)
20093 .map(SharedString::new)
20094 .unwrap_or_else(|| {
20095 SharedString::new(&*diagnostic_entry.diagnostic.message)
20096 });
20097 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
20098 let (Ok(i) | Err(i)) = inline_diagnostics
20099 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
20100 inline_diagnostics.insert(
20101 i,
20102 (
20103 start_anchor,
20104 InlineDiagnostic {
20105 message,
20106 group_id: diagnostic_entry.diagnostic.group_id,
20107 start: diagnostic_entry.range.start.to_point(&snapshot),
20108 is_primary: diagnostic_entry.diagnostic.is_primary,
20109 severity: diagnostic_entry.diagnostic.severity,
20110 },
20111 ),
20112 );
20113 }
20114 inline_diagnostics
20115 })
20116 .await;
20117
20118 editor
20119 .update(cx, |editor, cx| {
20120 editor.inline_diagnostics = new_inline_diagnostics;
20121 cx.notify();
20122 })
20123 .ok();
20124 });
20125 }
20126
20127 fn pull_diagnostics(
20128 &mut self,
20129 buffer_id: BufferId,
20130 _window: &Window,
20131 cx: &mut Context<Self>,
20132 ) -> Option<()> {
20133 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
20134 // skip any LSP updates for it.
20135
20136 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
20137 return None;
20138 }
20139 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
20140 .diagnostics
20141 .lsp_pull_diagnostics;
20142 if !pull_diagnostics_settings.enabled {
20143 return None;
20144 }
20145 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
20146 let project = self.project()?.downgrade();
20147 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
20148
20149 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
20150 cx.background_executor().timer(debounce).await;
20151 if let Ok(task) = project.update(cx, |project, cx| {
20152 project.lsp_store().update(cx, |lsp_store, cx| {
20153 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
20154 })
20155 }) {
20156 task.await.log_err();
20157 }
20158 project
20159 .update(cx, |project, cx| {
20160 project.lsp_store().update(cx, |lsp_store, cx| {
20161 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
20162 })
20163 })
20164 .log_err();
20165 });
20166
20167 Some(())
20168 }
20169
20170 pub fn set_selections_from_remote(
20171 &mut self,
20172 selections: Vec<Selection<Anchor>>,
20173 pending_selection: Option<Selection<Anchor>>,
20174 window: &mut Window,
20175 cx: &mut Context<Self>,
20176 ) {
20177 let old_cursor_position = self.selections.newest_anchor().head();
20178 self.selections
20179 .change_with(&self.display_snapshot(cx), |s| {
20180 s.select_anchors(selections);
20181 if let Some(pending_selection) = pending_selection {
20182 s.set_pending(pending_selection, SelectMode::Character);
20183 } else {
20184 s.clear_pending();
20185 }
20186 });
20187 self.selections_did_change(
20188 false,
20189 &old_cursor_position,
20190 SelectionEffects::default(),
20191 window,
20192 cx,
20193 );
20194 }
20195
20196 pub fn transact(
20197 &mut self,
20198 window: &mut Window,
20199 cx: &mut Context<Self>,
20200 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
20201 ) -> Option<TransactionId> {
20202 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
20203 this.start_transaction_at(Instant::now(), window, cx);
20204 update(this, window, cx);
20205 this.end_transaction_at(Instant::now(), cx)
20206 })
20207 }
20208
20209 pub fn start_transaction_at(
20210 &mut self,
20211 now: Instant,
20212 window: &mut Window,
20213 cx: &mut Context<Self>,
20214 ) -> Option<TransactionId> {
20215 self.end_selection(window, cx);
20216 if let Some(tx_id) = self
20217 .buffer
20218 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
20219 {
20220 self.selection_history
20221 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
20222 cx.emit(EditorEvent::TransactionBegun {
20223 transaction_id: tx_id,
20224 });
20225 Some(tx_id)
20226 } else {
20227 None
20228 }
20229 }
20230
20231 pub fn end_transaction_at(
20232 &mut self,
20233 now: Instant,
20234 cx: &mut Context<Self>,
20235 ) -> Option<TransactionId> {
20236 if let Some(transaction_id) = self
20237 .buffer
20238 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
20239 {
20240 if let Some((_, end_selections)) =
20241 self.selection_history.transaction_mut(transaction_id)
20242 {
20243 *end_selections = Some(self.selections.disjoint_anchors_arc());
20244 } else {
20245 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
20246 }
20247
20248 cx.emit(EditorEvent::Edited { transaction_id });
20249 Some(transaction_id)
20250 } else {
20251 None
20252 }
20253 }
20254
20255 pub fn modify_transaction_selection_history(
20256 &mut self,
20257 transaction_id: TransactionId,
20258 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
20259 ) -> bool {
20260 self.selection_history
20261 .transaction_mut(transaction_id)
20262 .map(modify)
20263 .is_some()
20264 }
20265
20266 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
20267 if self.selection_mark_mode {
20268 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20269 s.move_with(&mut |_, sel| {
20270 sel.collapse_to(sel.head(), SelectionGoal::None);
20271 });
20272 })
20273 }
20274 self.selection_mark_mode = true;
20275 cx.notify();
20276 }
20277
20278 pub fn swap_selection_ends(
20279 &mut self,
20280 _: &actions::SwapSelectionEnds,
20281 window: &mut Window,
20282 cx: &mut Context<Self>,
20283 ) {
20284 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20285 s.move_with(&mut |_, sel| {
20286 if sel.start != sel.end {
20287 sel.reversed = !sel.reversed
20288 }
20289 });
20290 });
20291 self.request_autoscroll(Autoscroll::newest(), cx);
20292 cx.notify();
20293 }
20294
20295 pub fn toggle_focus(
20296 workspace: &mut Workspace,
20297 _: &actions::ToggleFocus,
20298 window: &mut Window,
20299 cx: &mut Context<Workspace>,
20300 ) {
20301 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
20302 return;
20303 };
20304 workspace.activate_item(&item, true, true, window, cx);
20305 }
20306
20307 pub fn toggle_fold(
20308 &mut self,
20309 _: &actions::ToggleFold,
20310 window: &mut Window,
20311 cx: &mut Context<Self>,
20312 ) {
20313 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20314 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20315 let selection = self.selections.newest::<Point>(&display_map);
20316
20317 let range = if selection.is_empty() {
20318 let point = selection.head().to_display_point(&display_map);
20319 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20320 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20321 .to_point(&display_map);
20322 start..end
20323 } else {
20324 selection.range()
20325 };
20326 if display_map.folds_in_range(range).next().is_some() {
20327 self.unfold_lines(&Default::default(), window, cx)
20328 } else {
20329 self.fold(&Default::default(), window, cx)
20330 }
20331 } else {
20332 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20333 let buffer_ids: HashSet<_> = self
20334 .selections
20335 .disjoint_anchor_ranges()
20336 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20337 .collect();
20338
20339 let should_unfold = buffer_ids
20340 .iter()
20341 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20342
20343 for buffer_id in buffer_ids {
20344 if should_unfold {
20345 self.unfold_buffer(buffer_id, cx);
20346 } else {
20347 self.fold_buffer(buffer_id, cx);
20348 }
20349 }
20350 }
20351 }
20352
20353 pub fn toggle_fold_recursive(
20354 &mut self,
20355 _: &actions::ToggleFoldRecursive,
20356 window: &mut Window,
20357 cx: &mut Context<Self>,
20358 ) {
20359 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20360
20361 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20362 let range = if selection.is_empty() {
20363 let point = selection.head().to_display_point(&display_map);
20364 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20365 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20366 .to_point(&display_map);
20367 start..end
20368 } else {
20369 selection.range()
20370 };
20371 if display_map.folds_in_range(range).next().is_some() {
20372 self.unfold_recursive(&Default::default(), window, cx)
20373 } else {
20374 self.fold_recursive(&Default::default(), window, cx)
20375 }
20376 }
20377
20378 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
20379 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20380 let mut to_fold = Vec::new();
20381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20382 let selections = self.selections.all_adjusted(&display_map);
20383
20384 for selection in selections {
20385 let range = selection.range().sorted();
20386 let buffer_start_row = range.start.row;
20387
20388 if range.start.row != range.end.row {
20389 let mut found = false;
20390 let mut row = range.start.row;
20391 while row <= range.end.row {
20392 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20393 {
20394 found = true;
20395 row = crease.range().end.row + 1;
20396 to_fold.push(crease);
20397 } else {
20398 row += 1
20399 }
20400 }
20401 if found {
20402 continue;
20403 }
20404 }
20405
20406 for row in (0..=range.start.row).rev() {
20407 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20408 && crease.range().end.row >= buffer_start_row
20409 {
20410 to_fold.push(crease);
20411 if row <= range.start.row {
20412 break;
20413 }
20414 }
20415 }
20416 }
20417
20418 self.fold_creases(to_fold, true, window, cx);
20419 } else {
20420 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20421 let buffer_ids = self
20422 .selections
20423 .disjoint_anchor_ranges()
20424 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20425 .collect::<HashSet<_>>();
20426 for buffer_id in buffer_ids {
20427 self.fold_buffer(buffer_id, cx);
20428 }
20429 }
20430 }
20431
20432 pub fn toggle_fold_all(
20433 &mut self,
20434 _: &actions::ToggleFoldAll,
20435 window: &mut Window,
20436 cx: &mut Context<Self>,
20437 ) {
20438 let has_folds = if self.buffer.read(cx).is_singleton() {
20439 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20440 let has_folds = display_map
20441 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20442 .next()
20443 .is_some();
20444 has_folds
20445 } else {
20446 let snapshot = self.buffer.read(cx).snapshot(cx);
20447 let has_folds = snapshot
20448 .all_buffer_ids()
20449 .any(|buffer_id| self.is_buffer_folded(buffer_id, cx));
20450 has_folds
20451 };
20452
20453 if has_folds {
20454 self.unfold_all(&actions::UnfoldAll, window, cx);
20455 } else {
20456 self.fold_all(&actions::FoldAll, window, cx);
20457 }
20458 }
20459
20460 fn fold_at_level(
20461 &mut self,
20462 fold_at: &FoldAtLevel,
20463 window: &mut Window,
20464 cx: &mut Context<Self>,
20465 ) {
20466 if !self.buffer.read(cx).is_singleton() {
20467 return;
20468 }
20469
20470 let fold_at_level = fold_at.0;
20471 let snapshot = self.buffer.read(cx).snapshot(cx);
20472 let mut to_fold = Vec::new();
20473 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20474
20475 let row_ranges_to_keep: Vec<Range<u32>> = self
20476 .selections
20477 .all::<Point>(&self.display_snapshot(cx))
20478 .into_iter()
20479 .map(|sel| sel.start.row..sel.end.row)
20480 .collect();
20481
20482 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20483 while start_row < end_row {
20484 match self
20485 .snapshot(window, cx)
20486 .crease_for_buffer_row(MultiBufferRow(start_row))
20487 {
20488 Some(crease) => {
20489 let nested_start_row = crease.range().start.row + 1;
20490 let nested_end_row = crease.range().end.row;
20491
20492 if current_level < fold_at_level {
20493 stack.push((nested_start_row, nested_end_row, current_level + 1));
20494 } else if current_level == fold_at_level {
20495 // Fold iff there is no selection completely contained within the fold region
20496 if !row_ranges_to_keep.iter().any(|selection| {
20497 selection.end >= nested_start_row
20498 && selection.start <= nested_end_row
20499 }) {
20500 to_fold.push(crease);
20501 }
20502 }
20503
20504 start_row = nested_end_row + 1;
20505 }
20506 None => start_row += 1,
20507 }
20508 }
20509 }
20510
20511 self.fold_creases(to_fold, true, window, cx);
20512 }
20513
20514 pub fn fold_at_level_1(
20515 &mut self,
20516 _: &actions::FoldAtLevel1,
20517 window: &mut Window,
20518 cx: &mut Context<Self>,
20519 ) {
20520 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20521 }
20522
20523 pub fn fold_at_level_2(
20524 &mut self,
20525 _: &actions::FoldAtLevel2,
20526 window: &mut Window,
20527 cx: &mut Context<Self>,
20528 ) {
20529 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20530 }
20531
20532 pub fn fold_at_level_3(
20533 &mut self,
20534 _: &actions::FoldAtLevel3,
20535 window: &mut Window,
20536 cx: &mut Context<Self>,
20537 ) {
20538 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20539 }
20540
20541 pub fn fold_at_level_4(
20542 &mut self,
20543 _: &actions::FoldAtLevel4,
20544 window: &mut Window,
20545 cx: &mut Context<Self>,
20546 ) {
20547 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20548 }
20549
20550 pub fn fold_at_level_5(
20551 &mut self,
20552 _: &actions::FoldAtLevel5,
20553 window: &mut Window,
20554 cx: &mut Context<Self>,
20555 ) {
20556 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20557 }
20558
20559 pub fn fold_at_level_6(
20560 &mut self,
20561 _: &actions::FoldAtLevel6,
20562 window: &mut Window,
20563 cx: &mut Context<Self>,
20564 ) {
20565 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20566 }
20567
20568 pub fn fold_at_level_7(
20569 &mut self,
20570 _: &actions::FoldAtLevel7,
20571 window: &mut Window,
20572 cx: &mut Context<Self>,
20573 ) {
20574 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20575 }
20576
20577 pub fn fold_at_level_8(
20578 &mut self,
20579 _: &actions::FoldAtLevel8,
20580 window: &mut Window,
20581 cx: &mut Context<Self>,
20582 ) {
20583 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20584 }
20585
20586 pub fn fold_at_level_9(
20587 &mut self,
20588 _: &actions::FoldAtLevel9,
20589 window: &mut Window,
20590 cx: &mut Context<Self>,
20591 ) {
20592 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20593 }
20594
20595 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20596 if self.buffer.read(cx).is_singleton() {
20597 let mut fold_ranges = Vec::new();
20598 let snapshot = self.buffer.read(cx).snapshot(cx);
20599
20600 for row in 0..snapshot.max_row().0 {
20601 if let Some(foldable_range) = self
20602 .snapshot(window, cx)
20603 .crease_for_buffer_row(MultiBufferRow(row))
20604 {
20605 fold_ranges.push(foldable_range);
20606 }
20607 }
20608
20609 self.fold_creases(fold_ranges, true, window, cx);
20610 } else {
20611 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20612 editor
20613 .update_in(cx, |editor, _, cx| {
20614 let snapshot = editor.buffer.read(cx).snapshot(cx);
20615 for buffer_id in snapshot.all_buffer_ids() {
20616 editor.fold_buffer(buffer_id, cx);
20617 }
20618 })
20619 .ok();
20620 });
20621 }
20622 }
20623
20624 pub fn fold_function_bodies(
20625 &mut self,
20626 _: &actions::FoldFunctionBodies,
20627 window: &mut Window,
20628 cx: &mut Context<Self>,
20629 ) {
20630 let snapshot = self.buffer.read(cx).snapshot(cx);
20631
20632 let ranges = snapshot
20633 .text_object_ranges(
20634 MultiBufferOffset(0)..snapshot.len(),
20635 TreeSitterOptions::default(),
20636 )
20637 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20638 .collect::<Vec<_>>();
20639
20640 let creases = ranges
20641 .into_iter()
20642 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20643 .collect();
20644
20645 self.fold_creases(creases, true, window, cx);
20646 }
20647
20648 pub fn fold_recursive(
20649 &mut self,
20650 _: &actions::FoldRecursive,
20651 window: &mut Window,
20652 cx: &mut Context<Self>,
20653 ) {
20654 let mut to_fold = Vec::new();
20655 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20656 let selections = self.selections.all_adjusted(&display_map);
20657
20658 for selection in selections {
20659 let range = selection.range().sorted();
20660 let buffer_start_row = range.start.row;
20661
20662 if range.start.row != range.end.row {
20663 let mut found = false;
20664 for row in range.start.row..=range.end.row {
20665 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20666 found = true;
20667 to_fold.push(crease);
20668 }
20669 }
20670 if found {
20671 continue;
20672 }
20673 }
20674
20675 for row in (0..=range.start.row).rev() {
20676 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20677 if crease.range().end.row >= buffer_start_row {
20678 to_fold.push(crease);
20679 } else {
20680 break;
20681 }
20682 }
20683 }
20684 }
20685
20686 self.fold_creases(to_fold, true, window, cx);
20687 }
20688
20689 pub fn fold_at(
20690 &mut self,
20691 buffer_row: MultiBufferRow,
20692 window: &mut Window,
20693 cx: &mut Context<Self>,
20694 ) {
20695 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20696
20697 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20698 let autoscroll = self
20699 .selections
20700 .all::<Point>(&display_map)
20701 .iter()
20702 .any(|selection| crease.range().overlaps(&selection.range()));
20703
20704 self.fold_creases(vec![crease], autoscroll, window, cx);
20705 }
20706 }
20707
20708 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20709 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20711 let buffer = display_map.buffer_snapshot();
20712 let selections = self.selections.all::<Point>(&display_map);
20713 let ranges = selections
20714 .iter()
20715 .map(|s| {
20716 let range = s.display_range(&display_map).sorted();
20717 let mut start = range.start.to_point(&display_map);
20718 let mut end = range.end.to_point(&display_map);
20719 start.column = 0;
20720 end.column = buffer.line_len(MultiBufferRow(end.row));
20721 start..end
20722 })
20723 .collect::<Vec<_>>();
20724
20725 self.unfold_ranges(&ranges, true, true, cx);
20726 } else {
20727 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20728 let buffer_ids = self
20729 .selections
20730 .disjoint_anchor_ranges()
20731 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20732 .collect::<HashSet<_>>();
20733 for buffer_id in buffer_ids {
20734 self.unfold_buffer(buffer_id, cx);
20735 }
20736 }
20737 }
20738
20739 pub fn unfold_recursive(
20740 &mut self,
20741 _: &UnfoldRecursive,
20742 _window: &mut Window,
20743 cx: &mut Context<Self>,
20744 ) {
20745 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20746 let selections = self.selections.all::<Point>(&display_map);
20747 let ranges = selections
20748 .iter()
20749 .map(|s| {
20750 let mut range = s.display_range(&display_map).sorted();
20751 *range.start.column_mut() = 0;
20752 *range.end.column_mut() = display_map.line_len(range.end.row());
20753 let start = range.start.to_point(&display_map);
20754 let end = range.end.to_point(&display_map);
20755 start..end
20756 })
20757 .collect::<Vec<_>>();
20758
20759 self.unfold_ranges(&ranges, true, true, cx);
20760 }
20761
20762 pub fn unfold_at(
20763 &mut self,
20764 buffer_row: MultiBufferRow,
20765 _window: &mut Window,
20766 cx: &mut Context<Self>,
20767 ) {
20768 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20769
20770 let intersection_range = Point::new(buffer_row.0, 0)
20771 ..Point::new(
20772 buffer_row.0,
20773 display_map.buffer_snapshot().line_len(buffer_row),
20774 );
20775
20776 let autoscroll = self
20777 .selections
20778 .all::<Point>(&display_map)
20779 .iter()
20780 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20781
20782 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20783 }
20784
20785 pub fn unfold_all(
20786 &mut self,
20787 _: &actions::UnfoldAll,
20788 _window: &mut Window,
20789 cx: &mut Context<Self>,
20790 ) {
20791 if self.buffer.read(cx).is_singleton() {
20792 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20793 self.unfold_ranges(
20794 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20795 true,
20796 true,
20797 cx,
20798 );
20799 } else {
20800 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20801 editor
20802 .update(cx, |editor, cx| {
20803 let snapshot = editor.buffer.read(cx).snapshot(cx);
20804 for buffer_id in snapshot.all_buffer_ids() {
20805 editor.unfold_buffer(buffer_id, cx);
20806 }
20807 })
20808 .ok();
20809 });
20810 }
20811 }
20812
20813 pub fn fold_selected_ranges(
20814 &mut self,
20815 _: &FoldSelectedRanges,
20816 window: &mut Window,
20817 cx: &mut Context<Self>,
20818 ) {
20819 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20820 let selections = self.selections.all_adjusted(&display_map);
20821 let ranges = selections
20822 .into_iter()
20823 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20824 .collect::<Vec<_>>();
20825 self.fold_creases(ranges, true, window, cx);
20826 }
20827
20828 pub fn fold_ranges<T: ToOffset + Clone>(
20829 &mut self,
20830 ranges: Vec<Range<T>>,
20831 auto_scroll: bool,
20832 window: &mut Window,
20833 cx: &mut Context<Self>,
20834 ) {
20835 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20836 let ranges = ranges
20837 .into_iter()
20838 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20839 .collect::<Vec<_>>();
20840 self.fold_creases(ranges, auto_scroll, window, cx);
20841 }
20842
20843 pub fn fold_creases<T: ToOffset + Clone>(
20844 &mut self,
20845 creases: Vec<Crease<T>>,
20846 auto_scroll: bool,
20847 window: &mut Window,
20848 cx: &mut Context<Self>,
20849 ) {
20850 if creases.is_empty() {
20851 return;
20852 }
20853
20854 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20855
20856 if auto_scroll {
20857 self.request_autoscroll(Autoscroll::fit(), cx);
20858 }
20859
20860 cx.notify();
20861
20862 self.scrollbar_marker_state.dirty = true;
20863 self.update_data_on_scroll(window, cx);
20864 self.folds_did_change(cx);
20865 }
20866
20867 /// Removes any folds whose ranges intersect any of the given ranges.
20868 pub fn unfold_ranges<T: ToOffset + Clone>(
20869 &mut self,
20870 ranges: &[Range<T>],
20871 inclusive: bool,
20872 auto_scroll: bool,
20873 cx: &mut Context<Self>,
20874 ) {
20875 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20876 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20877 });
20878 self.folds_did_change(cx);
20879 }
20880
20881 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20882 self.fold_buffers([buffer_id], cx);
20883 }
20884
20885 pub fn fold_buffers(
20886 &mut self,
20887 buffer_ids: impl IntoIterator<Item = BufferId>,
20888 cx: &mut Context<Self>,
20889 ) {
20890 if self.buffer().read(cx).is_singleton() {
20891 return;
20892 }
20893
20894 let ids_to_fold: Vec<BufferId> = buffer_ids
20895 .into_iter()
20896 .filter(|id| !self.is_buffer_folded(*id, cx))
20897 .collect();
20898
20899 if ids_to_fold.is_empty() {
20900 return;
20901 }
20902
20903 self.display_map.update(cx, |display_map, cx| {
20904 display_map.fold_buffers(ids_to_fold.clone(), cx)
20905 });
20906
20907 let snapshot = self.display_snapshot(cx);
20908 self.selections.change_with(&snapshot, |selections| {
20909 for buffer_id in ids_to_fold.iter().copied() {
20910 selections.remove_selections_from_buffer(buffer_id);
20911 }
20912 });
20913
20914 cx.emit(EditorEvent::BufferFoldToggled {
20915 ids: ids_to_fold,
20916 folded: true,
20917 });
20918 cx.notify();
20919 }
20920
20921 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20922 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20923 return;
20924 }
20925 self.display_map.update(cx, |display_map, cx| {
20926 display_map.unfold_buffers([buffer_id], cx);
20927 });
20928 cx.emit(EditorEvent::BufferFoldToggled {
20929 ids: vec![buffer_id],
20930 folded: false,
20931 });
20932 cx.notify();
20933 }
20934
20935 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20936 self.display_map.read(cx).is_buffer_folded(buffer)
20937 }
20938
20939 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20940 if self.buffer().read(cx).is_singleton() {
20941 return false;
20942 }
20943 !self.folded_buffers(cx).is_empty()
20944 }
20945
20946 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20947 self.display_map.read(cx).folded_buffers()
20948 }
20949
20950 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20951 self.display_map.update(cx, |display_map, cx| {
20952 display_map.disable_header_for_buffer(buffer_id, cx);
20953 });
20954 cx.notify();
20955 }
20956
20957 /// Removes any folds with the given ranges.
20958 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20959 &mut self,
20960 ranges: &[Range<T>],
20961 type_id: TypeId,
20962 auto_scroll: bool,
20963 cx: &mut Context<Self>,
20964 ) {
20965 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20966 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20967 });
20968 self.folds_did_change(cx);
20969 }
20970
20971 fn remove_folds_with<T: ToOffset + Clone>(
20972 &mut self,
20973 ranges: &[Range<T>],
20974 auto_scroll: bool,
20975 cx: &mut Context<Self>,
20976 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20977 ) {
20978 if ranges.is_empty() {
20979 return;
20980 }
20981
20982 self.display_map.update(cx, update);
20983
20984 if auto_scroll {
20985 self.request_autoscroll(Autoscroll::fit(), cx);
20986 }
20987
20988 cx.notify();
20989 self.scrollbar_marker_state.dirty = true;
20990 self.active_indent_guides_state.dirty = true;
20991 }
20992
20993 pub fn update_renderer_widths(
20994 &mut self,
20995 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20996 cx: &mut Context<Self>,
20997 ) -> bool {
20998 self.display_map
20999 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
21000 }
21001
21002 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
21003 self.display_map.read(cx).fold_placeholder.clone()
21004 }
21005
21006 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
21007 self.buffer.update(cx, |buffer, cx| {
21008 buffer.set_all_diff_hunks_expanded(cx);
21009 });
21010 }
21011
21012 pub fn expand_all_diff_hunks(
21013 &mut self,
21014 _: &ExpandAllDiffHunks,
21015 _window: &mut Window,
21016 cx: &mut Context<Self>,
21017 ) {
21018 self.buffer.update(cx, |buffer, cx| {
21019 buffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
21020 });
21021 }
21022
21023 pub fn collapse_all_diff_hunks(
21024 &mut self,
21025 _: &CollapseAllDiffHunks,
21026 _window: &mut Window,
21027 cx: &mut Context<Self>,
21028 ) {
21029 self.buffer.update(cx, |buffer, cx| {
21030 buffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
21031 });
21032 }
21033
21034 pub fn toggle_selected_diff_hunks(
21035 &mut self,
21036 _: &ToggleSelectedDiffHunks,
21037 _window: &mut Window,
21038 cx: &mut Context<Self>,
21039 ) {
21040 let ranges: Vec<_> = self
21041 .selections
21042 .disjoint_anchors()
21043 .iter()
21044 .map(|s| s.range())
21045 .collect();
21046 self.toggle_diff_hunks_in_ranges(ranges, cx);
21047 }
21048
21049 pub fn diff_hunks_in_ranges<'a>(
21050 &'a self,
21051 ranges: &'a [Range<Anchor>],
21052 buffer: &'a MultiBufferSnapshot,
21053 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
21054 ranges.iter().flat_map(move |range| {
21055 let end_excerpt = buffer.excerpt_containing(range.end..range.end);
21056 let range = range.to_point(buffer);
21057 let mut peek_end = range.end;
21058 if range.end.row < buffer.max_row().0 {
21059 peek_end = Point::new(range.end.row + 1, 0);
21060 }
21061 buffer
21062 .diff_hunks_in_range(range.start..peek_end)
21063 .filter(move |hunk| {
21064 if let Some((_, excerpt_range)) = &end_excerpt
21065 && let Some(end_anchor) =
21066 buffer.anchor_in_excerpt(excerpt_range.context.end)
21067 && let Some(hunk_end_anchor) =
21068 buffer.anchor_in_excerpt(hunk.excerpt_range.context.end)
21069 && hunk_end_anchor.cmp(&end_anchor, buffer).is_gt()
21070 {
21071 false
21072 } else {
21073 true
21074 }
21075 })
21076 })
21077 }
21078
21079 pub fn has_stageable_diff_hunks_in_ranges(
21080 &self,
21081 ranges: &[Range<Anchor>],
21082 snapshot: &MultiBufferSnapshot,
21083 ) -> bool {
21084 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
21085 hunks.any(|hunk| hunk.status().has_secondary_hunk())
21086 }
21087
21088 pub fn toggle_staged_selected_diff_hunks(
21089 &mut self,
21090 _: &::git::ToggleStaged,
21091 _: &mut Window,
21092 cx: &mut Context<Self>,
21093 ) {
21094 let snapshot = self.buffer.read(cx).snapshot(cx);
21095 let ranges: Vec<_> = self
21096 .selections
21097 .disjoint_anchors()
21098 .iter()
21099 .map(|s| s.range())
21100 .collect();
21101 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
21102 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
21103 }
21104
21105 pub fn set_render_diff_hunk_controls(
21106 &mut self,
21107 render_diff_hunk_controls: RenderDiffHunkControlsFn,
21108 cx: &mut Context<Self>,
21109 ) {
21110 self.render_diff_hunk_controls = render_diff_hunk_controls;
21111 cx.notify();
21112 }
21113
21114 pub fn stage_and_next(
21115 &mut self,
21116 _: &::git::StageAndNext,
21117 window: &mut Window,
21118 cx: &mut Context<Self>,
21119 ) {
21120 self.do_stage_or_unstage_and_next(true, window, cx);
21121 }
21122
21123 pub fn unstage_and_next(
21124 &mut self,
21125 _: &::git::UnstageAndNext,
21126 window: &mut Window,
21127 cx: &mut Context<Self>,
21128 ) {
21129 self.do_stage_or_unstage_and_next(false, window, cx);
21130 }
21131
21132 pub fn stage_or_unstage_diff_hunks(
21133 &mut self,
21134 stage: bool,
21135 ranges: Vec<Range<Anchor>>,
21136 cx: &mut Context<Self>,
21137 ) {
21138 if self.delegate_stage_and_restore {
21139 let snapshot = self.buffer.read(cx).snapshot(cx);
21140 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
21141 if !hunks.is_empty() {
21142 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
21143 }
21144 return;
21145 }
21146 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
21147 cx.spawn(async move |this, cx| {
21148 task.await?;
21149 this.update(cx, |this, cx| {
21150 let snapshot = this.buffer.read(cx).snapshot(cx);
21151 let chunk_by = this
21152 .diff_hunks_in_ranges(&ranges, &snapshot)
21153 .chunk_by(|hunk| hunk.buffer_id);
21154 for (buffer_id, hunks) in &chunk_by {
21155 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
21156 }
21157 })
21158 })
21159 .detach_and_log_err(cx);
21160 }
21161
21162 fn save_buffers_for_ranges_if_needed(
21163 &mut self,
21164 ranges: &[Range<Anchor>],
21165 cx: &mut Context<Editor>,
21166 ) -> Task<Result<()>> {
21167 let multibuffer = self.buffer.read(cx);
21168 let snapshot = multibuffer.read(cx);
21169 let buffer_ids: HashSet<_> = ranges
21170 .iter()
21171 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
21172 .collect();
21173 drop(snapshot);
21174
21175 let mut buffers = HashSet::default();
21176 for buffer_id in buffer_ids {
21177 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
21178 let buffer = buffer_entity.read(cx);
21179 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
21180 {
21181 buffers.insert(buffer_entity);
21182 }
21183 }
21184 }
21185
21186 if let Some(project) = &self.project {
21187 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
21188 } else {
21189 Task::ready(Ok(()))
21190 }
21191 }
21192
21193 fn do_stage_or_unstage_and_next(
21194 &mut self,
21195 stage: bool,
21196 window: &mut Window,
21197 cx: &mut Context<Self>,
21198 ) {
21199 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
21200
21201 if ranges.iter().any(|range| range.start != range.end) {
21202 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
21203 return;
21204 }
21205
21206 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
21207
21208 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
21209 let wrap_around = !all_diff_hunks_expanded;
21210 let snapshot = self.snapshot(window, cx);
21211 let position = self
21212 .selections
21213 .newest::<Point>(&snapshot.display_snapshot)
21214 .head();
21215
21216 self.go_to_hunk_before_or_after_position(
21217 &snapshot,
21218 position,
21219 Direction::Next,
21220 wrap_around,
21221 window,
21222 cx,
21223 );
21224 }
21225
21226 pub(crate) fn do_stage_or_unstage(
21227 &self,
21228 stage: bool,
21229 buffer_id: BufferId,
21230 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
21231 cx: &mut App,
21232 ) -> Option<()> {
21233 let project = self.project()?;
21234 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
21235 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
21236 let buffer_snapshot = buffer.read(cx).snapshot();
21237 let file_exists = buffer_snapshot
21238 .file()
21239 .is_some_and(|file| file.disk_state().exists());
21240 diff.update(cx, |diff, cx| {
21241 diff.stage_or_unstage_hunks(
21242 stage,
21243 &hunks
21244 .map(|hunk| buffer_diff::DiffHunk {
21245 buffer_range: hunk.buffer_range,
21246 // We don't need to pass in word diffs here because they're only used for rendering and
21247 // this function changes internal state
21248 base_word_diffs: Vec::default(),
21249 buffer_word_diffs: Vec::default(),
21250 diff_base_byte_range: hunk.diff_base_byte_range.start.0
21251 ..hunk.diff_base_byte_range.end.0,
21252 secondary_status: hunk.status.secondary,
21253 range: Point::zero()..Point::zero(), // unused
21254 })
21255 .collect::<Vec<_>>(),
21256 &buffer_snapshot,
21257 file_exists,
21258 cx,
21259 )
21260 });
21261 None
21262 }
21263
21264 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
21265 let ranges: Vec<_> = self
21266 .selections
21267 .disjoint_anchors()
21268 .iter()
21269 .map(|s| s.range())
21270 .collect();
21271 self.buffer
21272 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
21273 }
21274
21275 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
21276 self.buffer.update(cx, |buffer, cx| {
21277 let ranges = vec![Anchor::Min..Anchor::Max];
21278 if !buffer.all_diff_hunks_expanded()
21279 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
21280 {
21281 buffer.collapse_diff_hunks(ranges, cx);
21282 true
21283 } else {
21284 false
21285 }
21286 })
21287 }
21288
21289 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
21290 if self.buffer.read(cx).all_diff_hunks_expanded() {
21291 return true;
21292 }
21293 let ranges = vec![Anchor::Min..Anchor::Max];
21294 self.buffer
21295 .read(cx)
21296 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
21297 }
21298
21299 fn toggle_diff_hunks_in_ranges(
21300 &mut self,
21301 ranges: Vec<Range<Anchor>>,
21302 cx: &mut Context<Editor>,
21303 ) {
21304 self.buffer.update(cx, |buffer, cx| {
21305 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
21306 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
21307 })
21308 }
21309
21310 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
21311 self.buffer.update(cx, |buffer, cx| {
21312 buffer.toggle_single_diff_hunk(range, cx);
21313 })
21314 }
21315
21316 pub(crate) fn apply_all_diff_hunks(
21317 &mut self,
21318 _: &ApplyAllDiffHunks,
21319 window: &mut Window,
21320 cx: &mut Context<Self>,
21321 ) {
21322 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21323
21324 let buffers = self.buffer.read(cx).all_buffers();
21325 for branch_buffer in buffers {
21326 branch_buffer.update(cx, |branch_buffer, cx| {
21327 branch_buffer.merge_into_base(Vec::new(), cx);
21328 });
21329 }
21330
21331 if let Some(project) = self.project.clone() {
21332 self.save(
21333 SaveOptions {
21334 format: true,
21335 autosave: false,
21336 },
21337 project,
21338 window,
21339 cx,
21340 )
21341 .detach_and_log_err(cx);
21342 }
21343 }
21344
21345 pub(crate) fn apply_selected_diff_hunks(
21346 &mut self,
21347 _: &ApplyDiffHunk,
21348 window: &mut Window,
21349 cx: &mut Context<Self>,
21350 ) {
21351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21352 let snapshot = self.snapshot(window, cx);
21353 let hunks = snapshot.hunks_for_ranges(
21354 self.selections
21355 .all(&snapshot.display_snapshot)
21356 .into_iter()
21357 .map(|selection| selection.range()),
21358 );
21359 let mut ranges_by_buffer = HashMap::default();
21360 self.transact(window, cx, |editor, _window, cx| {
21361 for hunk in hunks {
21362 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
21363 ranges_by_buffer
21364 .entry(buffer.clone())
21365 .or_insert_with(Vec::new)
21366 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
21367 }
21368 }
21369
21370 for (buffer, ranges) in ranges_by_buffer {
21371 buffer.update(cx, |buffer, cx| {
21372 buffer.merge_into_base(ranges, cx);
21373 });
21374 }
21375 });
21376
21377 if let Some(project) = self.project.clone() {
21378 self.save(
21379 SaveOptions {
21380 format: true,
21381 autosave: false,
21382 },
21383 project,
21384 window,
21385 cx,
21386 )
21387 .detach_and_log_err(cx);
21388 }
21389 }
21390
21391 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
21392 if hovered != self.gutter_hovered {
21393 self.gutter_hovered = hovered;
21394 cx.notify();
21395 }
21396 }
21397
21398 pub fn insert_blocks(
21399 &mut self,
21400 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
21401 autoscroll: Option<Autoscroll>,
21402 cx: &mut Context<Self>,
21403 ) -> Vec<CustomBlockId> {
21404 let blocks = self
21405 .display_map
21406 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
21407 if let Some(autoscroll) = autoscroll {
21408 self.request_autoscroll(autoscroll, cx);
21409 }
21410 cx.notify();
21411 blocks
21412 }
21413
21414 pub fn resize_blocks(
21415 &mut self,
21416 heights: HashMap<CustomBlockId, u32>,
21417 autoscroll: Option<Autoscroll>,
21418 cx: &mut Context<Self>,
21419 ) {
21420 self.display_map
21421 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21422 if let Some(autoscroll) = autoscroll {
21423 self.request_autoscroll(autoscroll, cx);
21424 }
21425 cx.notify();
21426 }
21427
21428 pub fn replace_blocks(
21429 &mut self,
21430 renderers: HashMap<CustomBlockId, RenderBlock>,
21431 autoscroll: Option<Autoscroll>,
21432 cx: &mut Context<Self>,
21433 ) {
21434 self.display_map
21435 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21436 if let Some(autoscroll) = autoscroll {
21437 self.request_autoscroll(autoscroll, cx);
21438 }
21439 cx.notify();
21440 }
21441
21442 pub fn remove_blocks(
21443 &mut self,
21444 block_ids: HashSet<CustomBlockId>,
21445 autoscroll: Option<Autoscroll>,
21446 cx: &mut Context<Self>,
21447 ) {
21448 self.display_map.update(cx, |display_map, cx| {
21449 display_map.remove_blocks(block_ids, cx)
21450 });
21451 if let Some(autoscroll) = autoscroll {
21452 self.request_autoscroll(autoscroll, cx);
21453 }
21454 cx.notify();
21455 }
21456
21457 pub fn row_for_block(
21458 &self,
21459 block_id: CustomBlockId,
21460 cx: &mut Context<Self>,
21461 ) -> Option<DisplayRow> {
21462 self.display_map
21463 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21464 }
21465
21466 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21467 self.focused_block = Some(focused_block);
21468 }
21469
21470 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21471 self.focused_block.take()
21472 }
21473
21474 pub fn insert_creases(
21475 &mut self,
21476 creases: impl IntoIterator<Item = Crease<Anchor>>,
21477 cx: &mut Context<Self>,
21478 ) -> Vec<CreaseId> {
21479 self.display_map
21480 .update(cx, |map, cx| map.insert_creases(creases, cx))
21481 }
21482
21483 pub fn remove_creases(
21484 &mut self,
21485 ids: impl IntoIterator<Item = CreaseId>,
21486 cx: &mut Context<Self>,
21487 ) -> Vec<(CreaseId, Range<Anchor>)> {
21488 self.display_map
21489 .update(cx, |map, cx| map.remove_creases(ids, cx))
21490 }
21491
21492 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21493 self.display_map
21494 .update(cx, |map, cx| map.snapshot(cx))
21495 .longest_row()
21496 }
21497
21498 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21499 self.display_map
21500 .update(cx, |map, cx| map.snapshot(cx))
21501 .max_point()
21502 }
21503
21504 pub fn text(&self, cx: &App) -> String {
21505 self.buffer.read(cx).read(cx).text()
21506 }
21507
21508 pub fn is_empty(&self, cx: &App) -> bool {
21509 self.buffer.read(cx).read(cx).is_empty()
21510 }
21511
21512 pub fn text_option(&self, cx: &App) -> Option<String> {
21513 let text = self.text(cx);
21514 let text = text.trim();
21515
21516 if text.is_empty() {
21517 return None;
21518 }
21519
21520 Some(text.to_string())
21521 }
21522
21523 pub fn set_text(
21524 &mut self,
21525 text: impl Into<Arc<str>>,
21526 window: &mut Window,
21527 cx: &mut Context<Self>,
21528 ) {
21529 self.transact(window, cx, |this, _, cx| {
21530 this.buffer
21531 .read(cx)
21532 .as_singleton()
21533 .expect("you can only call set_text on editors for singleton buffers")
21534 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21535 });
21536 }
21537
21538 pub fn display_text(&self, cx: &mut App) -> String {
21539 self.display_map
21540 .update(cx, |map, cx| map.snapshot(cx))
21541 .text()
21542 }
21543
21544 fn create_minimap(
21545 &self,
21546 minimap_settings: MinimapSettings,
21547 window: &mut Window,
21548 cx: &mut Context<Self>,
21549 ) -> Option<Entity<Self>> {
21550 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21551 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21552 }
21553
21554 fn initialize_new_minimap(
21555 &self,
21556 minimap_settings: MinimapSettings,
21557 window: &mut Window,
21558 cx: &mut Context<Self>,
21559 ) -> Entity<Self> {
21560 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21561 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21562
21563 let mut minimap = Editor::new_internal(
21564 EditorMode::Minimap {
21565 parent: cx.weak_entity(),
21566 },
21567 self.buffer.clone(),
21568 None,
21569 Some(self.display_map.clone()),
21570 window,
21571 cx,
21572 );
21573 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21574 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21575 minimap.scroll_manager.clone_state(
21576 &self.scroll_manager,
21577 &my_snapshot,
21578 &minimap_snapshot,
21579 cx,
21580 );
21581 minimap.set_text_style_refinement(TextStyleRefinement {
21582 font_size: Some(MINIMAP_FONT_SIZE),
21583 font_weight: Some(MINIMAP_FONT_WEIGHT),
21584 font_family: Some(MINIMAP_FONT_FAMILY),
21585 ..Default::default()
21586 });
21587 minimap.update_minimap_configuration(minimap_settings, cx);
21588 cx.new(|_| minimap)
21589 }
21590
21591 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21592 let current_line_highlight = minimap_settings
21593 .current_line_highlight
21594 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21595 self.set_current_line_highlight(Some(current_line_highlight));
21596 }
21597
21598 pub fn minimap(&self) -> Option<&Entity<Self>> {
21599 self.minimap
21600 .as_ref()
21601 .filter(|_| self.minimap_visibility.visible())
21602 }
21603
21604 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21605 let mut wrap_guides = smallvec![];
21606
21607 if self.show_wrap_guides == Some(false) {
21608 return wrap_guides;
21609 }
21610
21611 let settings = self.buffer.read(cx).language_settings(cx);
21612 if settings.show_wrap_guides {
21613 match self.soft_wrap_mode(cx) {
21614 SoftWrap::Column(soft_wrap) => {
21615 wrap_guides.push((soft_wrap as usize, true));
21616 }
21617 SoftWrap::Bounded(soft_wrap) => {
21618 wrap_guides.push((soft_wrap as usize, true));
21619 }
21620 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21621 }
21622 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21623 }
21624
21625 wrap_guides
21626 }
21627
21628 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21629 let settings = self.buffer.read(cx).language_settings(cx);
21630 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21631 match mode {
21632 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21633 SoftWrap::None
21634 }
21635 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21636 language_settings::SoftWrap::PreferredLineLength => {
21637 SoftWrap::Column(settings.preferred_line_length)
21638 }
21639 language_settings::SoftWrap::Bounded => {
21640 SoftWrap::Bounded(settings.preferred_line_length)
21641 }
21642 }
21643 }
21644
21645 pub fn set_soft_wrap_mode(
21646 &mut self,
21647 mode: language_settings::SoftWrap,
21648 cx: &mut Context<Self>,
21649 ) {
21650 self.soft_wrap_mode_override = Some(mode);
21651 cx.notify();
21652 }
21653
21654 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21655 self.hard_wrap = hard_wrap;
21656 cx.notify();
21657 }
21658
21659 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21660 self.text_style_refinement = Some(style);
21661 }
21662
21663 /// called by the Element so we know what style we were most recently rendered with.
21664 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21665 // We intentionally do not inform the display map about the minimap style
21666 // so that wrapping is not recalculated and stays consistent for the editor
21667 // and its linked minimap.
21668 if !self.mode.is_minimap() {
21669 let font = style.text.font();
21670 let font_size = style.text.font_size.to_pixels(window.rem_size());
21671 let display_map = self
21672 .placeholder_display_map
21673 .as_ref()
21674 .filter(|_| self.is_empty(cx))
21675 .unwrap_or(&self.display_map);
21676
21677 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21678 }
21679 self.style = Some(style);
21680 }
21681
21682 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21683 if self.style.is_none() {
21684 self.style = Some(self.create_style(cx));
21685 }
21686 self.style.as_ref().unwrap()
21687 }
21688
21689 // Called by the element. This method is not designed to be called outside of the editor
21690 // element's layout code because it does not notify when rewrapping is computed synchronously.
21691 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21692 if self.is_empty(cx) {
21693 self.placeholder_display_map
21694 .as_ref()
21695 .map_or(false, |display_map| {
21696 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21697 })
21698 } else {
21699 self.display_map
21700 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21701 }
21702 }
21703
21704 pub fn set_soft_wrap(&mut self) {
21705 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21706 }
21707
21708 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21709 if self.soft_wrap_mode_override.is_some() {
21710 self.soft_wrap_mode_override.take();
21711 } else {
21712 let soft_wrap = match self.soft_wrap_mode(cx) {
21713 SoftWrap::GitDiff => return,
21714 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21715 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21716 language_settings::SoftWrap::None
21717 }
21718 };
21719 self.soft_wrap_mode_override = Some(soft_wrap);
21720 }
21721 cx.notify();
21722 }
21723
21724 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21725 let Some(workspace) = self.workspace() else {
21726 return;
21727 };
21728 let fs = workspace.read(cx).app_state().fs.clone();
21729 let current_show = TabBarSettings::get_global(cx).show;
21730 update_settings_file(fs, cx, move |setting, _| {
21731 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21732 });
21733 }
21734
21735 pub fn toggle_indent_guides(
21736 &mut self,
21737 _: &ToggleIndentGuides,
21738 _: &mut Window,
21739 cx: &mut Context<Self>,
21740 ) {
21741 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21742 self.buffer
21743 .read(cx)
21744 .language_settings(cx)
21745 .indent_guides
21746 .enabled
21747 });
21748 self.show_indent_guides = Some(!currently_enabled);
21749 cx.notify();
21750 }
21751
21752 fn should_show_indent_guides(&self) -> Option<bool> {
21753 self.show_indent_guides
21754 }
21755
21756 pub fn disable_indent_guides_for_buffer(
21757 &mut self,
21758 buffer_id: BufferId,
21759 cx: &mut Context<Self>,
21760 ) {
21761 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21762 cx.notify();
21763 }
21764
21765 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21766 self.buffers_with_disabled_indent_guides
21767 .contains(&buffer_id)
21768 }
21769
21770 pub fn toggle_line_numbers(
21771 &mut self,
21772 _: &ToggleLineNumbers,
21773 _: &mut Window,
21774 cx: &mut Context<Self>,
21775 ) {
21776 let mut editor_settings = EditorSettings::get_global(cx).clone();
21777 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21778 EditorSettings::override_global(editor_settings, cx);
21779 }
21780
21781 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21782 if let Some(show_line_numbers) = self.show_line_numbers {
21783 return show_line_numbers;
21784 }
21785 EditorSettings::get_global(cx).gutter.line_numbers
21786 }
21787
21788 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21789 match (
21790 self.use_relative_line_numbers,
21791 EditorSettings::get_global(cx).relative_line_numbers,
21792 ) {
21793 (None, setting) => setting,
21794 (Some(false), _) => RelativeLineNumbers::Disabled,
21795 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21796 (Some(true), _) => RelativeLineNumbers::Enabled,
21797 }
21798 }
21799
21800 pub fn toggle_relative_line_numbers(
21801 &mut self,
21802 _: &ToggleRelativeLineNumbers,
21803 _: &mut Window,
21804 cx: &mut Context<Self>,
21805 ) {
21806 let is_relative = self.relative_line_numbers(cx);
21807 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21808 }
21809
21810 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21811 self.use_relative_line_numbers = is_relative;
21812 cx.notify();
21813 }
21814
21815 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21816 self.show_gutter = show_gutter;
21817 cx.notify();
21818 }
21819
21820 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21821 self.show_scrollbars = ScrollbarAxes {
21822 horizontal: show,
21823 vertical: show,
21824 };
21825 cx.notify();
21826 }
21827
21828 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21829 self.show_scrollbars.vertical = show;
21830 cx.notify();
21831 }
21832
21833 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21834 self.show_scrollbars.horizontal = show;
21835 cx.notify();
21836 }
21837
21838 pub fn set_minimap_visibility(
21839 &mut self,
21840 minimap_visibility: MinimapVisibility,
21841 window: &mut Window,
21842 cx: &mut Context<Self>,
21843 ) {
21844 if self.minimap_visibility != minimap_visibility {
21845 if minimap_visibility.visible() && self.minimap.is_none() {
21846 let minimap_settings = EditorSettings::get_global(cx).minimap;
21847 self.minimap =
21848 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21849 }
21850 self.minimap_visibility = minimap_visibility;
21851 cx.notify();
21852 }
21853 }
21854
21855 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21856 self.set_show_scrollbars(false, cx);
21857 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21858 }
21859
21860 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21861 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21862 }
21863
21864 /// Normally the text in full mode and auto height editors is padded on the
21865 /// left side by roughly half a character width for improved hit testing.
21866 ///
21867 /// Use this method to disable this for cases where this is not wanted (e.g.
21868 /// if you want to align the editor text with some other text above or below)
21869 /// or if you want to add this padding to single-line editors.
21870 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21871 self.offset_content = offset_content;
21872 cx.notify();
21873 }
21874
21875 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21876 self.show_line_numbers = Some(show_line_numbers);
21877 cx.notify();
21878 }
21879
21880 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21881 self.disable_expand_excerpt_buttons = true;
21882 cx.notify();
21883 }
21884
21885 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21886 self.number_deleted_lines = number;
21887 cx.notify();
21888 }
21889
21890 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21891 self.delegate_expand_excerpts = delegate;
21892 }
21893
21894 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21895 self.delegate_stage_and_restore = delegate;
21896 }
21897
21898 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21899 self.delegate_open_excerpts = delegate;
21900 }
21901
21902 pub fn set_on_local_selections_changed(
21903 &mut self,
21904 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21905 ) {
21906 self.on_local_selections_changed = callback;
21907 }
21908
21909 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21910 self.suppress_selection_callback = suppress;
21911 }
21912
21913 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21914 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21915 cx.notify();
21916 }
21917
21918 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21919 self.show_code_actions = Some(show_code_actions);
21920 cx.notify();
21921 }
21922
21923 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21924 self.show_runnables = Some(show_runnables);
21925 cx.notify();
21926 }
21927
21928 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21929 self.show_breakpoints = Some(show_breakpoints);
21930 cx.notify();
21931 }
21932
21933 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21934 self.show_diff_review_button = show;
21935 cx.notify();
21936 }
21937
21938 pub fn show_diff_review_button(&self) -> bool {
21939 self.show_diff_review_button
21940 }
21941
21942 pub fn render_diff_review_button(
21943 &self,
21944 display_row: DisplayRow,
21945 width: Pixels,
21946 cx: &mut Context<Self>,
21947 ) -> impl IntoElement {
21948 let text_color = cx.theme().colors().text;
21949 let icon_color = cx.theme().colors().icon_accent;
21950
21951 h_flex()
21952 .id("diff_review_button")
21953 .cursor_pointer()
21954 .w(width - px(1.))
21955 .h(relative(0.9))
21956 .justify_center()
21957 .rounded_sm()
21958 .border_1()
21959 .border_color(text_color.opacity(0.1))
21960 .bg(text_color.opacity(0.15))
21961 .hover(|s| {
21962 s.bg(icon_color.opacity(0.4))
21963 .border_color(icon_color.opacity(0.5))
21964 })
21965 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21966 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21967 .on_mouse_down(
21968 gpui::MouseButton::Left,
21969 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21970 editor.start_diff_review_drag(display_row, window, cx);
21971 }),
21972 )
21973 }
21974
21975 pub fn start_diff_review_drag(
21976 &mut self,
21977 display_row: DisplayRow,
21978 window: &mut Window,
21979 cx: &mut Context<Self>,
21980 ) {
21981 let snapshot = self.snapshot(window, cx);
21982 let point = snapshot
21983 .display_snapshot
21984 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21985 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21986 self.diff_review_drag_state = Some(DiffReviewDragState {
21987 start_anchor: anchor,
21988 current_anchor: anchor,
21989 });
21990 cx.notify();
21991 }
21992
21993 pub fn update_diff_review_drag(
21994 &mut self,
21995 display_row: DisplayRow,
21996 window: &mut Window,
21997 cx: &mut Context<Self>,
21998 ) {
21999 if self.diff_review_drag_state.is_none() {
22000 return;
22001 }
22002 let snapshot = self.snapshot(window, cx);
22003 let point = snapshot
22004 .display_snapshot
22005 .display_point_to_point(display_row.as_display_point(), Bias::Left);
22006 let anchor = snapshot.buffer_snapshot().anchor_before(point);
22007 if let Some(drag_state) = &mut self.diff_review_drag_state {
22008 drag_state.current_anchor = anchor;
22009 cx.notify();
22010 }
22011 }
22012
22013 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22014 if let Some(drag_state) = self.diff_review_drag_state.take() {
22015 let snapshot = self.snapshot(window, cx);
22016 let range = drag_state.row_range(&snapshot.display_snapshot);
22017 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
22018 }
22019 cx.notify();
22020 }
22021
22022 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
22023 self.diff_review_drag_state = None;
22024 cx.notify();
22025 }
22026
22027 /// Calculates the appropriate block height for the diff review overlay.
22028 /// Height is in lines: 2 for input row, 1 for header when comments exist,
22029 /// and 2 lines per comment when expanded.
22030 fn calculate_overlay_height(
22031 &self,
22032 hunk_key: &DiffHunkKey,
22033 comments_expanded: bool,
22034 snapshot: &MultiBufferSnapshot,
22035 ) -> u32 {
22036 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
22037 let base_height: u32 = 2; // Input row with avatar and buttons
22038
22039 if comment_count == 0 {
22040 base_height
22041 } else if comments_expanded {
22042 // Header (1 line) + 2 lines per comment
22043 base_height + 1 + (comment_count as u32 * 2)
22044 } else {
22045 // Just header when collapsed
22046 base_height + 1
22047 }
22048 }
22049
22050 pub fn show_diff_review_overlay(
22051 &mut self,
22052 display_range: Range<DisplayRow>,
22053 window: &mut Window,
22054 cx: &mut Context<Self>,
22055 ) {
22056 let Range { start, end } = display_range.sorted();
22057
22058 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22059 let editor_snapshot = self.snapshot(window, cx);
22060
22061 // Convert display rows to multibuffer points
22062 let start_point = editor_snapshot
22063 .display_snapshot
22064 .display_point_to_point(start.as_display_point(), Bias::Left);
22065 let end_point = editor_snapshot
22066 .display_snapshot
22067 .display_point_to_point(end.as_display_point(), Bias::Left);
22068 let end_multi_buffer_row = MultiBufferRow(end_point.row);
22069
22070 // Create anchor range for the selected lines (start of first line to end of last line)
22071 let line_end = Point::new(
22072 end_point.row,
22073 buffer_snapshot.line_len(end_multi_buffer_row),
22074 );
22075 let anchor_range =
22076 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
22077
22078 // Compute the hunk key for this display row
22079 let file_path = buffer_snapshot
22080 .file_at(start_point)
22081 .map(|file: &Arc<dyn language::File>| file.path().clone())
22082 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
22083 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
22084 let new_hunk_key = DiffHunkKey {
22085 file_path,
22086 hunk_start_anchor,
22087 };
22088
22089 // Check if we already have an overlay for this hunk
22090 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
22091 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
22092 }) {
22093 // Just focus the existing overlay's prompt editor
22094 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
22095 window.focus(&focus_handle, cx);
22096 return;
22097 }
22098
22099 // Dismiss overlays that have no comments for their hunks
22100 self.dismiss_overlays_without_comments(cx);
22101
22102 // Get the current user's avatar URI from the project's user_store
22103 let user_avatar_uri = self.project.as_ref().and_then(|project| {
22104 let user_store = project.read(cx).user_store();
22105 user_store
22106 .read(cx)
22107 .current_user()
22108 .map(|user| user.avatar_uri.clone())
22109 });
22110
22111 // Create anchor at the end of the last row so the block appears immediately below it
22112 // Use multibuffer coordinates for anchor creation
22113 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
22114 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
22115
22116 // Use the hunk key we already computed
22117 let hunk_key = new_hunk_key;
22118
22119 // Create the prompt editor for the review input
22120 let prompt_editor = cx.new(|cx| {
22121 let mut editor = Editor::single_line(window, cx);
22122 editor.set_placeholder_text("Add a review comment...", window, cx);
22123 editor
22124 });
22125
22126 // Register the Newline action on the prompt editor to submit the review
22127 let parent_editor = cx.entity().downgrade();
22128 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
22129 prompt_editor.register_action({
22130 let parent_editor = parent_editor.clone();
22131 move |_: &crate::actions::Newline, window, cx| {
22132 if let Some(editor) = parent_editor.upgrade() {
22133 editor.update(cx, |editor, cx| {
22134 editor.submit_diff_review_comment(window, cx);
22135 });
22136 }
22137 }
22138 })
22139 });
22140
22141 // Calculate initial height based on existing comments for this hunk
22142 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
22143
22144 // Create the overlay block
22145 let prompt_editor_for_render = prompt_editor.clone();
22146 let hunk_key_for_render = hunk_key.clone();
22147 let editor_handle = cx.entity().downgrade();
22148 let block = BlockProperties {
22149 style: BlockStyle::Sticky,
22150 placement: BlockPlacement::Below(anchor),
22151 height: Some(initial_height),
22152 render: Arc::new(move |cx| {
22153 Self::render_diff_review_overlay(
22154 &prompt_editor_for_render,
22155 &hunk_key_for_render,
22156 &editor_handle,
22157 cx,
22158 )
22159 }),
22160 priority: 0,
22161 };
22162
22163 let block_ids = self.insert_blocks([block], None, cx);
22164 let Some(block_id) = block_ids.into_iter().next() else {
22165 log::error!("Failed to insert diff review overlay block");
22166 return;
22167 };
22168
22169 self.diff_review_overlays.push(DiffReviewOverlay {
22170 anchor_range,
22171 block_id,
22172 prompt_editor: prompt_editor.clone(),
22173 hunk_key,
22174 comments_expanded: true,
22175 inline_edit_editors: HashMap::default(),
22176 inline_edit_subscriptions: HashMap::default(),
22177 user_avatar_uri,
22178 _subscription: subscription,
22179 });
22180
22181 // Focus the prompt editor
22182 let focus_handle = prompt_editor.focus_handle(cx);
22183 window.focus(&focus_handle, cx);
22184
22185 cx.notify();
22186 }
22187
22188 /// Dismisses all diff review overlays.
22189 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
22190 if self.diff_review_overlays.is_empty() {
22191 return;
22192 }
22193 let block_ids: HashSet<_> = self
22194 .diff_review_overlays
22195 .drain(..)
22196 .map(|overlay| overlay.block_id)
22197 .collect();
22198 self.remove_blocks(block_ids, None, cx);
22199 cx.notify();
22200 }
22201
22202 /// Dismisses overlays that have no comments stored for their hunks.
22203 /// Keeps overlays that have at least one comment.
22204 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
22205 let snapshot = self.buffer.read(cx).snapshot(cx);
22206
22207 // First, compute which overlays have comments (to avoid borrow issues with retain)
22208 let overlays_with_comments: Vec<bool> = self
22209 .diff_review_overlays
22210 .iter()
22211 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
22212 .collect();
22213
22214 // Now collect block IDs to remove and retain overlays
22215 let mut block_ids_to_remove = HashSet::default();
22216 let mut index = 0;
22217 self.diff_review_overlays.retain(|overlay| {
22218 let has_comments = overlays_with_comments[index];
22219 index += 1;
22220 if !has_comments {
22221 block_ids_to_remove.insert(overlay.block_id);
22222 }
22223 has_comments
22224 });
22225
22226 if !block_ids_to_remove.is_empty() {
22227 self.remove_blocks(block_ids_to_remove, None, cx);
22228 cx.notify();
22229 }
22230 }
22231
22232 /// Refreshes the diff review overlay block to update its height and render function.
22233 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
22234 fn refresh_diff_review_overlay_height(
22235 &mut self,
22236 hunk_key: &DiffHunkKey,
22237 _window: &mut Window,
22238 cx: &mut Context<Self>,
22239 ) {
22240 // Extract all needed data from overlay first to avoid borrow conflicts
22241 let snapshot = self.buffer.read(cx).snapshot(cx);
22242 let (comments_expanded, block_id, prompt_editor) = {
22243 let Some(overlay) = self
22244 .diff_review_overlays
22245 .iter()
22246 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22247 else {
22248 return;
22249 };
22250
22251 (
22252 overlay.comments_expanded,
22253 overlay.block_id,
22254 overlay.prompt_editor.clone(),
22255 )
22256 };
22257
22258 // Calculate new height
22259 let snapshot = self.buffer.read(cx).snapshot(cx);
22260 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
22261
22262 // Update the block height using resize_blocks (avoids flicker)
22263 let mut heights = HashMap::default();
22264 heights.insert(block_id, new_height);
22265 self.resize_blocks(heights, None, cx);
22266
22267 // Update the render function using replace_blocks (avoids flicker)
22268 let hunk_key_for_render = hunk_key.clone();
22269 let editor_handle = cx.entity().downgrade();
22270 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
22271 Arc::new(move |cx| {
22272 Self::render_diff_review_overlay(
22273 &prompt_editor,
22274 &hunk_key_for_render,
22275 &editor_handle,
22276 cx,
22277 )
22278 });
22279
22280 let mut renderers = HashMap::default();
22281 renderers.insert(block_id, render);
22282 self.replace_blocks(renderers, None, cx);
22283 }
22284
22285 /// Action handler for SubmitDiffReviewComment.
22286 pub fn submit_diff_review_comment_action(
22287 &mut self,
22288 _: &SubmitDiffReviewComment,
22289 window: &mut Window,
22290 cx: &mut Context<Self>,
22291 ) {
22292 self.submit_diff_review_comment(window, cx);
22293 }
22294
22295 /// Stores the diff review comment locally.
22296 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
22297 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22298 // Find the overlay that currently has focus
22299 let overlay_index = self
22300 .diff_review_overlays
22301 .iter()
22302 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
22303 let Some(overlay_index) = overlay_index else {
22304 return;
22305 };
22306 let overlay = &self.diff_review_overlays[overlay_index];
22307
22308 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
22309 if comment_text.is_empty() {
22310 return;
22311 }
22312
22313 let anchor_range = overlay.anchor_range.clone();
22314 let hunk_key = overlay.hunk_key.clone();
22315
22316 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
22317
22318 // Clear the prompt editor but keep the overlay open
22319 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
22320 overlay.prompt_editor.update(cx, |editor, cx| {
22321 editor.clear(window, cx);
22322 });
22323 }
22324
22325 // Refresh the overlay to update the block height for the new comment
22326 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22327
22328 cx.notify();
22329 }
22330
22331 /// Returns the prompt editor for the diff review overlay, if one is active.
22332 /// This is primarily used for testing.
22333 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
22334 self.diff_review_overlays
22335 .first()
22336 .map(|overlay| &overlay.prompt_editor)
22337 }
22338
22339 /// Returns the line range for the first diff review overlay, if one is active.
22340 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
22341 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
22342 let overlay = self.diff_review_overlays.first()?;
22343 let snapshot = self.buffer.read(cx).snapshot(cx);
22344 let start_point = overlay.anchor_range.start.to_point(&snapshot);
22345 let end_point = overlay.anchor_range.end.to_point(&snapshot);
22346 let start_row = snapshot
22347 .point_to_buffer_point(start_point)
22348 .map(|(_, p)| p.row)
22349 .unwrap_or(start_point.row);
22350 let end_row = snapshot
22351 .point_to_buffer_point(end_point)
22352 .map(|(_, p)| p.row)
22353 .unwrap_or(end_point.row);
22354 Some((start_row, end_row))
22355 }
22356
22357 /// Sets whether the comments section is expanded in the diff review overlay.
22358 /// This is primarily used for testing.
22359 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
22360 for overlay in &mut self.diff_review_overlays {
22361 overlay.comments_expanded = expanded;
22362 }
22363 cx.notify();
22364 }
22365
22366 /// Compares two DiffHunkKeys for equality by resolving their anchors.
22367 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
22368 a.file_path == b.file_path
22369 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
22370 }
22371
22372 /// Returns comments for a specific hunk, ordered by creation time.
22373 pub fn comments_for_hunk<'a>(
22374 &'a self,
22375 key: &DiffHunkKey,
22376 snapshot: &MultiBufferSnapshot,
22377 ) -> &'a [StoredReviewComment] {
22378 let key_point = key.hunk_start_anchor.to_point(snapshot);
22379 self.stored_review_comments
22380 .iter()
22381 .find(|(k, _)| {
22382 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22383 })
22384 .map(|(_, comments)| comments.as_slice())
22385 .unwrap_or(&[])
22386 }
22387
22388 /// Returns the total count of stored review comments across all hunks.
22389 pub fn total_review_comment_count(&self) -> usize {
22390 self.stored_review_comments
22391 .iter()
22392 .map(|(_, v)| v.len())
22393 .sum()
22394 }
22395
22396 /// Returns the count of comments for a specific hunk.
22397 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
22398 let key_point = key.hunk_start_anchor.to_point(snapshot);
22399 self.stored_review_comments
22400 .iter()
22401 .find(|(k, _)| {
22402 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22403 })
22404 .map(|(_, v)| v.len())
22405 .unwrap_or(0)
22406 }
22407
22408 /// Adds a new review comment to a specific hunk.
22409 pub fn add_review_comment(
22410 &mut self,
22411 hunk_key: DiffHunkKey,
22412 comment: String,
22413 anchor_range: Range<Anchor>,
22414 cx: &mut Context<Self>,
22415 ) -> usize {
22416 let id = self.next_review_comment_id;
22417 self.next_review_comment_id += 1;
22418
22419 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22420
22421 let snapshot = self.buffer.read(cx).snapshot(cx);
22422 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22423
22424 // Find existing entry for this hunk or add a new one
22425 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22426 k.file_path == hunk_key.file_path
22427 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22428 }) {
22429 comments.push(stored_comment);
22430 } else {
22431 self.stored_review_comments
22432 .push((hunk_key, vec![stored_comment]));
22433 }
22434
22435 cx.emit(EditorEvent::ReviewCommentsChanged {
22436 total_count: self.total_review_comment_count(),
22437 });
22438 cx.notify();
22439 id
22440 }
22441
22442 /// Removes a review comment by ID from any hunk.
22443 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22444 for (_, comments) in self.stored_review_comments.iter_mut() {
22445 if let Some(index) = comments.iter().position(|c| c.id == id) {
22446 comments.remove(index);
22447 cx.emit(EditorEvent::ReviewCommentsChanged {
22448 total_count: self.total_review_comment_count(),
22449 });
22450 cx.notify();
22451 return true;
22452 }
22453 }
22454 false
22455 }
22456
22457 /// Updates a review comment's text by ID.
22458 pub fn update_review_comment(
22459 &mut self,
22460 id: usize,
22461 new_comment: String,
22462 cx: &mut Context<Self>,
22463 ) -> bool {
22464 for (_, comments) in self.stored_review_comments.iter_mut() {
22465 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22466 comment.comment = new_comment;
22467 comment.is_editing = false;
22468 cx.emit(EditorEvent::ReviewCommentsChanged {
22469 total_count: self.total_review_comment_count(),
22470 });
22471 cx.notify();
22472 return true;
22473 }
22474 }
22475 false
22476 }
22477
22478 /// Sets a comment's editing state.
22479 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22480 for (_, comments) in self.stored_review_comments.iter_mut() {
22481 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22482 comment.is_editing = is_editing;
22483 cx.notify();
22484 return;
22485 }
22486 }
22487 }
22488
22489 /// Takes all stored comments from all hunks, clearing the storage.
22490 /// Returns a Vec of (hunk_key, comments) pairs.
22491 pub fn take_all_review_comments(
22492 &mut self,
22493 cx: &mut Context<Self>,
22494 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22495 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22496 self.dismiss_all_diff_review_overlays(cx);
22497 let comments = std::mem::take(&mut self.stored_review_comments);
22498 // Reset the ID counter since all comments have been taken
22499 self.next_review_comment_id = 0;
22500 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22501 cx.notify();
22502 comments
22503 }
22504
22505 /// Removes review comments whose anchors are no longer valid or whose
22506 /// associated diff hunks no longer exist.
22507 ///
22508 /// This should be called when the buffer changes to prevent orphaned comments
22509 /// from accumulating.
22510 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22511 let snapshot = self.buffer.read(cx).snapshot(cx);
22512 let original_count = self.total_review_comment_count();
22513
22514 // Remove comments with invalid hunk anchors
22515 self.stored_review_comments
22516 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22517
22518 // Also clean up individual comments with invalid anchor ranges
22519 for (_, comments) in &mut self.stored_review_comments {
22520 comments.retain(|comment| {
22521 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22522 });
22523 }
22524
22525 // Remove empty hunk entries
22526 self.stored_review_comments
22527 .retain(|(_, comments)| !comments.is_empty());
22528
22529 let new_count = self.total_review_comment_count();
22530 if new_count != original_count {
22531 cx.emit(EditorEvent::ReviewCommentsChanged {
22532 total_count: new_count,
22533 });
22534 cx.notify();
22535 }
22536 }
22537
22538 /// Toggles the expanded state of the comments section in the overlay.
22539 pub fn toggle_review_comments_expanded(
22540 &mut self,
22541 _: &ToggleReviewCommentsExpanded,
22542 window: &mut Window,
22543 cx: &mut Context<Self>,
22544 ) {
22545 // Find the overlay that currently has focus, or use the first one
22546 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22547 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22548 overlay.comments_expanded = !overlay.comments_expanded;
22549 Some(overlay.hunk_key.clone())
22550 } else {
22551 None
22552 }
22553 });
22554
22555 // If no focused overlay found, toggle the first one
22556 let hunk_key = overlay_info.or_else(|| {
22557 self.diff_review_overlays.first_mut().map(|overlay| {
22558 overlay.comments_expanded = !overlay.comments_expanded;
22559 overlay.hunk_key.clone()
22560 })
22561 });
22562
22563 if let Some(hunk_key) = hunk_key {
22564 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22565 cx.notify();
22566 }
22567 }
22568
22569 /// Handles the EditReviewComment action - sets a comment into editing mode.
22570 pub fn edit_review_comment(
22571 &mut self,
22572 action: &EditReviewComment,
22573 window: &mut Window,
22574 cx: &mut Context<Self>,
22575 ) {
22576 let comment_id = action.id;
22577
22578 // Set the comment to editing mode
22579 self.set_comment_editing(comment_id, true, cx);
22580
22581 // Find the overlay that contains this comment and create an inline editor if needed
22582 // First, find which hunk this comment belongs to
22583 let hunk_key = self
22584 .stored_review_comments
22585 .iter()
22586 .find_map(|(key, comments)| {
22587 if comments.iter().any(|c| c.id == comment_id) {
22588 Some(key.clone())
22589 } else {
22590 None
22591 }
22592 });
22593
22594 let snapshot = self.buffer.read(cx).snapshot(cx);
22595 if let Some(hunk_key) = hunk_key {
22596 if let Some(overlay) = self
22597 .diff_review_overlays
22598 .iter_mut()
22599 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22600 {
22601 if let std::collections::hash_map::Entry::Vacant(entry) =
22602 overlay.inline_edit_editors.entry(comment_id)
22603 {
22604 // Find the comment text
22605 let comment_text = self
22606 .stored_review_comments
22607 .iter()
22608 .flat_map(|(_, comments)| comments)
22609 .find(|c| c.id == comment_id)
22610 .map(|c| c.comment.clone())
22611 .unwrap_or_default();
22612
22613 // Create inline editor
22614 let parent_editor = cx.entity().downgrade();
22615 let inline_editor = cx.new(|cx| {
22616 let mut editor = Editor::single_line(window, cx);
22617 editor.set_text(&*comment_text, window, cx);
22618 // Select all text for easy replacement
22619 editor.select_all(&crate::actions::SelectAll, window, cx);
22620 editor
22621 });
22622
22623 // Register the Newline action to confirm the edit
22624 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22625 inline_editor.register_action({
22626 let parent_editor = parent_editor.clone();
22627 move |_: &crate::actions::Newline, window, cx| {
22628 if let Some(editor) = parent_editor.upgrade() {
22629 editor.update(cx, |editor, cx| {
22630 editor.confirm_edit_review_comment(comment_id, window, cx);
22631 });
22632 }
22633 }
22634 })
22635 });
22636
22637 // Store the subscription to keep the action handler alive
22638 overlay
22639 .inline_edit_subscriptions
22640 .insert(comment_id, subscription);
22641
22642 // Focus the inline editor
22643 let focus_handle = inline_editor.focus_handle(cx);
22644 window.focus(&focus_handle, cx);
22645
22646 entry.insert(inline_editor);
22647 }
22648 }
22649 }
22650
22651 cx.notify();
22652 }
22653
22654 /// Confirms an inline edit of a review comment.
22655 pub fn confirm_edit_review_comment(
22656 &mut self,
22657 comment_id: usize,
22658 _window: &mut Window,
22659 cx: &mut Context<Self>,
22660 ) {
22661 // Get the new text from the inline editor
22662 // Find the overlay containing this comment's inline editor
22663 let snapshot = self.buffer.read(cx).snapshot(cx);
22664 let hunk_key = self
22665 .stored_review_comments
22666 .iter()
22667 .find_map(|(key, comments)| {
22668 if comments.iter().any(|c| c.id == comment_id) {
22669 Some(key.clone())
22670 } else {
22671 None
22672 }
22673 });
22674
22675 let new_text = hunk_key
22676 .as_ref()
22677 .and_then(|hunk_key| {
22678 self.diff_review_overlays
22679 .iter()
22680 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22681 })
22682 .as_ref()
22683 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22684 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22685
22686 if let Some(new_text) = new_text {
22687 if !new_text.is_empty() {
22688 self.update_review_comment(comment_id, new_text, cx);
22689 }
22690 }
22691
22692 // Remove the inline editor and its subscription
22693 if let Some(hunk_key) = hunk_key {
22694 if let Some(overlay) = self
22695 .diff_review_overlays
22696 .iter_mut()
22697 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22698 {
22699 overlay.inline_edit_editors.remove(&comment_id);
22700 overlay.inline_edit_subscriptions.remove(&comment_id);
22701 }
22702 }
22703
22704 // Clear editing state
22705 self.set_comment_editing(comment_id, false, cx);
22706 }
22707
22708 /// Cancels an inline edit of a review comment.
22709 pub fn cancel_edit_review_comment(
22710 &mut self,
22711 comment_id: usize,
22712 _window: &mut Window,
22713 cx: &mut Context<Self>,
22714 ) {
22715 // Find which hunk this comment belongs to
22716 let hunk_key = self
22717 .stored_review_comments
22718 .iter()
22719 .find_map(|(key, comments)| {
22720 if comments.iter().any(|c| c.id == comment_id) {
22721 Some(key.clone())
22722 } else {
22723 None
22724 }
22725 });
22726
22727 // Remove the inline editor and its subscription
22728 if let Some(hunk_key) = hunk_key {
22729 let snapshot = self.buffer.read(cx).snapshot(cx);
22730 if let Some(overlay) = self
22731 .diff_review_overlays
22732 .iter_mut()
22733 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22734 {
22735 overlay.inline_edit_editors.remove(&comment_id);
22736 overlay.inline_edit_subscriptions.remove(&comment_id);
22737 }
22738 }
22739
22740 // Clear editing state
22741 self.set_comment_editing(comment_id, false, cx);
22742 }
22743
22744 /// Action handler for ConfirmEditReviewComment.
22745 pub fn confirm_edit_review_comment_action(
22746 &mut self,
22747 action: &ConfirmEditReviewComment,
22748 window: &mut Window,
22749 cx: &mut Context<Self>,
22750 ) {
22751 self.confirm_edit_review_comment(action.id, window, cx);
22752 }
22753
22754 /// Action handler for CancelEditReviewComment.
22755 pub fn cancel_edit_review_comment_action(
22756 &mut self,
22757 action: &CancelEditReviewComment,
22758 window: &mut Window,
22759 cx: &mut Context<Self>,
22760 ) {
22761 self.cancel_edit_review_comment(action.id, window, cx);
22762 }
22763
22764 /// Handles the DeleteReviewComment action - removes a comment.
22765 pub fn delete_review_comment(
22766 &mut self,
22767 action: &DeleteReviewComment,
22768 window: &mut Window,
22769 cx: &mut Context<Self>,
22770 ) {
22771 // Get the hunk key before removing the comment
22772 // Find the hunk key from the comment itself
22773 let comment_id = action.id;
22774 let hunk_key = self
22775 .stored_review_comments
22776 .iter()
22777 .find_map(|(key, comments)| {
22778 if comments.iter().any(|c| c.id == comment_id) {
22779 Some(key.clone())
22780 } else {
22781 None
22782 }
22783 });
22784
22785 // Also get it from the overlay for refresh purposes
22786 let overlay_hunk_key = self
22787 .diff_review_overlays
22788 .first()
22789 .map(|o| o.hunk_key.clone());
22790
22791 self.remove_review_comment(action.id, cx);
22792
22793 // Refresh the overlay height after removing a comment
22794 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22795 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22796 }
22797 }
22798
22799 fn render_diff_review_overlay(
22800 prompt_editor: &Entity<Editor>,
22801 hunk_key: &DiffHunkKey,
22802 editor_handle: &WeakEntity<Editor>,
22803 cx: &mut BlockContext,
22804 ) -> AnyElement {
22805 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22806 if ranges.is_empty() {
22807 return None;
22808 }
22809 let formatted: Vec<String> = ranges
22810 .iter()
22811 .map(|(start, end)| {
22812 let start_line = start + 1;
22813 let end_line = end + 1;
22814 if start_line == end_line {
22815 format!("Line {start_line}")
22816 } else {
22817 format!("Lines {start_line}-{end_line}")
22818 }
22819 })
22820 .collect();
22821 // Don't show label for single line in single excerpt
22822 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22823 return None;
22824 }
22825 Some(formatted.join(" ⋯ "))
22826 }
22827
22828 let theme = cx.theme();
22829 let colors = theme.colors();
22830
22831 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22832 editor_handle
22833 .upgrade()
22834 .map(|editor| {
22835 let editor = editor.read(cx);
22836 let snapshot = editor.buffer().read(cx).snapshot(cx);
22837 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22838 let (expanded, editors, avatar_uri, line_ranges) = editor
22839 .diff_review_overlays
22840 .iter()
22841 .find(|overlay| {
22842 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22843 })
22844 .map(|o| {
22845 let start_point = o.anchor_range.start.to_point(&snapshot);
22846 let end_point = o.anchor_range.end.to_point(&snapshot);
22847 // Get line ranges per excerpt to detect discontinuities
22848 let buffer_ranges =
22849 snapshot.range_to_buffer_ranges(start_point..end_point);
22850 let ranges: Vec<(u32, u32)> = buffer_ranges
22851 .iter()
22852 .map(|(buffer_snapshot, range, _)| {
22853 let start = buffer_snapshot.offset_to_point(range.start.0).row;
22854 let end = buffer_snapshot.offset_to_point(range.end.0).row;
22855 (start, end)
22856 })
22857 .collect();
22858 (
22859 o.comments_expanded,
22860 o.inline_edit_editors.clone(),
22861 o.user_avatar_uri.clone(),
22862 if ranges.is_empty() {
22863 None
22864 } else {
22865 Some(ranges)
22866 },
22867 )
22868 })
22869 .unwrap_or((true, HashMap::default(), None, None));
22870 (comments, expanded, editors, avatar_uri, line_ranges)
22871 })
22872 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22873
22874 let comment_count = comments.len();
22875 let avatar_size = px(20.);
22876 let action_icon_size = IconSize::XSmall;
22877
22878 v_flex()
22879 .w_full()
22880 .bg(colors.editor_background)
22881 .border_b_1()
22882 .border_color(colors.border)
22883 .px_2()
22884 .pb_2()
22885 .gap_2()
22886 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22887 .when_some(line_ranges, |el, ranges| {
22888 let label = format_line_ranges(&ranges);
22889 if let Some(label) = label {
22890 el.child(
22891 h_flex()
22892 .w_full()
22893 .px_2()
22894 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22895 )
22896 } else {
22897 el
22898 }
22899 })
22900 // Top row: editable input with user's avatar
22901 .child(
22902 h_flex()
22903 .w_full()
22904 .items_center()
22905 .gap_2()
22906 .px_2()
22907 .py_1p5()
22908 .rounded_md()
22909 .bg(colors.surface_background)
22910 .child(
22911 div()
22912 .size(avatar_size)
22913 .flex_shrink_0()
22914 .rounded_full()
22915 .overflow_hidden()
22916 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22917 Avatar::new(avatar_uri.clone())
22918 .size(avatar_size)
22919 .into_any_element()
22920 } else {
22921 Icon::new(IconName::Person)
22922 .size(IconSize::Small)
22923 .color(ui::Color::Muted)
22924 .into_any_element()
22925 }),
22926 )
22927 .child(
22928 div()
22929 .flex_1()
22930 .border_1()
22931 .border_color(colors.border)
22932 .rounded_md()
22933 .bg(colors.editor_background)
22934 .px_2()
22935 .py_1()
22936 .child(prompt_editor.clone()),
22937 )
22938 .child(
22939 h_flex()
22940 .flex_shrink_0()
22941 .gap_1()
22942 .child(
22943 IconButton::new("diff-review-close", IconName::Close)
22944 .icon_color(ui::Color::Muted)
22945 .icon_size(action_icon_size)
22946 .tooltip(Tooltip::text("Close"))
22947 .on_click(|_, window, cx| {
22948 window
22949 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22950 }),
22951 )
22952 .child(
22953 IconButton::new("diff-review-add", IconName::Return)
22954 .icon_color(ui::Color::Muted)
22955 .icon_size(action_icon_size)
22956 .tooltip(Tooltip::text("Add comment"))
22957 .on_click(|_, window, cx| {
22958 window.dispatch_action(
22959 Box::new(crate::actions::SubmitDiffReviewComment),
22960 cx,
22961 );
22962 }),
22963 ),
22964 ),
22965 )
22966 // Expandable comments section (only shown when there are comments)
22967 .when(comment_count > 0, |el| {
22968 el.child(Self::render_comments_section(
22969 comments,
22970 comments_expanded,
22971 inline_editors,
22972 user_avatar_uri,
22973 avatar_size,
22974 action_icon_size,
22975 colors,
22976 ))
22977 })
22978 .into_any_element()
22979 }
22980
22981 fn render_comments_section(
22982 comments: Vec<StoredReviewComment>,
22983 expanded: bool,
22984 inline_editors: HashMap<usize, Entity<Editor>>,
22985 user_avatar_uri: Option<SharedUri>,
22986 avatar_size: Pixels,
22987 action_icon_size: IconSize,
22988 colors: &theme::ThemeColors,
22989 ) -> impl IntoElement {
22990 let comment_count = comments.len();
22991
22992 v_flex()
22993 .w_full()
22994 .gap_1()
22995 // Header with expand/collapse toggle
22996 .child(
22997 h_flex()
22998 .id("review-comments-header")
22999 .w_full()
23000 .items_center()
23001 .gap_1()
23002 .px_2()
23003 .py_1()
23004 .cursor_pointer()
23005 .rounded_md()
23006 .hover(|style| style.bg(colors.ghost_element_hover))
23007 .on_click(|_, window: &mut Window, cx| {
23008 window.dispatch_action(
23009 Box::new(crate::actions::ToggleReviewCommentsExpanded),
23010 cx,
23011 );
23012 })
23013 .child(
23014 Icon::new(if expanded {
23015 IconName::ChevronDown
23016 } else {
23017 IconName::ChevronRight
23018 })
23019 .size(IconSize::Small)
23020 .color(ui::Color::Muted),
23021 )
23022 .child(
23023 Label::new(format!(
23024 "{} Comment{}",
23025 comment_count,
23026 if comment_count == 1 { "" } else { "s" }
23027 ))
23028 .size(LabelSize::Small)
23029 .color(Color::Muted),
23030 ),
23031 )
23032 // Comments list (when expanded)
23033 .when(expanded, |el| {
23034 el.children(comments.into_iter().map(|comment| {
23035 let inline_editor = inline_editors.get(&comment.id).cloned();
23036 Self::render_comment_row(
23037 comment,
23038 inline_editor,
23039 user_avatar_uri.clone(),
23040 avatar_size,
23041 action_icon_size,
23042 colors,
23043 )
23044 }))
23045 })
23046 }
23047
23048 fn render_comment_row(
23049 comment: StoredReviewComment,
23050 inline_editor: Option<Entity<Editor>>,
23051 user_avatar_uri: Option<SharedUri>,
23052 avatar_size: Pixels,
23053 action_icon_size: IconSize,
23054 colors: &theme::ThemeColors,
23055 ) -> impl IntoElement {
23056 let comment_id = comment.id;
23057 let is_editing = inline_editor.is_some();
23058
23059 h_flex()
23060 .w_full()
23061 .items_center()
23062 .gap_2()
23063 .px_2()
23064 .py_1p5()
23065 .rounded_md()
23066 .bg(colors.surface_background)
23067 .child(
23068 div()
23069 .size(avatar_size)
23070 .flex_shrink_0()
23071 .rounded_full()
23072 .overflow_hidden()
23073 .child(if let Some(ref avatar_uri) = user_avatar_uri {
23074 Avatar::new(avatar_uri.clone())
23075 .size(avatar_size)
23076 .into_any_element()
23077 } else {
23078 Icon::new(IconName::Person)
23079 .size(IconSize::Small)
23080 .color(ui::Color::Muted)
23081 .into_any_element()
23082 }),
23083 )
23084 .child(if let Some(editor) = inline_editor {
23085 // Inline edit mode: show an editable text field
23086 div()
23087 .flex_1()
23088 .border_1()
23089 .border_color(colors.border)
23090 .rounded_md()
23091 .bg(colors.editor_background)
23092 .px_2()
23093 .py_1()
23094 .child(editor)
23095 .into_any_element()
23096 } else {
23097 // Display mode: show the comment text
23098 div()
23099 .flex_1()
23100 .text_sm()
23101 .text_color(colors.text)
23102 .child(comment.comment)
23103 .into_any_element()
23104 })
23105 .child(if is_editing {
23106 // Editing mode: show close and confirm buttons
23107 h_flex()
23108 .gap_1()
23109 .child(
23110 IconButton::new(
23111 format!("diff-review-cancel-edit-{comment_id}"),
23112 IconName::Close,
23113 )
23114 .icon_color(ui::Color::Muted)
23115 .icon_size(action_icon_size)
23116 .tooltip(Tooltip::text("Cancel"))
23117 .on_click(move |_, window, cx| {
23118 window.dispatch_action(
23119 Box::new(crate::actions::CancelEditReviewComment {
23120 id: comment_id,
23121 }),
23122 cx,
23123 );
23124 }),
23125 )
23126 .child(
23127 IconButton::new(
23128 format!("diff-review-confirm-edit-{comment_id}"),
23129 IconName::Return,
23130 )
23131 .icon_color(ui::Color::Muted)
23132 .icon_size(action_icon_size)
23133 .tooltip(Tooltip::text("Confirm"))
23134 .on_click(move |_, window, cx| {
23135 window.dispatch_action(
23136 Box::new(crate::actions::ConfirmEditReviewComment {
23137 id: comment_id,
23138 }),
23139 cx,
23140 );
23141 }),
23142 )
23143 .into_any_element()
23144 } else {
23145 // Display mode: no action buttons for now (edit/delete not yet implemented)
23146 gpui::Empty.into_any_element()
23147 })
23148 }
23149
23150 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
23151 if self.display_map.read(cx).masked != masked {
23152 self.display_map.update(cx, |map, _| map.masked = masked);
23153 }
23154 cx.notify()
23155 }
23156
23157 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
23158 self.show_wrap_guides = Some(show_wrap_guides);
23159 cx.notify();
23160 }
23161
23162 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
23163 self.show_indent_guides = Some(show_indent_guides);
23164 cx.notify();
23165 }
23166
23167 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
23168 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
23169 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
23170 && let Some(dir) = file.abs_path(cx).parent()
23171 {
23172 return Some(dir.to_owned());
23173 }
23174 }
23175
23176 None
23177 }
23178
23179 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
23180 self.active_buffer(cx)?
23181 .read(cx)
23182 .file()
23183 .and_then(|f| f.as_local())
23184 }
23185
23186 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
23187 self.active_buffer(cx).and_then(|buffer| {
23188 let buffer = buffer.read(cx);
23189 if let Some(project_path) = buffer.project_path(cx) {
23190 let project = self.project()?.read(cx);
23191 project.absolute_path(&project_path, cx)
23192 } else {
23193 buffer
23194 .file()
23195 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
23196 }
23197 })
23198 }
23199
23200 pub fn reveal_in_finder(
23201 &mut self,
23202 _: &RevealInFileManager,
23203 _window: &mut Window,
23204 cx: &mut Context<Self>,
23205 ) {
23206 if let Some(path) = self.target_file_abs_path(cx) {
23207 if let Some(project) = self.project() {
23208 project.update(cx, |project, cx| project.reveal_path(&path, cx));
23209 } else {
23210 cx.reveal_path(&path);
23211 }
23212 }
23213 }
23214
23215 pub fn copy_path(
23216 &mut self,
23217 _: &zed_actions::workspace::CopyPath,
23218 _window: &mut Window,
23219 cx: &mut Context<Self>,
23220 ) {
23221 if let Some(path) = self.target_file_abs_path(cx)
23222 && let Some(path) = path.to_str()
23223 {
23224 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
23225 } else {
23226 cx.propagate();
23227 }
23228 }
23229
23230 pub fn copy_relative_path(
23231 &mut self,
23232 _: &zed_actions::workspace::CopyRelativePath,
23233 _window: &mut Window,
23234 cx: &mut Context<Self>,
23235 ) {
23236 if let Some(path) = self.active_buffer(cx).and_then(|buffer| {
23237 let project = self.project()?.read(cx);
23238 let path = buffer.read(cx).file()?.path();
23239 let path = path.display(project.path_style(cx));
23240 Some(path)
23241 }) {
23242 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
23243 } else {
23244 cx.propagate();
23245 }
23246 }
23247
23248 /// Returns the project path for the editor's buffer, if any buffer is
23249 /// opened in the editor.
23250 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
23251 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
23252 buffer.read(cx).project_path(cx)
23253 } else {
23254 None
23255 }
23256 }
23257
23258 // Returns true if the editor handled a go-to-line request
23259 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
23260 maybe!({
23261 let breakpoint_store = self.breakpoint_store.as_ref()?;
23262
23263 let (active_stack_frame, debug_line_pane_id) = {
23264 let store = breakpoint_store.read(cx);
23265 let active_stack_frame = store.active_position().cloned();
23266 let debug_line_pane_id = store.active_debug_line_pane_id();
23267 (active_stack_frame, debug_line_pane_id)
23268 };
23269
23270 let Some(active_stack_frame) = active_stack_frame else {
23271 self.clear_row_highlights::<ActiveDebugLine>();
23272 return None;
23273 };
23274
23275 if let Some(debug_line_pane_id) = debug_line_pane_id {
23276 if let Some(workspace) = self
23277 .workspace
23278 .as_ref()
23279 .and_then(|(workspace, _)| workspace.upgrade())
23280 {
23281 let editor_pane_id = workspace
23282 .read(cx)
23283 .pane_for_item_id(cx.entity_id())
23284 .map(|pane| pane.entity_id());
23285
23286 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
23287 self.clear_row_highlights::<ActiveDebugLine>();
23288 return None;
23289 }
23290 }
23291 }
23292
23293 let position = active_stack_frame.position;
23294
23295 let snapshot = self.buffer.read(cx).snapshot(cx);
23296 let multibuffer_anchor = snapshot.anchor_in_excerpt(position)?;
23297
23298 self.clear_row_highlights::<ActiveDebugLine>();
23299
23300 self.go_to_line::<ActiveDebugLine>(
23301 multibuffer_anchor,
23302 Some(cx.theme().colors().editor_debugger_active_line_background),
23303 window,
23304 cx,
23305 );
23306
23307 cx.notify();
23308
23309 Some(())
23310 })
23311 .is_some()
23312 }
23313
23314 pub fn copy_file_name_without_extension(
23315 &mut self,
23316 _: &CopyFileNameWithoutExtension,
23317 _: &mut Window,
23318 cx: &mut Context<Self>,
23319 ) {
23320 if let Some(file_stem) = self.active_buffer(cx).and_then(|buffer| {
23321 let file = buffer.read(cx).file()?;
23322 file.path().file_stem()
23323 }) {
23324 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
23325 }
23326 }
23327
23328 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
23329 if let Some(file_name) = self.active_buffer(cx).and_then(|buffer| {
23330 let file = buffer.read(cx).file()?;
23331 Some(file.file_name(cx))
23332 }) {
23333 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
23334 }
23335 }
23336
23337 pub fn toggle_git_blame(
23338 &mut self,
23339 _: &::git::Blame,
23340 window: &mut Window,
23341 cx: &mut Context<Self>,
23342 ) {
23343 self.show_git_blame_gutter = !self.show_git_blame_gutter;
23344
23345 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
23346 self.start_git_blame(true, window, cx);
23347 }
23348
23349 cx.notify();
23350 }
23351
23352 pub fn toggle_git_blame_inline(
23353 &mut self,
23354 _: &ToggleGitBlameInline,
23355 window: &mut Window,
23356 cx: &mut Context<Self>,
23357 ) {
23358 self.toggle_git_blame_inline_internal(true, window, cx);
23359 cx.notify();
23360 }
23361
23362 pub fn open_git_blame_commit(
23363 &mut self,
23364 _: &OpenGitBlameCommit,
23365 window: &mut Window,
23366 cx: &mut Context<Self>,
23367 ) {
23368 self.open_git_blame_commit_internal(window, cx);
23369 }
23370
23371 fn open_git_blame_commit_internal(
23372 &mut self,
23373 window: &mut Window,
23374 cx: &mut Context<Self>,
23375 ) -> Option<()> {
23376 let blame = self.blame.as_ref()?;
23377 let snapshot = self.snapshot(window, cx);
23378 let cursor = self
23379 .selections
23380 .newest::<Point>(&snapshot.display_snapshot)
23381 .head();
23382 let (buffer, point) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
23383 let (_, blame_entry) = blame
23384 .update(cx, |blame, cx| {
23385 blame
23386 .blame_for_rows(
23387 &[RowInfo {
23388 buffer_id: Some(buffer.remote_id()),
23389 buffer_row: Some(point.row),
23390 ..Default::default()
23391 }],
23392 cx,
23393 )
23394 .next()
23395 })
23396 .flatten()?;
23397 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23398 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23399 let workspace = self.workspace()?.downgrade();
23400 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23401 None
23402 }
23403
23404 pub fn git_blame_inline_enabled(&self) -> bool {
23405 self.git_blame_inline_enabled
23406 }
23407
23408 pub fn toggle_selection_menu(
23409 &mut self,
23410 _: &ToggleSelectionMenu,
23411 _: &mut Window,
23412 cx: &mut Context<Self>,
23413 ) {
23414 self.show_selection_menu = self
23415 .show_selection_menu
23416 .map(|show_selections_menu| !show_selections_menu)
23417 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23418
23419 cx.notify();
23420 }
23421
23422 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23423 self.show_selection_menu
23424 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23425 }
23426
23427 fn start_git_blame(
23428 &mut self,
23429 user_triggered: bool,
23430 window: &mut Window,
23431 cx: &mut Context<Self>,
23432 ) {
23433 if let Some(project) = self.project() {
23434 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23435 && buffer.read(cx).file().is_none()
23436 {
23437 return;
23438 }
23439
23440 let focused = self.focus_handle(cx).contains_focused(window, cx);
23441
23442 let project = project.clone();
23443 let blame = cx
23444 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23445 self.blame_subscription =
23446 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23447 self.blame = Some(blame);
23448 }
23449 }
23450
23451 fn toggle_git_blame_inline_internal(
23452 &mut self,
23453 user_triggered: bool,
23454 window: &mut Window,
23455 cx: &mut Context<Self>,
23456 ) {
23457 if self.git_blame_inline_enabled {
23458 self.git_blame_inline_enabled = false;
23459 self.show_git_blame_inline = false;
23460 self.show_git_blame_inline_delay_task.take();
23461 } else {
23462 self.git_blame_inline_enabled = true;
23463 self.start_git_blame_inline(user_triggered, window, cx);
23464 }
23465
23466 cx.notify();
23467 }
23468
23469 fn start_git_blame_inline(
23470 &mut self,
23471 user_triggered: bool,
23472 window: &mut Window,
23473 cx: &mut Context<Self>,
23474 ) {
23475 self.start_git_blame(user_triggered, window, cx);
23476
23477 if ProjectSettings::get_global(cx)
23478 .git
23479 .inline_blame_delay()
23480 .is_some()
23481 {
23482 self.start_inline_blame_timer(window, cx);
23483 } else {
23484 self.show_git_blame_inline = true
23485 }
23486 }
23487
23488 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23489 self.blame.as_ref()
23490 }
23491
23492 pub fn show_git_blame_gutter(&self) -> bool {
23493 self.show_git_blame_gutter
23494 }
23495
23496 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23497 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23498 }
23499
23500 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23501 self.show_git_blame_inline
23502 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23503 && !self.newest_selection_head_on_empty_line(cx)
23504 && self.has_blame_entries(cx)
23505 }
23506
23507 fn has_blame_entries(&self, cx: &App) -> bool {
23508 self.blame()
23509 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23510 }
23511
23512 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23513 let cursor_anchor = self.selections.newest_anchor().head();
23514
23515 let snapshot = self.buffer.read(cx).snapshot(cx);
23516 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23517
23518 snapshot.line_len(buffer_row) == 0
23519 }
23520
23521 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23522 let buffer_and_selection = maybe!({
23523 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23524 let selection_range = selection.range();
23525
23526 let multi_buffer = self.buffer().read(cx);
23527 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23528 let buffer_ranges = multi_buffer_snapshot
23529 .range_to_buffer_ranges(selection_range.start..selection_range.end);
23530
23531 let (buffer_snapshot, range, _) = if selection.reversed {
23532 buffer_ranges.first()
23533 } else {
23534 buffer_ranges.last()
23535 }?;
23536
23537 let buffer_range = range.to_point(buffer_snapshot);
23538 let buffer = multi_buffer.buffer(buffer_snapshot.remote_id()).unwrap();
23539
23540 let Some(buffer_diff) = multi_buffer.diff_for(buffer_snapshot.remote_id()) else {
23541 return Some((buffer, buffer_range.start.row..buffer_range.end.row));
23542 };
23543
23544 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23545 let start = buffer_diff_snapshot
23546 .buffer_point_to_base_text_point(buffer_range.start, &buffer_snapshot);
23547 let end = buffer_diff_snapshot
23548 .buffer_point_to_base_text_point(buffer_range.end, &buffer_snapshot);
23549
23550 Some((buffer, start.row..end.row))
23551 });
23552
23553 let Some((buffer, selection)) = buffer_and_selection else {
23554 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23555 };
23556
23557 let Some(project) = self.project() else {
23558 return Task::ready(Err(anyhow!("editor does not have project")));
23559 };
23560
23561 project.update(cx, |project, cx| {
23562 project.get_permalink_to_line(&buffer, selection, cx)
23563 })
23564 }
23565
23566 pub fn copy_permalink_to_line(
23567 &mut self,
23568 _: &CopyPermalinkToLine,
23569 window: &mut Window,
23570 cx: &mut Context<Self>,
23571 ) {
23572 let permalink_task = self.get_permalink_to_line(cx);
23573 let workspace = self.workspace();
23574
23575 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23576 Ok(permalink) => {
23577 cx.update(|_, cx| {
23578 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23579 })
23580 .ok();
23581 }
23582 Err(err) => {
23583 let message = format!("Failed to copy permalink: {err}");
23584
23585 anyhow::Result::<()>::Err(err).log_err();
23586
23587 if let Some(workspace) = workspace {
23588 workspace
23589 .update_in(cx, |workspace, _, cx| {
23590 struct CopyPermalinkToLine;
23591
23592 workspace.show_toast(
23593 Toast::new(
23594 NotificationId::unique::<CopyPermalinkToLine>(),
23595 message,
23596 ),
23597 cx,
23598 )
23599 })
23600 .ok();
23601 }
23602 }
23603 })
23604 .detach();
23605 }
23606
23607 pub fn copy_file_location(
23608 &mut self,
23609 _: &CopyFileLocation,
23610 _: &mut Window,
23611 cx: &mut Context<Self>,
23612 ) {
23613 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23614
23615 let start_line = selection.start.row + 1;
23616 let end_line = selection.end.row + 1;
23617
23618 let end_line = if selection.end.column == 0 && end_line > start_line {
23619 end_line - 1
23620 } else {
23621 end_line
23622 };
23623
23624 if let Some(file_location) = self.active_buffer(cx).and_then(|buffer| {
23625 let project = self.project()?.read(cx);
23626 let file = buffer.read(cx).file()?;
23627 let path = file.path().display(project.path_style(cx));
23628
23629 let location = if start_line == end_line {
23630 format!("{path}:{start_line}")
23631 } else {
23632 format!("{path}:{start_line}-{end_line}")
23633 };
23634 Some(location)
23635 }) {
23636 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23637 }
23638 }
23639
23640 pub fn open_permalink_to_line(
23641 &mut self,
23642 _: &OpenPermalinkToLine,
23643 window: &mut Window,
23644 cx: &mut Context<Self>,
23645 ) {
23646 let permalink_task = self.get_permalink_to_line(cx);
23647 let workspace = self.workspace();
23648
23649 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23650 Ok(permalink) => {
23651 cx.update(|_, cx| {
23652 cx.open_url(permalink.as_ref());
23653 })
23654 .ok();
23655 }
23656 Err(err) => {
23657 let message = format!("Failed to open permalink: {err}");
23658
23659 anyhow::Result::<()>::Err(err).log_err();
23660
23661 if let Some(workspace) = workspace {
23662 workspace.update(cx, |workspace, cx| {
23663 struct OpenPermalinkToLine;
23664
23665 workspace.show_toast(
23666 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23667 cx,
23668 )
23669 });
23670 }
23671 }
23672 })
23673 .detach();
23674 }
23675
23676 pub fn insert_uuid_v4(
23677 &mut self,
23678 _: &InsertUuidV4,
23679 window: &mut Window,
23680 cx: &mut Context<Self>,
23681 ) {
23682 self.insert_uuid(UuidVersion::V4, window, cx);
23683 }
23684
23685 pub fn insert_uuid_v7(
23686 &mut self,
23687 _: &InsertUuidV7,
23688 window: &mut Window,
23689 cx: &mut Context<Self>,
23690 ) {
23691 self.insert_uuid(UuidVersion::V7, window, cx);
23692 }
23693
23694 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23695 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23696 self.transact(window, cx, |this, window, cx| {
23697 let edits = this
23698 .selections
23699 .all::<Point>(&this.display_snapshot(cx))
23700 .into_iter()
23701 .map(|selection| {
23702 let uuid = match version {
23703 UuidVersion::V4 => uuid::Uuid::new_v4(),
23704 UuidVersion::V7 => uuid::Uuid::now_v7(),
23705 };
23706
23707 (selection.range(), uuid.to_string())
23708 });
23709 this.edit(edits, cx);
23710 this.refresh_edit_prediction(true, false, window, cx);
23711 });
23712 }
23713
23714 pub fn open_selections_in_multibuffer(
23715 &mut self,
23716 _: &OpenSelectionsInMultibuffer,
23717 window: &mut Window,
23718 cx: &mut Context<Self>,
23719 ) {
23720 let multibuffer = self.buffer.read(cx);
23721
23722 let Some(buffer) = multibuffer.as_singleton() else {
23723 return;
23724 };
23725 let buffer_snapshot = buffer.read(cx).snapshot();
23726
23727 let Some(workspace) = self.workspace() else {
23728 return;
23729 };
23730
23731 let title = multibuffer.title(cx).to_string();
23732
23733 let locations = self
23734 .selections
23735 .all_anchors(&self.display_snapshot(cx))
23736 .iter()
23737 .map(|selection| {
23738 (
23739 buffer.clone(),
23740 (selection.start.text_anchor_in(&buffer_snapshot)
23741 ..selection.end.text_anchor_in(&buffer_snapshot))
23742 .to_point(buffer.read(cx)),
23743 )
23744 })
23745 .into_group_map();
23746
23747 cx.spawn_in(window, async move |_, cx| {
23748 workspace.update_in(cx, |workspace, window, cx| {
23749 Self::open_locations_in_multibuffer(
23750 workspace,
23751 locations,
23752 format!("Selections for '{title}'"),
23753 false,
23754 false,
23755 MultibufferSelectionMode::All,
23756 window,
23757 cx,
23758 );
23759 })
23760 })
23761 .detach();
23762 }
23763
23764 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23765 /// last highlight added will be used.
23766 ///
23767 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23768 pub fn highlight_rows<T: 'static>(
23769 &mut self,
23770 range: Range<Anchor>,
23771 color: Hsla,
23772 options: RowHighlightOptions,
23773 cx: &mut Context<Self>,
23774 ) {
23775 let snapshot = self.buffer().read(cx).snapshot(cx);
23776 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23777 let ix = row_highlights.binary_search_by(|highlight| {
23778 Ordering::Equal
23779 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23780 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23781 });
23782
23783 if let Err(mut ix) = ix {
23784 let index = post_inc(&mut self.highlight_order);
23785
23786 // If this range intersects with the preceding highlight, then merge it with
23787 // the preceding highlight. Otherwise insert a new highlight.
23788 let mut merged = false;
23789 if ix > 0 {
23790 let prev_highlight = &mut row_highlights[ix - 1];
23791 if prev_highlight
23792 .range
23793 .end
23794 .cmp(&range.start, &snapshot)
23795 .is_ge()
23796 {
23797 ix -= 1;
23798 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23799 prev_highlight.range.end = range.end;
23800 }
23801 merged = true;
23802 prev_highlight.index = index;
23803 prev_highlight.color = color;
23804 prev_highlight.options = options;
23805 }
23806 }
23807
23808 if !merged {
23809 row_highlights.insert(
23810 ix,
23811 RowHighlight {
23812 range,
23813 index,
23814 color,
23815 options,
23816 type_id: TypeId::of::<T>(),
23817 },
23818 );
23819 }
23820
23821 // If any of the following highlights intersect with this one, merge them.
23822 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23823 let highlight = &row_highlights[ix];
23824 if next_highlight
23825 .range
23826 .start
23827 .cmp(&highlight.range.end, &snapshot)
23828 .is_le()
23829 {
23830 if next_highlight
23831 .range
23832 .end
23833 .cmp(&highlight.range.end, &snapshot)
23834 .is_gt()
23835 {
23836 row_highlights[ix].range.end = next_highlight.range.end;
23837 }
23838 row_highlights.remove(ix + 1);
23839 } else {
23840 break;
23841 }
23842 }
23843 }
23844 }
23845
23846 /// Remove any highlighted row ranges of the given type that intersect the
23847 /// given ranges.
23848 pub fn remove_highlighted_rows<T: 'static>(
23849 &mut self,
23850 ranges_to_remove: Vec<Range<Anchor>>,
23851 cx: &mut Context<Self>,
23852 ) {
23853 let snapshot = self.buffer().read(cx).snapshot(cx);
23854 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23855 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23856 row_highlights.retain(|highlight| {
23857 while let Some(range_to_remove) = ranges_to_remove.peek() {
23858 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23859 Ordering::Less | Ordering::Equal => {
23860 ranges_to_remove.next();
23861 }
23862 Ordering::Greater => {
23863 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23864 Ordering::Less | Ordering::Equal => {
23865 return false;
23866 }
23867 Ordering::Greater => break,
23868 }
23869 }
23870 }
23871 }
23872
23873 true
23874 })
23875 }
23876
23877 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23878 pub fn clear_row_highlights<T: 'static>(&mut self) {
23879 self.highlighted_rows.remove(&TypeId::of::<T>());
23880 }
23881
23882 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23883 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23884 self.highlighted_rows
23885 .get(&TypeId::of::<T>())
23886 .map_or(&[] as &[_], |vec| vec.as_slice())
23887 .iter()
23888 .map(|highlight| (highlight.range.clone(), highlight.color))
23889 }
23890
23891 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23892 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23893 /// Allows to ignore certain kinds of highlights.
23894 pub fn highlighted_display_rows(
23895 &self,
23896 window: &mut Window,
23897 cx: &mut App,
23898 ) -> BTreeMap<DisplayRow, LineHighlight> {
23899 let snapshot = self.snapshot(window, cx);
23900 let mut used_highlight_orders = HashMap::default();
23901 self.highlighted_rows
23902 .iter()
23903 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23904 .fold(
23905 BTreeMap::<DisplayRow, LineHighlight>::new(),
23906 |mut unique_rows, highlight| {
23907 let start = highlight.range.start.to_display_point(&snapshot);
23908 let end = highlight.range.end.to_display_point(&snapshot);
23909 let start_row = start.row().0;
23910 let end_row = if !highlight.range.end.is_max() && end.column() == 0 {
23911 end.row().0.saturating_sub(1)
23912 } else {
23913 end.row().0
23914 };
23915 for row in start_row..=end_row {
23916 let used_index =
23917 used_highlight_orders.entry(row).or_insert(highlight.index);
23918 if highlight.index >= *used_index {
23919 *used_index = highlight.index;
23920 unique_rows.insert(
23921 DisplayRow(row),
23922 LineHighlight {
23923 include_gutter: highlight.options.include_gutter,
23924 border: None,
23925 background: highlight.color.into(),
23926 type_id: Some(highlight.type_id),
23927 },
23928 );
23929 }
23930 }
23931 unique_rows
23932 },
23933 )
23934 }
23935
23936 pub fn highlighted_display_row_for_autoscroll(
23937 &self,
23938 snapshot: &DisplaySnapshot,
23939 ) -> Option<DisplayRow> {
23940 self.highlighted_rows
23941 .values()
23942 .flat_map(|highlighted_rows| highlighted_rows.iter())
23943 .filter_map(|highlight| {
23944 if highlight.options.autoscroll {
23945 Some(highlight.range.start.to_display_point(snapshot).row())
23946 } else {
23947 None
23948 }
23949 })
23950 .min()
23951 }
23952
23953 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23954 self.highlight_background(
23955 HighlightKey::SearchWithinRange,
23956 ranges,
23957 |_, colors| colors.colors().editor_document_highlight_read_background,
23958 cx,
23959 )
23960 }
23961
23962 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23963 self.breadcrumb_header = Some(new_header);
23964 }
23965
23966 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23967 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23968 }
23969
23970 pub fn highlight_background(
23971 &mut self,
23972 key: HighlightKey,
23973 ranges: &[Range<Anchor>],
23974 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23975 cx: &mut Context<Self>,
23976 ) {
23977 self.background_highlights
23978 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23979 self.scrollbar_marker_state.dirty = true;
23980 cx.notify();
23981 }
23982
23983 pub fn clear_background_highlights(
23984 &mut self,
23985 key: HighlightKey,
23986 cx: &mut Context<Self>,
23987 ) -> Option<BackgroundHighlight> {
23988 let text_highlights = self.background_highlights.remove(&key)?;
23989 if !text_highlights.1.is_empty() {
23990 self.scrollbar_marker_state.dirty = true;
23991 cx.notify();
23992 }
23993 Some(text_highlights)
23994 }
23995
23996 pub fn highlight_gutter<T: 'static>(
23997 &mut self,
23998 ranges: impl Into<Vec<Range<Anchor>>>,
23999 color_fetcher: fn(&App) -> Hsla,
24000 cx: &mut Context<Self>,
24001 ) {
24002 self.gutter_highlights
24003 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
24004 cx.notify();
24005 }
24006
24007 pub fn clear_gutter_highlights<T: 'static>(
24008 &mut self,
24009 cx: &mut Context<Self>,
24010 ) -> Option<GutterHighlight> {
24011 cx.notify();
24012 self.gutter_highlights.remove(&TypeId::of::<T>())
24013 }
24014
24015 pub fn insert_gutter_highlight<T: 'static>(
24016 &mut self,
24017 range: Range<Anchor>,
24018 color_fetcher: fn(&App) -> Hsla,
24019 cx: &mut Context<Self>,
24020 ) {
24021 let snapshot = self.buffer().read(cx).snapshot(cx);
24022 let mut highlights = self
24023 .gutter_highlights
24024 .remove(&TypeId::of::<T>())
24025 .map(|(_, highlights)| highlights)
24026 .unwrap_or_default();
24027 let ix = highlights.binary_search_by(|highlight| {
24028 Ordering::Equal
24029 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
24030 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
24031 });
24032 if let Err(ix) = ix {
24033 highlights.insert(ix, range);
24034 }
24035 self.gutter_highlights
24036 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
24037 }
24038
24039 pub fn remove_gutter_highlights<T: 'static>(
24040 &mut self,
24041 ranges_to_remove: Vec<Range<Anchor>>,
24042 cx: &mut Context<Self>,
24043 ) {
24044 let snapshot = self.buffer().read(cx).snapshot(cx);
24045 let Some((color_fetcher, mut gutter_highlights)) =
24046 self.gutter_highlights.remove(&TypeId::of::<T>())
24047 else {
24048 return;
24049 };
24050 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
24051 gutter_highlights.retain(|highlight| {
24052 while let Some(range_to_remove) = ranges_to_remove.peek() {
24053 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
24054 Ordering::Less | Ordering::Equal => {
24055 ranges_to_remove.next();
24056 }
24057 Ordering::Greater => {
24058 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
24059 Ordering::Less | Ordering::Equal => {
24060 return false;
24061 }
24062 Ordering::Greater => break,
24063 }
24064 }
24065 }
24066 }
24067
24068 true
24069 });
24070 self.gutter_highlights
24071 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
24072 }
24073
24074 #[cfg(any(test, feature = "test-support"))]
24075 pub fn all_text_highlights(
24076 &self,
24077 window: &mut Window,
24078 cx: &mut Context<Self>,
24079 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
24080 let snapshot = self.snapshot(window, cx);
24081 self.display_map.update(cx, |display_map, _| {
24082 display_map
24083 .all_text_highlights()
24084 .map(|(_, highlight)| {
24085 let (style, ranges) = highlight.as_ref();
24086 (
24087 *style,
24088 ranges
24089 .iter()
24090 .map(|range| range.clone().to_display_points(&snapshot))
24091 .collect(),
24092 )
24093 })
24094 .collect()
24095 })
24096 }
24097
24098 #[cfg(any(test, feature = "test-support"))]
24099 pub fn all_text_background_highlights(
24100 &self,
24101 window: &mut Window,
24102 cx: &mut Context<Self>,
24103 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
24104 let snapshot = self.snapshot(window, cx);
24105 let buffer = &snapshot.buffer_snapshot();
24106 let start = buffer.anchor_before(MultiBufferOffset(0));
24107 let end = buffer.anchor_after(buffer.len());
24108 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
24109 }
24110
24111 #[cfg(any(test, feature = "test-support"))]
24112 pub fn sorted_background_highlights_in_range(
24113 &self,
24114 search_range: Range<Anchor>,
24115 display_snapshot: &DisplaySnapshot,
24116 theme: &Theme,
24117 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
24118 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
24119 res.sort_by(|a, b| {
24120 a.0.start
24121 .cmp(&b.0.start)
24122 .then_with(|| a.0.end.cmp(&b.0.end))
24123 .then_with(|| a.1.cmp(&b.1))
24124 });
24125 res
24126 }
24127
24128 #[cfg(any(test, feature = "test-support"))]
24129 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
24130 let snapshot = self.buffer().read(cx).snapshot(cx);
24131
24132 let highlights = self
24133 .background_highlights
24134 .get(&HighlightKey::BufferSearchHighlights);
24135
24136 if let Some((_color, ranges)) = highlights {
24137 ranges
24138 .iter()
24139 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
24140 .collect_vec()
24141 } else {
24142 vec![]
24143 }
24144 }
24145
24146 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
24147 self.background_highlights
24148 .get(&key)
24149 .is_some_and(|(_, highlights)| !highlights.is_empty())
24150 }
24151
24152 /// Returns all background highlights for a given range.
24153 ///
24154 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
24155 pub fn background_highlights_in_range(
24156 &self,
24157 search_range: Range<Anchor>,
24158 display_snapshot: &DisplaySnapshot,
24159 theme: &Theme,
24160 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
24161 let mut results = Vec::new();
24162 for (color_fetcher, ranges) in self.background_highlights.values() {
24163 let start_ix = match ranges.binary_search_by(|probe| {
24164 let cmp = probe
24165 .end
24166 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
24167 if cmp.is_gt() {
24168 Ordering::Greater
24169 } else {
24170 Ordering::Less
24171 }
24172 }) {
24173 Ok(i) | Err(i) => i,
24174 };
24175 for (index, range) in ranges[start_ix..].iter().enumerate() {
24176 if range
24177 .start
24178 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
24179 .is_ge()
24180 {
24181 break;
24182 }
24183
24184 let color = color_fetcher(&(start_ix + index), theme);
24185 let start = range.start.to_display_point(display_snapshot);
24186 let end = range.end.to_display_point(display_snapshot);
24187 results.push((start..end, color))
24188 }
24189 }
24190 results
24191 }
24192
24193 pub fn gutter_highlights_in_range(
24194 &self,
24195 search_range: Range<Anchor>,
24196 display_snapshot: &DisplaySnapshot,
24197 cx: &App,
24198 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
24199 let mut results = Vec::new();
24200 for (color_fetcher, ranges) in self.gutter_highlights.values() {
24201 let color = color_fetcher(cx);
24202 let start_ix = match ranges.binary_search_by(|probe| {
24203 let cmp = probe
24204 .end
24205 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
24206 if cmp.is_gt() {
24207 Ordering::Greater
24208 } else {
24209 Ordering::Less
24210 }
24211 }) {
24212 Ok(i) | Err(i) => i,
24213 };
24214 for range in &ranges[start_ix..] {
24215 if range
24216 .start
24217 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
24218 .is_ge()
24219 {
24220 break;
24221 }
24222
24223 let start = range.start.to_display_point(display_snapshot);
24224 let end = range.end.to_display_point(display_snapshot);
24225 results.push((start..end, color))
24226 }
24227 }
24228 results
24229 }
24230
24231 /// Get the text ranges corresponding to the redaction query
24232 pub fn redacted_ranges(
24233 &self,
24234 search_range: Range<Anchor>,
24235 display_snapshot: &DisplaySnapshot,
24236 cx: &App,
24237 ) -> Vec<Range<DisplayPoint>> {
24238 display_snapshot
24239 .buffer_snapshot()
24240 .redacted_ranges(search_range, |file| {
24241 if let Some(file) = file {
24242 file.is_private()
24243 && EditorSettings::get(
24244 Some(SettingsLocation {
24245 worktree_id: file.worktree_id(cx),
24246 path: file.path().as_ref(),
24247 }),
24248 cx,
24249 )
24250 .redact_private_values
24251 } else {
24252 false
24253 }
24254 })
24255 .map(|range| {
24256 range.start.to_display_point(display_snapshot)
24257 ..range.end.to_display_point(display_snapshot)
24258 })
24259 .collect()
24260 }
24261
24262 pub fn highlight_text_key(
24263 &mut self,
24264 key: HighlightKey,
24265 ranges: Vec<Range<Anchor>>,
24266 style: HighlightStyle,
24267 merge: bool,
24268 cx: &mut Context<Self>,
24269 ) {
24270 self.display_map.update(cx, |map, cx| {
24271 map.highlight_text(key, ranges, style, merge, cx);
24272 });
24273 cx.notify();
24274 }
24275
24276 pub fn highlight_text(
24277 &mut self,
24278 key: HighlightKey,
24279 ranges: Vec<Range<Anchor>>,
24280 style: HighlightStyle,
24281 cx: &mut Context<Self>,
24282 ) {
24283 self.display_map.update(cx, |map, cx| {
24284 map.highlight_text(key, ranges, style, false, cx)
24285 });
24286 cx.notify();
24287 }
24288
24289 pub fn text_highlights<'a>(
24290 &'a self,
24291 key: HighlightKey,
24292 cx: &'a App,
24293 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
24294 self.display_map.read(cx).text_highlights(key)
24295 }
24296
24297 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
24298 let cleared = self
24299 .display_map
24300 .update(cx, |map, _| map.clear_highlights(key));
24301 if cleared {
24302 cx.notify();
24303 }
24304 }
24305
24306 pub fn clear_highlights_with(
24307 &mut self,
24308 f: &mut dyn FnMut(&HighlightKey) -> bool,
24309 cx: &mut Context<Self>,
24310 ) {
24311 let cleared = self
24312 .display_map
24313 .update(cx, |map, _| map.clear_highlights_with(f));
24314 if cleared {
24315 cx.notify();
24316 }
24317 }
24318
24319 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
24320 (self.read_only(cx) || self.blink_manager.read(cx).visible())
24321 && self.focus_handle.is_focused(window)
24322 }
24323
24324 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
24325 self.show_cursor_when_unfocused = is_enabled;
24326 cx.notify();
24327 }
24328
24329 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
24330 cx.notify();
24331 }
24332
24333 fn on_debug_session_event(
24334 &mut self,
24335 _session: Entity<Session>,
24336 event: &SessionEvent,
24337 cx: &mut Context<Self>,
24338 ) {
24339 if let SessionEvent::InvalidateInlineValue = event {
24340 self.refresh_inline_values(cx);
24341 }
24342 }
24343
24344 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
24345 let Some(semantics) = self.semantics_provider.clone() else {
24346 return;
24347 };
24348
24349 if !self.inline_value_cache.enabled {
24350 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24351 self.splice_inlays(&inlays, Vec::new(), cx);
24352 return;
24353 }
24354
24355 let current_execution_position = self
24356 .highlighted_rows
24357 .get(&TypeId::of::<ActiveDebugLine>())
24358 .and_then(|lines| lines.last().map(|line| line.range.end));
24359
24360 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24361 let inline_values = editor
24362 .update(cx, |editor, cx| {
24363 let Some(current_execution_position) = current_execution_position else {
24364 return Some(Task::ready(Ok(Vec::new())));
24365 };
24366
24367 let (buffer, buffer_anchor) =
24368 editor.buffer.read_with(cx, |multibuffer, cx| {
24369 let multibuffer_snapshot = multibuffer.snapshot(cx);
24370 let (buffer_anchor, _) = multibuffer_snapshot
24371 .anchor_to_buffer_anchor(current_execution_position)?;
24372 let buffer = multibuffer.buffer(buffer_anchor.buffer_id)?;
24373 Some((buffer, buffer_anchor))
24374 })?;
24375
24376 let range = buffer.read(cx).anchor_before(0)..buffer_anchor;
24377
24378 semantics.inline_values(buffer, range, cx)
24379 })
24380 .ok()
24381 .flatten()?
24382 .await
24383 .context("refreshing debugger inlays")
24384 .log_err()?;
24385
24386 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24387
24388 for (buffer_id, inline_value) in inline_values
24389 .into_iter()
24390 .map(|hint| (hint.position.buffer_id, hint))
24391 {
24392 buffer_inline_values
24393 .entry(buffer_id)
24394 .or_default()
24395 .push(inline_value);
24396 }
24397
24398 editor
24399 .update(cx, |editor, cx| {
24400 let snapshot = editor.buffer.read(cx).snapshot(cx);
24401 let mut new_inlays = Vec::default();
24402
24403 for (_buffer_id, inline_values) in buffer_inline_values {
24404 for hint in inline_values {
24405 let Some(anchor) = snapshot.anchor_in_excerpt(hint.position) else {
24406 continue;
24407 };
24408 let inlay = Inlay::debugger(
24409 post_inc(&mut editor.next_inlay_id),
24410 anchor,
24411 hint.text(),
24412 );
24413 if !inlay.text().chars().contains(&'\n') {
24414 new_inlays.push(inlay);
24415 }
24416 }
24417 }
24418
24419 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24420 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24421
24422 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24423 })
24424 .ok()?;
24425 Some(())
24426 });
24427 }
24428
24429 fn on_buffer_event(
24430 &mut self,
24431 multibuffer: &Entity<MultiBuffer>,
24432 event: &multi_buffer::Event,
24433 window: &mut Window,
24434 cx: &mut Context<Self>,
24435 ) {
24436 match event {
24437 multi_buffer::Event::Edited {
24438 edited_buffer,
24439 is_local,
24440 } => {
24441 self.scrollbar_marker_state.dirty = true;
24442 self.active_indent_guides_state.dirty = true;
24443 self.refresh_active_diagnostics(cx);
24444 self.refresh_code_actions(window, cx);
24445 self.refresh_single_line_folds(window, cx);
24446 let snapshot = self.snapshot(window, cx);
24447 self.refresh_matching_bracket_highlights(&snapshot, cx);
24448 self.refresh_outline_symbols_at_cursor(cx);
24449 self.refresh_sticky_headers(&snapshot, cx);
24450 if *is_local && self.has_active_edit_prediction() {
24451 self.update_visible_edit_prediction(window, cx);
24452 }
24453
24454 // Clean up orphaned review comments after edits
24455 self.cleanup_orphaned_review_comments(cx);
24456
24457 if let Some(buffer) = edited_buffer {
24458 if buffer.read(cx).file().is_none() {
24459 cx.emit(EditorEvent::TitleChanged);
24460 }
24461
24462 if self.project.is_some() {
24463 let buffer_id = buffer.read(cx).remote_id();
24464 self.register_buffer(buffer_id, cx);
24465 self.update_lsp_data(Some(buffer_id), window, cx);
24466 self.refresh_inlay_hints(
24467 InlayHintRefreshReason::BufferEdited(buffer_id),
24468 cx,
24469 );
24470 }
24471 }
24472
24473 cx.emit(EditorEvent::BufferEdited);
24474 cx.emit(SearchEvent::MatchesInvalidated);
24475
24476 let Some(project) = &self.project else { return };
24477 let (telemetry, is_via_ssh) = {
24478 let project = project.read(cx);
24479 let telemetry = project.client().telemetry().clone();
24480 let is_via_ssh = project.is_via_remote_server();
24481 (telemetry, is_via_ssh)
24482 };
24483 telemetry.log_edit_event("editor", is_via_ssh);
24484 }
24485 multi_buffer::Event::BufferRangesUpdated {
24486 buffer,
24487 ranges,
24488 path_key,
24489 } => {
24490 self.refresh_document_highlights(cx);
24491 let buffer_id = buffer.read(cx).remote_id();
24492 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24493 && let Some(project) = &self.project
24494 {
24495 update_uncommitted_diff_for_buffer(
24496 cx.entity(),
24497 project,
24498 [buffer.clone()],
24499 self.buffer.clone(),
24500 cx,
24501 )
24502 .detach();
24503 }
24504 self.register_visible_buffers(cx);
24505 self.update_lsp_data(Some(buffer_id), window, cx);
24506 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24507 self.refresh_runnables(None, window, cx);
24508 self.bracket_fetched_tree_sitter_chunks
24509 .retain(|range, _| range.start.buffer_id != buffer_id);
24510 self.colorize_brackets(false, cx);
24511 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24512 self.semantic_token_state.invalidate_buffer(&buffer_id);
24513 cx.emit(EditorEvent::BufferRangesUpdated {
24514 buffer: buffer.clone(),
24515 ranges: ranges.clone(),
24516 path_key: path_key.clone(),
24517 });
24518 }
24519 multi_buffer::Event::BuffersRemoved { removed_buffer_ids } => {
24520 if let Some(inlay_hints) = &mut self.inlay_hints {
24521 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24522 }
24523 self.refresh_inlay_hints(
24524 InlayHintRefreshReason::BuffersRemoved(removed_buffer_ids.clone()),
24525 cx,
24526 );
24527 for buffer_id in removed_buffer_ids {
24528 self.registered_buffers.remove(buffer_id);
24529 self.clear_runnables(Some(*buffer_id));
24530 self.semantic_token_state.invalidate_buffer(buffer_id);
24531 self.display_map.update(cx, |display_map, cx| {
24532 display_map.invalidate_semantic_highlights(*buffer_id);
24533 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24534 });
24535 }
24536
24537 self.display_map.update(cx, |display_map, cx| {
24538 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24539 });
24540
24541 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24542 cx.emit(EditorEvent::BuffersRemoved {
24543 removed_buffer_ids: removed_buffer_ids.clone(),
24544 });
24545 }
24546 multi_buffer::Event::BuffersEdited { buffer_ids } => {
24547 self.display_map.update(cx, |map, cx| {
24548 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24549 });
24550 cx.emit(EditorEvent::BuffersEdited {
24551 buffer_ids: buffer_ids.clone(),
24552 });
24553 }
24554 multi_buffer::Event::Reparsed(buffer_id) => {
24555 self.refresh_runnables(Some(*buffer_id), window, cx);
24556 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24557 self.colorize_brackets(true, cx);
24558 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24559
24560 cx.emit(EditorEvent::Reparsed(*buffer_id));
24561 }
24562 multi_buffer::Event::DiffHunksToggled => {
24563 self.refresh_runnables(None, window, cx);
24564 }
24565 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24566 if !is_fresh_language {
24567 self.registered_buffers.remove(&buffer_id);
24568 }
24569 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24570 cx.emit(EditorEvent::Reparsed(*buffer_id));
24571 self.update_edit_prediction_settings(cx);
24572 cx.notify();
24573 }
24574 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24575 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24576 multi_buffer::Event::FileHandleChanged
24577 | multi_buffer::Event::Reloaded
24578 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24579 multi_buffer::Event::DiagnosticsUpdated => {
24580 self.update_diagnostics_state(window, cx);
24581 }
24582 _ => {}
24583 };
24584 }
24585
24586 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24587 if !self.diagnostics_enabled() {
24588 return;
24589 }
24590 self.refresh_active_diagnostics(cx);
24591 self.refresh_inline_diagnostics(true, window, cx);
24592 self.scrollbar_marker_state.dirty = true;
24593 cx.notify();
24594 }
24595
24596 pub fn start_temporary_diff_override(&mut self) {
24597 self.load_diff_task.take();
24598 self.temporary_diff_override = true;
24599 }
24600
24601 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24602 self.temporary_diff_override = false;
24603 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24604 self.buffer.update(cx, |buffer, cx| {
24605 buffer.set_all_diff_hunks_collapsed(cx);
24606 });
24607
24608 if let Some(project) = self.project.clone() {
24609 self.load_diff_task = Some(
24610 update_uncommitted_diff_for_buffer(
24611 cx.entity(),
24612 &project,
24613 self.buffer.read(cx).all_buffers(),
24614 self.buffer.clone(),
24615 cx,
24616 )
24617 .shared(),
24618 );
24619 }
24620 }
24621
24622 fn on_display_map_changed(
24623 &mut self,
24624 _: Entity<DisplayMap>,
24625 _: &mut Window,
24626 cx: &mut Context<Self>,
24627 ) {
24628 cx.notify();
24629 }
24630
24631 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24632 if !self.mode.is_full() {
24633 return None;
24634 }
24635
24636 let theme_settings = theme_settings::ThemeSettings::get_global(cx);
24637 let theme = cx.theme();
24638 let accent_colors = theme.accents().clone();
24639
24640 let accent_overrides = theme_settings
24641 .theme_overrides
24642 .get(theme.name.as_ref())
24643 .map(|theme_style| &theme_style.accents)
24644 .into_iter()
24645 .flatten()
24646 .chain(
24647 theme_settings
24648 .experimental_theme_overrides
24649 .as_ref()
24650 .map(|overrides| &overrides.accents)
24651 .into_iter()
24652 .flatten(),
24653 )
24654 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24655 .collect();
24656
24657 Some(AccentData {
24658 colors: accent_colors,
24659 overrides: accent_overrides,
24660 })
24661 }
24662
24663 fn fetch_applicable_language_settings(
24664 &self,
24665 cx: &App,
24666 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24667 if !self.mode.is_full() {
24668 return HashMap::default();
24669 }
24670
24671 self.buffer().read(cx).all_buffers().into_iter().fold(
24672 HashMap::default(),
24673 |mut acc, buffer| {
24674 let buffer = buffer.read(cx);
24675 let language = buffer.language().map(|language| language.name());
24676 if let hash_map::Entry::Vacant(v) = acc.entry(language) {
24677 v.insert(LanguageSettings::for_buffer(&buffer, cx).into_owned());
24678 }
24679 acc
24680 },
24681 )
24682 }
24683
24684 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24685 let new_language_settings = self.fetch_applicable_language_settings(cx);
24686 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24687 self.applicable_language_settings = new_language_settings;
24688
24689 let new_accents = self.fetch_accent_data(cx);
24690 let accents_changed = new_accents != self.accent_data;
24691 self.accent_data = new_accents;
24692
24693 if self.diagnostics_enabled() {
24694 let new_severity = EditorSettings::get_global(cx)
24695 .diagnostics_max_severity
24696 .unwrap_or(DiagnosticSeverity::Hint);
24697 self.set_max_diagnostics_severity(new_severity, cx);
24698 }
24699 self.refresh_runnables(None, window, cx);
24700 self.update_edit_prediction_settings(cx);
24701 self.refresh_edit_prediction(true, false, window, cx);
24702 self.refresh_inline_values(cx);
24703
24704 let old_cursor_shape = self.cursor_shape;
24705 let old_show_breadcrumbs = self.show_breadcrumbs;
24706
24707 {
24708 let editor_settings = EditorSettings::get_global(cx);
24709 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24710 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24711 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24712 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24713 }
24714
24715 if old_cursor_shape != self.cursor_shape {
24716 cx.emit(EditorEvent::CursorShapeChanged);
24717 }
24718
24719 if old_show_breadcrumbs != self.show_breadcrumbs {
24720 cx.emit(EditorEvent::BreadcrumbsChanged);
24721 }
24722
24723 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24724 let project_settings = ProjectSettings::get_global(cx);
24725 (
24726 project_settings.session.restore_unsaved_buffers,
24727 project_settings.diagnostics.inline.enabled,
24728 project_settings.git.inline_blame.enabled,
24729 )
24730 };
24731 self.buffer_serialization = self
24732 .should_serialize_buffer()
24733 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24734
24735 if self.mode.is_full() {
24736 if self.show_inline_diagnostics != show_inline_diagnostics {
24737 self.show_inline_diagnostics = show_inline_diagnostics;
24738 self.refresh_inline_diagnostics(false, window, cx);
24739 }
24740
24741 if self.git_blame_inline_enabled != inline_blame_enabled {
24742 self.toggle_git_blame_inline_internal(false, window, cx);
24743 }
24744
24745 let minimap_settings = EditorSettings::get_global(cx).minimap;
24746 if self.minimap_visibility != MinimapVisibility::Disabled {
24747 if self.minimap_visibility.settings_visibility()
24748 != minimap_settings.minimap_enabled()
24749 {
24750 self.set_minimap_visibility(
24751 MinimapVisibility::for_mode(self.mode(), cx),
24752 window,
24753 cx,
24754 );
24755 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24756 minimap_entity.update(cx, |minimap_editor, cx| {
24757 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24758 })
24759 }
24760 }
24761
24762 if language_settings_changed || accents_changed {
24763 self.colorize_brackets(true, cx);
24764 }
24765
24766 if language_settings_changed {
24767 self.clear_disabled_lsp_folding_ranges(window, cx);
24768 self.refresh_document_symbols(None, cx);
24769 }
24770
24771 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24772 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24773 }) {
24774 if !inlay_splice.is_empty() {
24775 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24776 }
24777 self.refresh_document_colors(None, window, cx);
24778 }
24779
24780 self.refresh_inlay_hints(
24781 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24782 self.selections.newest_anchor().head(),
24783 &self.buffer.read(cx).snapshot(cx),
24784 cx,
24785 )),
24786 cx,
24787 );
24788
24789 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24790 .global_lsp_settings
24791 .semantic_token_rules
24792 .clone();
24793 let semantic_token_rules_changed = self
24794 .semantic_token_state
24795 .update_rules(new_semantic_token_rules);
24796 if language_settings_changed || semantic_token_rules_changed {
24797 self.invalidate_semantic_tokens(None);
24798 self.refresh_semantic_tokens(None, None, cx);
24799 }
24800 }
24801
24802 cx.notify();
24803 }
24804
24805 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24806 if !self.mode.is_full() {
24807 return;
24808 }
24809
24810 let new_accents = self.fetch_accent_data(cx);
24811 if new_accents != self.accent_data {
24812 self.accent_data = new_accents;
24813 self.colorize_brackets(true, cx);
24814 }
24815
24816 self.invalidate_semantic_tokens(None);
24817 self.refresh_semantic_tokens(None, None, cx);
24818 }
24819
24820 pub fn set_searchable(&mut self, searchable: bool) {
24821 self.searchable = searchable;
24822 }
24823
24824 pub fn searchable(&self) -> bool {
24825 self.searchable
24826 }
24827
24828 pub fn open_excerpts_in_split(
24829 &mut self,
24830 _: &OpenExcerptsSplit,
24831 window: &mut Window,
24832 cx: &mut Context<Self>,
24833 ) {
24834 self.open_excerpts_common(None, true, window, cx)
24835 }
24836
24837 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24838 self.open_excerpts_common(None, false, window, cx)
24839 }
24840
24841 pub(crate) fn open_excerpts_common(
24842 &mut self,
24843 jump_data: Option<JumpData>,
24844 split: bool,
24845 window: &mut Window,
24846 cx: &mut Context<Self>,
24847 ) {
24848 if self.buffer.read(cx).is_singleton() {
24849 cx.propagate();
24850 return;
24851 }
24852
24853 let mut new_selections_by_buffer = HashMap::default();
24854 match &jump_data {
24855 Some(JumpData::MultiBufferPoint {
24856 anchor,
24857 position,
24858 line_offset_from_top,
24859 }) => {
24860 if let Some(buffer) = self.buffer.read(cx).buffer(anchor.buffer_id) {
24861 let buffer_snapshot = buffer.read(cx).snapshot();
24862 let jump_to_point = if buffer_snapshot.can_resolve(&anchor) {
24863 language::ToPoint::to_point(anchor, &buffer_snapshot)
24864 } else {
24865 buffer_snapshot.clip_point(*position, Bias::Left)
24866 };
24867 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24868 new_selections_by_buffer.insert(
24869 buffer,
24870 (
24871 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24872 Some(*line_offset_from_top),
24873 ),
24874 );
24875 }
24876 }
24877 Some(JumpData::MultiBufferRow {
24878 row,
24879 line_offset_from_top,
24880 }) => {
24881 let point = MultiBufferPoint::new(row.0, 0);
24882 if let Some((buffer, buffer_point)) =
24883 self.buffer.read(cx).point_to_buffer_point(point, cx)
24884 {
24885 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24886 new_selections_by_buffer
24887 .entry(buffer)
24888 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24889 .0
24890 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24891 }
24892 }
24893 None => {
24894 let selections = self
24895 .selections
24896 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24897 let multi_buffer = self.buffer.read(cx);
24898 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
24899 for selection in selections {
24900 for (snapshot, range, anchor) in multi_buffer_snapshot
24901 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24902 {
24903 if let Some((text_anchor, _)) = anchor.and_then(|anchor| {
24904 multi_buffer_snapshot.anchor_to_buffer_anchor(anchor)
24905 }) {
24906 let Some(buffer_handle) = multi_buffer.buffer(text_anchor.buffer_id)
24907 else {
24908 continue;
24909 };
24910 let offset = text::ToOffset::to_offset(
24911 &text_anchor,
24912 &buffer_handle.read(cx).snapshot(),
24913 );
24914 let range = BufferOffset(offset)..BufferOffset(offset);
24915 new_selections_by_buffer
24916 .entry(buffer_handle)
24917 .or_insert((Vec::new(), None))
24918 .0
24919 .push(range)
24920 } else {
24921 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24922 else {
24923 continue;
24924 };
24925 new_selections_by_buffer
24926 .entry(buffer_handle)
24927 .or_insert((Vec::new(), None))
24928 .0
24929 .push(range)
24930 }
24931 }
24932 }
24933 }
24934 }
24935
24936 if self.delegate_open_excerpts {
24937 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24938 .into_iter()
24939 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24940 .collect();
24941 if !selections_by_buffer.is_empty() {
24942 cx.emit(EditorEvent::OpenExcerptsRequested {
24943 selections_by_buffer,
24944 split,
24945 });
24946 }
24947 return;
24948 }
24949
24950 let Some(workspace) = self.workspace() else {
24951 cx.propagate();
24952 return;
24953 };
24954
24955 new_selections_by_buffer
24956 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24957
24958 if new_selections_by_buffer.is_empty() {
24959 return;
24960 }
24961
24962 Self::open_buffers_in_workspace(
24963 workspace.downgrade(),
24964 new_selections_by_buffer,
24965 split,
24966 window,
24967 cx,
24968 );
24969 }
24970
24971 pub(crate) fn open_buffers_in_workspace(
24972 workspace: WeakEntity<Workspace>,
24973 new_selections_by_buffer: HashMap<
24974 Entity<language::Buffer>,
24975 (Vec<Range<BufferOffset>>, Option<u32>),
24976 >,
24977 split: bool,
24978 window: &mut Window,
24979 cx: &mut App,
24980 ) {
24981 // We defer the pane interaction because we ourselves are a workspace item
24982 // and activating a new item causes the pane to call a method on us reentrantly,
24983 // which panics if we're on the stack.
24984 window.defer(cx, move |window, cx| {
24985 workspace
24986 .update(cx, |workspace, cx| {
24987 let pane = if split {
24988 workspace.adjacent_pane(window, cx)
24989 } else {
24990 workspace.active_pane().clone()
24991 };
24992
24993 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24994 let buffer_read = buffer.read(cx);
24995 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24996 (true, project::File::from_dyn(Some(file)).is_some())
24997 } else {
24998 (false, false)
24999 };
25000
25001 // If project file is none workspace.open_project_item will fail to open the excerpt
25002 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
25003 // so we check if there's a tab match in that case first
25004 let editor = (!has_file || !is_project_file)
25005 .then(|| {
25006 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
25007 // so `workspace.open_project_item` will never find them, always opening a new editor.
25008 // Instead, we try to activate the existing editor in the pane first.
25009 let (editor, pane_item_index, pane_item_id) =
25010 pane.read(cx).items().enumerate().find_map(|(i, item)| {
25011 let editor = item.downcast::<Editor>()?;
25012 let singleton_buffer =
25013 editor.read(cx).buffer().read(cx).as_singleton()?;
25014 if singleton_buffer == buffer {
25015 Some((editor, i, item.item_id()))
25016 } else {
25017 None
25018 }
25019 })?;
25020 pane.update(cx, |pane, cx| {
25021 pane.activate_item(pane_item_index, true, true, window, cx);
25022 if !PreviewTabsSettings::get_global(cx)
25023 .enable_preview_from_multibuffer
25024 {
25025 pane.unpreview_item_if_preview(pane_item_id);
25026 }
25027 });
25028 Some(editor)
25029 })
25030 .flatten()
25031 .unwrap_or_else(|| {
25032 let keep_old_preview = PreviewTabsSettings::get_global(cx)
25033 .enable_keep_preview_on_code_navigation;
25034 let allow_new_preview = PreviewTabsSettings::get_global(cx)
25035 .enable_preview_from_multibuffer;
25036 workspace.open_project_item::<Self>(
25037 pane.clone(),
25038 buffer,
25039 true,
25040 true,
25041 keep_old_preview,
25042 allow_new_preview,
25043 window,
25044 cx,
25045 )
25046 });
25047
25048 editor.update(cx, |editor, cx| {
25049 if has_file && !is_project_file {
25050 editor.set_read_only(true);
25051 }
25052 let autoscroll = match scroll_offset {
25053 Some(scroll_offset) => {
25054 Autoscroll::top_relative(scroll_offset as usize)
25055 }
25056 None => Autoscroll::newest(),
25057 };
25058 let nav_history = editor.nav_history.take();
25059 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25060 let Some(buffer_snapshot) = multibuffer_snapshot.as_singleton() else {
25061 return;
25062 };
25063 editor.change_selections(
25064 SelectionEffects::scroll(autoscroll),
25065 window,
25066 cx,
25067 |s| {
25068 s.select_ranges(ranges.into_iter().map(|range| {
25069 let range = buffer_snapshot.anchor_before(range.start)
25070 ..buffer_snapshot.anchor_after(range.end);
25071 multibuffer_snapshot
25072 .buffer_anchor_range_to_anchor_range(range)
25073 .unwrap()
25074 }));
25075 },
25076 );
25077 editor.nav_history = nav_history;
25078 });
25079 }
25080 })
25081 .ok();
25082 });
25083 }
25084
25085 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
25086 let snapshot = self.buffer.read(cx).read(cx);
25087 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
25088 Some(
25089 ranges
25090 .iter()
25091 .map(move |range| {
25092 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
25093 })
25094 .collect(),
25095 )
25096 }
25097
25098 fn selection_replacement_ranges(
25099 &self,
25100 range: Range<MultiBufferOffsetUtf16>,
25101 cx: &mut App,
25102 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
25103 let selections = self
25104 .selections
25105 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25106 let newest_selection = selections
25107 .iter()
25108 .max_by_key(|selection| selection.id)
25109 .unwrap();
25110 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
25111 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
25112 let snapshot = self.buffer.read(cx).read(cx);
25113 selections
25114 .into_iter()
25115 .map(|mut selection| {
25116 selection.start.0.0 =
25117 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
25118 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
25119 snapshot.clip_offset_utf16(selection.start, Bias::Left)
25120 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
25121 })
25122 .collect()
25123 }
25124
25125 fn report_editor_event(
25126 &self,
25127 reported_event: ReportEditorEvent,
25128 file_extension: Option<String>,
25129 cx: &App,
25130 ) {
25131 if cfg!(any(test, feature = "test-support")) {
25132 return;
25133 }
25134
25135 let Some(project) = &self.project else { return };
25136
25137 // If None, we are in a file without an extension
25138 let file = self
25139 .buffer
25140 .read(cx)
25141 .as_singleton()
25142 .and_then(|b| b.read(cx).file());
25143 let file_extension = file_extension.or(file
25144 .as_ref()
25145 .and_then(|file| Path::new(file.file_name(cx)).extension())
25146 .and_then(|e| e.to_str())
25147 .map(|a| a.to_string()));
25148
25149 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
25150 .map(|vim_mode| vim_mode.0)
25151 .unwrap_or(false);
25152
25153 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
25154 let copilot_enabled = edit_predictions_provider
25155 == language::language_settings::EditPredictionProvider::Copilot;
25156 let copilot_enabled_for_language = self
25157 .buffer
25158 .read(cx)
25159 .language_settings(cx)
25160 .show_edit_predictions;
25161
25162 let project = project.read(cx);
25163 let event_type = reported_event.event_type();
25164
25165 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
25166 telemetry::event!(
25167 event_type,
25168 type = if auto_saved {"autosave"} else {"manual"},
25169 file_extension,
25170 vim_mode,
25171 copilot_enabled,
25172 copilot_enabled_for_language,
25173 edit_predictions_provider,
25174 is_via_ssh = project.is_via_remote_server(),
25175 );
25176 } else {
25177 telemetry::event!(
25178 event_type,
25179 file_extension,
25180 vim_mode,
25181 copilot_enabled,
25182 copilot_enabled_for_language,
25183 edit_predictions_provider,
25184 is_via_ssh = project.is_via_remote_server(),
25185 );
25186 };
25187 }
25188
25189 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
25190 /// with each line being an array of {text, highlight} objects.
25191 fn copy_highlight_json(
25192 &mut self,
25193 _: &CopyHighlightJson,
25194 _: &mut Window,
25195 cx: &mut Context<Self>,
25196 ) {
25197 #[derive(Serialize)]
25198 struct Chunk<'a> {
25199 text: String,
25200 highlight: Option<&'a str>,
25201 }
25202
25203 let snapshot = self.buffer.read(cx).snapshot(cx);
25204 let mut selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
25205 let max_point = snapshot.max_point();
25206
25207 let range = if self.selections.line_mode() {
25208 selection.start = Point::new(selection.start.row, 0);
25209 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
25210 selection.goal = SelectionGoal::None;
25211 selection.range()
25212 } else if selection.is_empty() {
25213 Point::new(0, 0)..max_point
25214 } else {
25215 selection.range()
25216 };
25217
25218 let chunks = snapshot.chunks(
25219 range,
25220 LanguageAwareStyling {
25221 tree_sitter: true,
25222 diagnostics: true,
25223 },
25224 );
25225 let mut lines = Vec::new();
25226 let mut line: VecDeque<Chunk> = VecDeque::new();
25227
25228 let Some(style) = self.style.as_ref() else {
25229 return;
25230 };
25231
25232 for chunk in chunks {
25233 let highlight = chunk
25234 .syntax_highlight_id
25235 .and_then(|id| style.syntax.get_capture_name(id));
25236
25237 let mut chunk_lines = chunk.text.split('\n').peekable();
25238 while let Some(text) = chunk_lines.next() {
25239 let mut merged_with_last_token = false;
25240 if let Some(last_token) = line.back_mut()
25241 && last_token.highlight == highlight
25242 {
25243 last_token.text.push_str(text);
25244 merged_with_last_token = true;
25245 }
25246
25247 if !merged_with_last_token {
25248 line.push_back(Chunk {
25249 text: text.into(),
25250 highlight,
25251 });
25252 }
25253
25254 if chunk_lines.peek().is_some() {
25255 if line.len() > 1 && line.front().unwrap().text.is_empty() {
25256 line.pop_front();
25257 }
25258 if line.len() > 1 && line.back().unwrap().text.is_empty() {
25259 line.pop_back();
25260 }
25261
25262 lines.push(mem::take(&mut line));
25263 }
25264 }
25265 }
25266
25267 if line.iter().any(|chunk| !chunk.text.is_empty()) {
25268 lines.push(line);
25269 }
25270
25271 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
25272 return;
25273 };
25274 cx.write_to_clipboard(ClipboardItem::new_string(lines));
25275 }
25276
25277 pub fn open_context_menu(
25278 &mut self,
25279 _: &OpenContextMenu,
25280 window: &mut Window,
25281 cx: &mut Context<Self>,
25282 ) {
25283 self.request_autoscroll(Autoscroll::newest(), cx);
25284 let position = self
25285 .selections
25286 .newest_display(&self.display_snapshot(cx))
25287 .start;
25288 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
25289 }
25290
25291 pub fn replay_insert_event(
25292 &mut self,
25293 text: &str,
25294 relative_utf16_range: Option<Range<isize>>,
25295 window: &mut Window,
25296 cx: &mut Context<Self>,
25297 ) {
25298 if !self.input_enabled {
25299 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25300 return;
25301 }
25302 if let Some(relative_utf16_range) = relative_utf16_range {
25303 let selections = self
25304 .selections
25305 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25306 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25307 let new_ranges = selections.into_iter().map(|range| {
25308 let start = MultiBufferOffsetUtf16(OffsetUtf16(
25309 range
25310 .head()
25311 .0
25312 .0
25313 .saturating_add_signed(relative_utf16_range.start),
25314 ));
25315 let end = MultiBufferOffsetUtf16(OffsetUtf16(
25316 range
25317 .head()
25318 .0
25319 .0
25320 .saturating_add_signed(relative_utf16_range.end),
25321 ));
25322 start..end
25323 });
25324 s.select_ranges(new_ranges);
25325 });
25326 }
25327
25328 self.handle_input(text, window, cx);
25329 }
25330
25331 pub fn is_focused(&self, window: &Window) -> bool {
25332 self.focus_handle.is_focused(window)
25333 }
25334
25335 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25336 cx.emit(EditorEvent::Focused);
25337
25338 if let Some(descendant) = self
25339 .last_focused_descendant
25340 .take()
25341 .and_then(|descendant| descendant.upgrade())
25342 {
25343 window.focus(&descendant, cx);
25344 } else {
25345 if let Some(blame) = self.blame.as_ref() {
25346 blame.update(cx, GitBlame::focus)
25347 }
25348
25349 self.blink_manager.update(cx, BlinkManager::enable);
25350 self.show_cursor_names(window, cx);
25351 self.buffer.update(cx, |buffer, cx| {
25352 buffer.finalize_last_transaction(cx);
25353 if self.leader_id.is_none() {
25354 buffer.set_active_selections(
25355 &self.selections.disjoint_anchors_arc(),
25356 self.selections.line_mode(),
25357 self.cursor_shape,
25358 cx,
25359 );
25360 }
25361 });
25362
25363 if let Some(position_map) = self.last_position_map.clone()
25364 && !self.mouse_cursor_hidden
25365 {
25366 EditorElement::mouse_moved(
25367 self,
25368 &MouseMoveEvent {
25369 position: window.mouse_position(),
25370 pressed_button: None,
25371 modifiers: window.modifiers(),
25372 },
25373 &position_map,
25374 None,
25375 window,
25376 cx,
25377 );
25378 }
25379 }
25380 }
25381
25382 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25383 cx.emit(EditorEvent::FocusedIn)
25384 }
25385
25386 fn handle_focus_out(
25387 &mut self,
25388 event: FocusOutEvent,
25389 _window: &mut Window,
25390 cx: &mut Context<Self>,
25391 ) {
25392 if event.blurred != self.focus_handle {
25393 self.last_focused_descendant = Some(event.blurred);
25394 }
25395 self.selection_drag_state = SelectionDragState::None;
25396 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25397 }
25398
25399 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25400 self.blink_manager.update(cx, BlinkManager::disable);
25401 self.buffer
25402 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25403
25404 if let Some(blame) = self.blame.as_ref() {
25405 blame.update(cx, GitBlame::blur)
25406 }
25407 if !self.hover_state.focused(window, cx) {
25408 hide_hover(self, cx);
25409 }
25410 if !self
25411 .context_menu
25412 .borrow()
25413 .as_ref()
25414 .is_some_and(|context_menu| context_menu.focused(window, cx))
25415 {
25416 self.hide_context_menu(window, cx);
25417 }
25418 self.take_active_edit_prediction(true, cx);
25419 cx.emit(EditorEvent::Blurred);
25420 cx.notify();
25421 }
25422
25423 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25424 let mut pending: String = window
25425 .pending_input_keystrokes()
25426 .into_iter()
25427 .flatten()
25428 .filter_map(|keystroke| keystroke.key_char.clone())
25429 .collect();
25430
25431 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25432 pending = "".to_string();
25433 }
25434
25435 let existing_pending = self
25436 .text_highlights(HighlightKey::PendingInput, cx)
25437 .map(|(_, ranges)| ranges.to_vec());
25438 if existing_pending.is_none() && pending.is_empty() {
25439 return;
25440 }
25441 let transaction =
25442 self.transact(window, cx, |this, window, cx| {
25443 let selections = this
25444 .selections
25445 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25446 let edits = selections
25447 .iter()
25448 .map(|selection| (selection.end..selection.end, pending.clone()));
25449 this.edit(edits, cx);
25450 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25451 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25452 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25453 }));
25454 });
25455 if let Some(existing_ranges) = existing_pending {
25456 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25457 this.edit(edits, cx);
25458 }
25459 });
25460
25461 let snapshot = self.snapshot(window, cx);
25462 let ranges = self
25463 .selections
25464 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25465 .into_iter()
25466 .map(|selection| {
25467 snapshot.buffer_snapshot().anchor_after(selection.end)
25468 ..snapshot
25469 .buffer_snapshot()
25470 .anchor_before(selection.end + pending.len())
25471 })
25472 .collect();
25473
25474 if pending.is_empty() {
25475 self.clear_highlights(HighlightKey::PendingInput, cx);
25476 } else {
25477 self.highlight_text(
25478 HighlightKey::PendingInput,
25479 ranges,
25480 HighlightStyle {
25481 underline: Some(UnderlineStyle {
25482 thickness: px(1.),
25483 color: None,
25484 wavy: false,
25485 }),
25486 ..Default::default()
25487 },
25488 cx,
25489 );
25490 }
25491
25492 self.ime_transaction = self.ime_transaction.or(transaction);
25493 if let Some(transaction) = self.ime_transaction {
25494 self.buffer.update(cx, |buffer, cx| {
25495 buffer.group_until_transaction(transaction, cx);
25496 });
25497 }
25498
25499 if self
25500 .text_highlights(HighlightKey::PendingInput, cx)
25501 .is_none()
25502 {
25503 self.ime_transaction.take();
25504 }
25505 }
25506
25507 pub fn register_action_renderer(
25508 &mut self,
25509 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25510 ) -> Subscription {
25511 let id = self.next_editor_action_id.post_inc();
25512 self.editor_actions
25513 .borrow_mut()
25514 .insert(id, Box::new(listener));
25515
25516 let editor_actions = self.editor_actions.clone();
25517 Subscription::new(move || {
25518 editor_actions.borrow_mut().remove(&id);
25519 })
25520 }
25521
25522 pub fn register_action<A: Action>(
25523 &mut self,
25524 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25525 ) -> Subscription {
25526 let id = self.next_editor_action_id.post_inc();
25527 let listener = Arc::new(listener);
25528 self.editor_actions.borrow_mut().insert(
25529 id,
25530 Box::new(move |_, window, _| {
25531 let listener = listener.clone();
25532 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25533 let action = action.downcast_ref().unwrap();
25534 if phase == DispatchPhase::Bubble {
25535 listener(action, window, cx)
25536 }
25537 })
25538 }),
25539 );
25540
25541 let editor_actions = self.editor_actions.clone();
25542 Subscription::new(move || {
25543 editor_actions.borrow_mut().remove(&id);
25544 })
25545 }
25546
25547 pub fn file_header_size(&self) -> u32 {
25548 FILE_HEADER_HEIGHT
25549 }
25550
25551 pub fn restore(
25552 &mut self,
25553 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25554 window: &mut Window,
25555 cx: &mut Context<Self>,
25556 ) {
25557 self.buffer().update(cx, |multi_buffer, cx| {
25558 for (buffer_id, changes) in revert_changes {
25559 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25560 buffer.update(cx, |buffer, cx| {
25561 buffer.edit(
25562 changes
25563 .into_iter()
25564 .map(|(range, text)| (range, text.to_string())),
25565 None,
25566 cx,
25567 );
25568 });
25569 }
25570 }
25571 });
25572 let selections = self
25573 .selections
25574 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
25575 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25576 s.select(selections);
25577 });
25578 }
25579
25580 pub fn to_pixel_point(
25581 &mut self,
25582 source: Anchor,
25583 editor_snapshot: &EditorSnapshot,
25584 window: &mut Window,
25585 cx: &mut App,
25586 ) -> Option<gpui::Point<Pixels>> {
25587 let source_point = source.to_display_point(editor_snapshot);
25588 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25589 }
25590
25591 pub fn display_to_pixel_point(
25592 &mut self,
25593 source: DisplayPoint,
25594 editor_snapshot: &EditorSnapshot,
25595 window: &mut Window,
25596 cx: &mut App,
25597 ) -> Option<gpui::Point<Pixels>> {
25598 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25599 let text_layout_details = self.text_layout_details(window, cx);
25600 let scroll_top = text_layout_details
25601 .scroll_anchor
25602 .scroll_position(editor_snapshot)
25603 .y;
25604
25605 if source.row().as_f64() < scroll_top.floor() {
25606 return None;
25607 }
25608 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25609 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25610 Some(gpui::Point::new(source_x, source_y))
25611 }
25612
25613 pub fn has_visible_completions_menu(&self) -> bool {
25614 !self.edit_prediction_preview_is_active()
25615 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25616 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25617 })
25618 }
25619
25620 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25621 if self.mode.is_minimap() {
25622 return;
25623 }
25624 self.addons
25625 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25626 }
25627
25628 pub fn unregister_addon<T: Addon>(&mut self) {
25629 self.addons.remove(&std::any::TypeId::of::<T>());
25630 }
25631
25632 pub fn addon<T: Addon>(&self) -> Option<&T> {
25633 let type_id = std::any::TypeId::of::<T>();
25634 self.addons
25635 .get(&type_id)
25636 .and_then(|item| item.to_any().downcast_ref::<T>())
25637 }
25638
25639 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25640 let type_id = std::any::TypeId::of::<T>();
25641 self.addons
25642 .get_mut(&type_id)
25643 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25644 }
25645
25646 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25647 let text_layout_details = self.text_layout_details(window, cx);
25648 let style = &text_layout_details.editor_style;
25649 let font_id = window.text_system().resolve_font(&style.text.font());
25650 let font_size = style.text.font_size.to_pixels(window.rem_size());
25651 let line_height = style.text.line_height_in_pixels(window.rem_size());
25652 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25653 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25654
25655 CharacterDimensions {
25656 em_width,
25657 em_advance,
25658 line_height,
25659 }
25660 }
25661
25662 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25663 self.load_diff_task.clone()
25664 }
25665
25666 fn read_metadata_from_db(
25667 &mut self,
25668 item_id: u64,
25669 workspace_id: WorkspaceId,
25670 window: &mut Window,
25671 cx: &mut Context<Editor>,
25672 ) {
25673 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25674 && !self.mode.is_minimap()
25675 && WorkspaceSettings::get(None, cx).restore_on_startup
25676 != RestoreOnStartupBehavior::EmptyTab
25677 {
25678 let buffer_snapshot = OnceCell::new();
25679
25680 // Get file path for path-based fold lookup
25681 let file_path: Option<Arc<Path>> =
25682 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25683 project::File::from_dyn(buffer.read(cx).file())
25684 .map(|file| Arc::from(file.abs_path(cx)))
25685 });
25686
25687 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25688 let db = EditorDb::global(cx);
25689 let (folds, needs_migration) = if let Some(ref path) = file_path {
25690 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
25691 && !folds.is_empty()
25692 {
25693 (Some(folds), false)
25694 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
25695 && !folds.is_empty()
25696 {
25697 // Found old editor_folds data, will migrate to file_folds
25698 (Some(folds), true)
25699 } else {
25700 (None, false)
25701 }
25702 } else {
25703 // No file path, try editor_folds as fallback
25704 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
25705 (folds.filter(|f| !f.is_empty()), false)
25706 };
25707
25708 if let Some(folds) = folds {
25709 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25710 let snapshot_len = snapshot.len().0;
25711
25712 // Helper: search for fingerprint in buffer, return offset if found
25713 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25714 // Ensure we start at a character boundary (defensive)
25715 let search_start = snapshot
25716 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25717 .0;
25718 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25719
25720 let mut byte_offset = search_start;
25721 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25722 if byte_offset > search_end {
25723 break;
25724 }
25725 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25726 return Some(byte_offset);
25727 }
25728 byte_offset += ch.len_utf8();
25729 }
25730 None
25731 };
25732
25733 // Track search position to handle duplicate fingerprints correctly.
25734 // Folds are stored in document order, so we advance after each match.
25735 let mut search_start = 0usize;
25736
25737 // Collect db_folds for migration (only folds with valid fingerprints)
25738 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25739
25740 let valid_folds: Vec<_> = folds
25741 .into_iter()
25742 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25743 // Skip folds without fingerprints (old data before migration)
25744 let sfp = start_fp?;
25745 let efp = end_fp?;
25746 let efp_len = efp.len();
25747
25748 // Fast path: check if fingerprints match at stored offsets
25749 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25750 let start_matches = stored_start < snapshot_len
25751 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25752 let efp_check_pos = stored_end.saturating_sub(efp_len);
25753 let end_matches = efp_check_pos >= stored_start
25754 && stored_end <= snapshot_len
25755 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25756
25757 let (new_start, new_end) = if start_matches && end_matches {
25758 // Offsets unchanged, use stored values
25759 (stored_start, stored_end)
25760 } else if sfp == efp {
25761 // Short fold: identical fingerprints can only match once per search
25762 // Use stored fold length to compute new_end
25763 let new_start = find_fingerprint(&sfp, search_start)?;
25764 let fold_len = stored_end - stored_start;
25765 let new_end = new_start + fold_len;
25766 (new_start, new_end)
25767 } else {
25768 // Slow path: search for fingerprints in buffer
25769 let new_start = find_fingerprint(&sfp, search_start)?;
25770 // Search for end_fp after start, then add efp_len to get actual fold end
25771 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25772 let new_end = efp_pos + efp_len;
25773 (new_start, new_end)
25774 };
25775
25776 // Advance search position for next fold
25777 search_start = new_end;
25778
25779 // Validate fold makes sense (end must be after start)
25780 if new_end <= new_start {
25781 return None;
25782 }
25783
25784 // Collect for migration if needed
25785 if needs_migration {
25786 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25787 }
25788
25789 Some(
25790 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25791 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25792 )
25793 })
25794 .collect();
25795
25796 if !valid_folds.is_empty() {
25797 self.fold_ranges(valid_folds, false, window, cx);
25798
25799 // Migrate from editor_folds to file_folds if we loaded from old table
25800 if needs_migration {
25801 if let Some(ref path) = file_path {
25802 let path = path.clone();
25803 let db = EditorDb::global(cx);
25804 cx.spawn(async move |_, _| {
25805 db.save_file_folds(workspace_id, path, db_folds_for_migration)
25806 .await
25807 .log_err();
25808 })
25809 .detach();
25810 }
25811 }
25812 }
25813 }
25814
25815 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
25816 && !selections.is_empty()
25817 {
25818 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25819 // skip adding the initial selection to selection history
25820 self.selection_history.mode = SelectionHistoryMode::Skipping;
25821 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25822 s.select_ranges(selections.into_iter().map(|(start, end)| {
25823 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25824 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25825 }));
25826 });
25827 self.selection_history.mode = SelectionHistoryMode::Normal;
25828 };
25829 }
25830
25831 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25832 }
25833
25834 /// Load folds from the file_folds database table by file path.
25835 /// Used when manually opening a file that was previously closed.
25836 fn load_folds_from_db(
25837 &mut self,
25838 workspace_id: WorkspaceId,
25839 file_path: PathBuf,
25840 window: &mut Window,
25841 cx: &mut Context<Editor>,
25842 ) {
25843 if self.mode.is_minimap()
25844 || WorkspaceSettings::get(None, cx).restore_on_startup
25845 == RestoreOnStartupBehavior::EmptyTab
25846 {
25847 return;
25848 }
25849
25850 let Some(folds) = EditorDb::global(cx)
25851 .get_file_folds(workspace_id, &file_path)
25852 .log_err()
25853 else {
25854 return;
25855 };
25856 if folds.is_empty() {
25857 return;
25858 }
25859
25860 let snapshot = self.buffer.read(cx).snapshot(cx);
25861 let snapshot_len = snapshot.len().0;
25862
25863 // Helper: search for fingerprint in buffer, return offset if found
25864 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25865 let search_start = snapshot
25866 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25867 .0;
25868 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25869
25870 let mut byte_offset = search_start;
25871 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25872 if byte_offset > search_end {
25873 break;
25874 }
25875 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25876 return Some(byte_offset);
25877 }
25878 byte_offset += ch.len_utf8();
25879 }
25880 None
25881 };
25882
25883 let mut search_start = 0usize;
25884
25885 let valid_folds: Vec<_> = folds
25886 .into_iter()
25887 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25888 let sfp = start_fp?;
25889 let efp = end_fp?;
25890 let efp_len = efp.len();
25891
25892 let start_matches = stored_start < snapshot_len
25893 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25894 let efp_check_pos = stored_end.saturating_sub(efp_len);
25895 let end_matches = efp_check_pos >= stored_start
25896 && stored_end <= snapshot_len
25897 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25898
25899 let (new_start, new_end) = if start_matches && end_matches {
25900 (stored_start, stored_end)
25901 } else if sfp == efp {
25902 let new_start = find_fingerprint(&sfp, search_start)?;
25903 let fold_len = stored_end - stored_start;
25904 let new_end = new_start + fold_len;
25905 (new_start, new_end)
25906 } else {
25907 let new_start = find_fingerprint(&sfp, search_start)?;
25908 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25909 let new_end = efp_pos + efp_len;
25910 (new_start, new_end)
25911 };
25912
25913 search_start = new_end;
25914
25915 if new_end <= new_start {
25916 return None;
25917 }
25918
25919 Some(
25920 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25921 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25922 )
25923 })
25924 .collect();
25925
25926 if !valid_folds.is_empty() {
25927 self.fold_ranges(valid_folds, false, window, cx);
25928 }
25929 }
25930
25931 fn lsp_data_enabled(&self) -> bool {
25932 self.enable_lsp_data && self.mode().is_full()
25933 }
25934
25935 fn update_lsp_data(
25936 &mut self,
25937 for_buffer: Option<BufferId>,
25938 window: &mut Window,
25939 cx: &mut Context<'_, Self>,
25940 ) {
25941 if !self.lsp_data_enabled() {
25942 return;
25943 }
25944
25945 if let Some(buffer_id) = for_buffer {
25946 self.pull_diagnostics(buffer_id, window, cx);
25947 }
25948 self.refresh_semantic_tokens(for_buffer, None, cx);
25949 self.refresh_document_colors(for_buffer, window, cx);
25950 self.refresh_folding_ranges(for_buffer, window, cx);
25951 self.refresh_document_symbols(for_buffer, cx);
25952 }
25953
25954 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25955 if !self.lsp_data_enabled() {
25956 return;
25957 }
25958 let visible_buffers: Vec<_> = self
25959 .visible_buffers(cx)
25960 .into_iter()
25961 .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
25962 .collect();
25963 for visible_buffer in visible_buffers {
25964 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25965 }
25966 }
25967
25968 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25969 if !self.lsp_data_enabled() {
25970 return;
25971 }
25972
25973 if !self.registered_buffers.contains_key(&buffer_id)
25974 && let Some(project) = self.project.as_ref()
25975 {
25976 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25977 project.update(cx, |project, cx| {
25978 self.registered_buffers.insert(
25979 buffer_id,
25980 project.register_buffer_with_language_servers(&buffer, cx),
25981 );
25982 });
25983 } else {
25984 self.registered_buffers.remove(&buffer_id);
25985 }
25986 }
25987 }
25988
25989 fn create_style(&self, cx: &App) -> EditorStyle {
25990 let settings = ThemeSettings::get_global(cx);
25991
25992 let mut text_style = match self.mode {
25993 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25994 color: cx.theme().colors().editor_foreground,
25995 font_family: settings.ui_font.family.clone(),
25996 font_features: settings.ui_font.features.clone(),
25997 font_fallbacks: settings.ui_font.fallbacks.clone(),
25998 font_size: rems(0.875).into(),
25999 font_weight: settings.ui_font.weight,
26000 line_height: relative(settings.buffer_line_height.value()),
26001 ..Default::default()
26002 },
26003 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
26004 color: cx.theme().colors().editor_foreground,
26005 font_family: settings.buffer_font.family.clone(),
26006 font_features: settings.buffer_font.features.clone(),
26007 font_fallbacks: settings.buffer_font.fallbacks.clone(),
26008 font_size: settings.buffer_font_size(cx).into(),
26009 font_weight: settings.buffer_font.weight,
26010 line_height: relative(settings.buffer_line_height.value()),
26011 ..Default::default()
26012 },
26013 };
26014 if let Some(text_style_refinement) = &self.text_style_refinement {
26015 text_style.refine(text_style_refinement)
26016 }
26017
26018 let background = match self.mode {
26019 EditorMode::SingleLine => cx.theme().system().transparent,
26020 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
26021 EditorMode::Full { .. } => cx.theme().colors().editor_background,
26022 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
26023 };
26024
26025 EditorStyle {
26026 background,
26027 border: cx.theme().colors().border,
26028 local_player: cx.theme().players().local(),
26029 text: text_style,
26030 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
26031 syntax: cx.theme().syntax().clone(),
26032 status: cx.theme().status().clone(),
26033 inlay_hints_style: make_inlay_hints_style(cx),
26034 edit_prediction_styles: make_suggestion_styles(cx),
26035 unnecessary_code_fade: settings.unnecessary_code_fade,
26036 show_underlines: self.diagnostics_enabled(),
26037 }
26038 }
26039
26040 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
26041 let multibuffer = self.buffer().read(cx);
26042 let is_singleton = multibuffer.is_singleton();
26043 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
26044 let buffer = multibuffer.buffer(*buffer_id)?;
26045
26046 let buffer = buffer.read(cx);
26047 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
26048 let mut breadcrumbs = if is_singleton {
26049 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
26050 buffer
26051 .snapshot()
26052 .resolve_file_path(
26053 self.project
26054 .as_ref()
26055 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
26056 .unwrap_or_default(),
26057 cx,
26058 )
26059 .unwrap_or_else(|| {
26060 if multibuffer.is_singleton() {
26061 multibuffer.title(cx).to_string()
26062 } else {
26063 "untitled".to_string()
26064 }
26065 })
26066 });
26067 vec![HighlightedText {
26068 text: text.into(),
26069 highlights: vec![],
26070 }]
26071 } else {
26072 vec![]
26073 };
26074
26075 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
26076 text: symbol.text.clone().into(),
26077 highlights: symbol.highlight_ranges.clone(),
26078 }));
26079 Some(breadcrumbs)
26080 }
26081
26082 fn disable_lsp_data(&mut self) {
26083 self.enable_lsp_data = false;
26084 }
26085
26086 fn disable_runnables(&mut self) {
26087 self.enable_runnables = false;
26088 }
26089
26090 pub fn disable_mouse_wheel_zoom(&mut self) {
26091 self.enable_mouse_wheel_zoom = false;
26092 }
26093
26094 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
26095 self.register_visible_buffers(cx);
26096 self.colorize_brackets(false, cx);
26097 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
26098 if !self.buffer().read(cx).is_singleton() {
26099 self.update_lsp_data(None, window, cx);
26100 self.refresh_runnables(None, window, cx);
26101 }
26102 }
26103}
26104
26105fn edit_for_markdown_paste<'a>(
26106 buffer: &MultiBufferSnapshot,
26107 range: Range<MultiBufferOffset>,
26108 to_insert: &'a str,
26109 url: Option<url::Url>,
26110) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
26111 if url.is_none() {
26112 return (range, Cow::Borrowed(to_insert));
26113 };
26114
26115 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
26116
26117 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
26118 Cow::Borrowed(to_insert)
26119 } else {
26120 Cow::Owned(format!("[{old_text}]({to_insert})"))
26121 };
26122 (range, new_text)
26123}
26124
26125fn process_completion_for_edit(
26126 completion: &Completion,
26127 intent: CompletionIntent,
26128 buffer: &Entity<Buffer>,
26129 cursor_position: &text::Anchor,
26130 cx: &mut Context<Editor>,
26131) -> CompletionEdit {
26132 let buffer = buffer.read(cx);
26133 let buffer_snapshot = buffer.snapshot();
26134 let (snippet, new_text) = if completion.is_snippet() {
26135 let mut snippet_source = completion.new_text.clone();
26136 // Workaround for typescript language server issues so that methods don't expand within
26137 // strings and functions with type expressions. The previous point is used because the query
26138 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
26139 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
26140 let previous_point = if previous_point.column > 0 {
26141 cursor_position.to_previous_offset(&buffer_snapshot)
26142 } else {
26143 cursor_position.to_offset(&buffer_snapshot)
26144 };
26145 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
26146 && scope.prefers_label_for_snippet_in_completion()
26147 && let Some(label) = completion.label()
26148 && matches!(
26149 completion.kind(),
26150 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
26151 )
26152 {
26153 snippet_source = label;
26154 }
26155 match Snippet::parse(&snippet_source).log_err() {
26156 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
26157 None => (None, completion.new_text.clone()),
26158 }
26159 } else {
26160 (None, completion.new_text.clone())
26161 };
26162
26163 let mut range_to_replace = {
26164 let replace_range = &completion.replace_range;
26165 if let CompletionSource::Lsp {
26166 insert_range: Some(insert_range),
26167 ..
26168 } = &completion.source
26169 {
26170 debug_assert_eq!(
26171 insert_range.start, replace_range.start,
26172 "insert_range and replace_range should start at the same position"
26173 );
26174 debug_assert!(
26175 insert_range
26176 .start
26177 .cmp(cursor_position, &buffer_snapshot)
26178 .is_le(),
26179 "insert_range should start before or at cursor position"
26180 );
26181 debug_assert!(
26182 replace_range
26183 .start
26184 .cmp(cursor_position, &buffer_snapshot)
26185 .is_le(),
26186 "replace_range should start before or at cursor position"
26187 );
26188
26189 let should_replace = match intent {
26190 CompletionIntent::CompleteWithInsert => false,
26191 CompletionIntent::CompleteWithReplace => true,
26192 CompletionIntent::Complete | CompletionIntent::Compose => {
26193 let insert_mode = LanguageSettings::for_buffer(&buffer, cx)
26194 .completions
26195 .lsp_insert_mode;
26196 match insert_mode {
26197 LspInsertMode::Insert => false,
26198 LspInsertMode::Replace => true,
26199 LspInsertMode::ReplaceSubsequence => {
26200 let mut text_to_replace = buffer.chars_for_range(
26201 buffer.anchor_before(replace_range.start)
26202 ..buffer.anchor_after(replace_range.end),
26203 );
26204 let mut current_needle = text_to_replace.next();
26205 for haystack_ch in completion.label.text.chars() {
26206 if let Some(needle_ch) = current_needle
26207 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
26208 {
26209 current_needle = text_to_replace.next();
26210 }
26211 }
26212 current_needle.is_none()
26213 }
26214 LspInsertMode::ReplaceSuffix => {
26215 if replace_range
26216 .end
26217 .cmp(cursor_position, &buffer_snapshot)
26218 .is_gt()
26219 {
26220 let range_after_cursor = *cursor_position..replace_range.end;
26221 let text_after_cursor = buffer
26222 .text_for_range(
26223 buffer.anchor_before(range_after_cursor.start)
26224 ..buffer.anchor_after(range_after_cursor.end),
26225 )
26226 .collect::<String>()
26227 .to_ascii_lowercase();
26228 completion
26229 .label
26230 .text
26231 .to_ascii_lowercase()
26232 .ends_with(&text_after_cursor)
26233 } else {
26234 true
26235 }
26236 }
26237 }
26238 }
26239 };
26240
26241 if should_replace {
26242 replace_range.clone()
26243 } else {
26244 insert_range.clone()
26245 }
26246 } else {
26247 replace_range.clone()
26248 }
26249 };
26250
26251 if range_to_replace
26252 .end
26253 .cmp(cursor_position, &buffer_snapshot)
26254 .is_lt()
26255 {
26256 range_to_replace.end = *cursor_position;
26257 }
26258
26259 CompletionEdit {
26260 new_text,
26261 replace_range: range_to_replace,
26262 snippet,
26263 }
26264}
26265
26266struct CompletionEdit {
26267 new_text: String,
26268 replace_range: Range<text::Anchor>,
26269 snippet: Option<Snippet>,
26270}
26271
26272fn comment_delimiter_for_newline(
26273 start_point: &Point,
26274 buffer: &MultiBufferSnapshot,
26275 language: &LanguageScope,
26276) -> Option<Arc<str>> {
26277 let delimiters = language.line_comment_prefixes();
26278 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
26279 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26280
26281 let num_of_whitespaces = snapshot
26282 .chars_for_range(range.clone())
26283 .take_while(|c| c.is_whitespace())
26284 .count();
26285 let comment_candidate = snapshot
26286 .chars_for_range(range.clone())
26287 .skip(num_of_whitespaces)
26288 .take(max_len_of_delimiter + 2)
26289 .collect::<String>();
26290 let (delimiter, trimmed_len, is_repl) = delimiters
26291 .iter()
26292 .filter_map(|delimiter| {
26293 let prefix = delimiter.trim_end();
26294 if comment_candidate.starts_with(prefix) {
26295 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
26296 {
26297 stripped_comment.starts_with(" %%")
26298 } else {
26299 false
26300 };
26301 Some((delimiter, prefix.len(), is_repl))
26302 } else {
26303 None
26304 }
26305 })
26306 .max_by_key(|(_, len, _)| *len)?;
26307
26308 if let Some(BlockCommentConfig {
26309 start: block_start, ..
26310 }) = language.block_comment()
26311 {
26312 let block_start_trimmed = block_start.trim_end();
26313 if block_start_trimmed.starts_with(delimiter.trim_end()) {
26314 let line_content = snapshot
26315 .chars_for_range(range.clone())
26316 .skip(num_of_whitespaces)
26317 .take(block_start_trimmed.len())
26318 .collect::<String>();
26319
26320 if line_content.starts_with(block_start_trimmed) {
26321 return None;
26322 }
26323 }
26324 }
26325
26326 let cursor_is_placed_after_comment_marker =
26327 num_of_whitespaces + trimmed_len <= start_point.column as usize;
26328 if cursor_is_placed_after_comment_marker {
26329 if !is_repl {
26330 return Some(delimiter.clone());
26331 }
26332
26333 let line_content_after_cursor: String = snapshot
26334 .chars_for_range(range)
26335 .skip(start_point.column as usize)
26336 .collect();
26337
26338 if line_content_after_cursor.trim().is_empty() {
26339 return None;
26340 } else {
26341 return Some(delimiter.clone());
26342 }
26343 } else {
26344 None
26345 }
26346}
26347
26348fn documentation_delimiter_for_newline(
26349 start_point: &Point,
26350 buffer: &MultiBufferSnapshot,
26351 language: &LanguageScope,
26352 newline_config: &mut NewlineConfig,
26353) -> Option<Arc<str>> {
26354 let BlockCommentConfig {
26355 start: start_tag,
26356 end: end_tag,
26357 prefix: delimiter,
26358 tab_size: len,
26359 } = language.documentation_comment()?;
26360 let is_within_block_comment = buffer
26361 .language_scope_at(*start_point)
26362 .is_some_and(|scope| scope.override_name() == Some("comment"));
26363 if !is_within_block_comment {
26364 return None;
26365 }
26366
26367 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26368
26369 let num_of_whitespaces = snapshot
26370 .chars_for_range(range.clone())
26371 .take_while(|c| c.is_whitespace())
26372 .count();
26373
26374 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
26375 let column = start_point.column;
26376 let cursor_is_after_start_tag = {
26377 let start_tag_len = start_tag.len();
26378 let start_tag_line = snapshot
26379 .chars_for_range(range.clone())
26380 .skip(num_of_whitespaces)
26381 .take(start_tag_len)
26382 .collect::<String>();
26383 if start_tag_line.starts_with(start_tag.as_ref()) {
26384 num_of_whitespaces + start_tag_len <= column as usize
26385 } else {
26386 false
26387 }
26388 };
26389
26390 let cursor_is_after_delimiter = {
26391 let delimiter_trim = delimiter.trim_end();
26392 let delimiter_line = snapshot
26393 .chars_for_range(range.clone())
26394 .skip(num_of_whitespaces)
26395 .take(delimiter_trim.len())
26396 .collect::<String>();
26397 if delimiter_line.starts_with(delimiter_trim) {
26398 num_of_whitespaces + delimiter_trim.len() <= column as usize
26399 } else {
26400 false
26401 }
26402 };
26403
26404 let mut needs_extra_line = false;
26405 let mut extra_line_additional_indent = IndentSize::spaces(0);
26406
26407 let cursor_is_before_end_tag_if_exists = {
26408 let mut char_position = 0u32;
26409 let mut end_tag_offset = None;
26410
26411 'outer: for chunk in snapshot.text_for_range(range) {
26412 if let Some(byte_pos) = chunk.find(&**end_tag) {
26413 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26414 end_tag_offset = Some(char_position + chars_before_match);
26415 break 'outer;
26416 }
26417 char_position += chunk.chars().count() as u32;
26418 }
26419
26420 if let Some(end_tag_offset) = end_tag_offset {
26421 let cursor_is_before_end_tag = column <= end_tag_offset;
26422 if cursor_is_after_start_tag {
26423 if cursor_is_before_end_tag {
26424 needs_extra_line = true;
26425 }
26426 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26427 if cursor_is_at_start_of_end_tag {
26428 extra_line_additional_indent.len = *len;
26429 }
26430 }
26431 cursor_is_before_end_tag
26432 } else {
26433 true
26434 }
26435 };
26436
26437 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26438 && cursor_is_before_end_tag_if_exists
26439 {
26440 let additional_indent = if cursor_is_after_start_tag {
26441 IndentSize::spaces(*len)
26442 } else {
26443 IndentSize::spaces(0)
26444 };
26445
26446 *newline_config = NewlineConfig::Newline {
26447 additional_indent,
26448 extra_line_additional_indent: if needs_extra_line {
26449 Some(extra_line_additional_indent)
26450 } else {
26451 None
26452 },
26453 prevent_auto_indent: true,
26454 };
26455 Some(delimiter.clone())
26456 } else {
26457 None
26458 }
26459}
26460
26461const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26462
26463fn list_delimiter_for_newline(
26464 start_point: &Point,
26465 buffer: &MultiBufferSnapshot,
26466 language: &LanguageScope,
26467 newline_config: &mut NewlineConfig,
26468) -> Option<Arc<str>> {
26469 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26470
26471 let num_of_whitespaces = snapshot
26472 .chars_for_range(range.clone())
26473 .take_while(|c| c.is_whitespace())
26474 .count();
26475
26476 let task_list_entries: Vec<_> = language
26477 .task_list()
26478 .into_iter()
26479 .flat_map(|config| {
26480 config
26481 .prefixes
26482 .iter()
26483 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26484 })
26485 .collect();
26486 let unordered_list_entries: Vec<_> = language
26487 .unordered_list()
26488 .iter()
26489 .map(|marker| (marker.as_ref(), marker.as_ref()))
26490 .collect();
26491
26492 let all_entries: Vec<_> = task_list_entries
26493 .into_iter()
26494 .chain(unordered_list_entries)
26495 .collect();
26496
26497 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26498 let candidate: String = snapshot
26499 .chars_for_range(range.clone())
26500 .skip(num_of_whitespaces)
26501 .take(max_prefix_len)
26502 .collect();
26503
26504 if let Some((prefix, continuation)) = all_entries
26505 .iter()
26506 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26507 .max_by_key(|(prefix, _)| prefix.len())
26508 {
26509 let end_of_prefix = num_of_whitespaces + prefix.len();
26510 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26511 let has_content_after_marker = snapshot
26512 .chars_for_range(range)
26513 .skip(end_of_prefix)
26514 .any(|c| !c.is_whitespace());
26515
26516 if has_content_after_marker && cursor_is_after_prefix {
26517 return Some((*continuation).into());
26518 }
26519
26520 if start_point.column as usize == end_of_prefix {
26521 if num_of_whitespaces == 0 {
26522 *newline_config = NewlineConfig::ClearCurrentLine;
26523 } else {
26524 *newline_config = NewlineConfig::UnindentCurrentLine {
26525 continuation: (*continuation).into(),
26526 };
26527 }
26528 }
26529
26530 return None;
26531 }
26532 }
26533
26534 let candidate: String = snapshot
26535 .chars_for_range(range.clone())
26536 .skip(num_of_whitespaces)
26537 .take(ORDERED_LIST_MAX_MARKER_LEN)
26538 .collect();
26539
26540 for ordered_config in language.ordered_list() {
26541 let regex = match Regex::new(&ordered_config.pattern) {
26542 Ok(r) => r,
26543 Err(_) => continue,
26544 };
26545
26546 if let Some(captures) = regex.captures(&candidate) {
26547 let full_match = captures.get(0)?;
26548 let marker_len = full_match.len();
26549 let end_of_prefix = num_of_whitespaces + marker_len;
26550 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26551
26552 let has_content_after_marker = snapshot
26553 .chars_for_range(range)
26554 .skip(end_of_prefix)
26555 .any(|c| !c.is_whitespace());
26556
26557 if has_content_after_marker && cursor_is_after_prefix {
26558 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26559 let continuation = ordered_config
26560 .format
26561 .replace("{1}", &(number + 1).to_string());
26562 return Some(continuation.into());
26563 }
26564
26565 if start_point.column as usize == end_of_prefix {
26566 let continuation = ordered_config.format.replace("{1}", "1");
26567 if num_of_whitespaces == 0 {
26568 *newline_config = NewlineConfig::ClearCurrentLine;
26569 } else {
26570 *newline_config = NewlineConfig::UnindentCurrentLine {
26571 continuation: continuation.into(),
26572 };
26573 }
26574 }
26575
26576 return None;
26577 }
26578 }
26579
26580 None
26581}
26582
26583fn is_list_prefix_row(
26584 row: MultiBufferRow,
26585 buffer: &MultiBufferSnapshot,
26586 language: &LanguageScope,
26587) -> bool {
26588 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26589 return false;
26590 };
26591
26592 let num_of_whitespaces = snapshot
26593 .chars_for_range(range.clone())
26594 .take_while(|c| c.is_whitespace())
26595 .count();
26596
26597 let task_list_prefixes: Vec<_> = language
26598 .task_list()
26599 .into_iter()
26600 .flat_map(|config| {
26601 config
26602 .prefixes
26603 .iter()
26604 .map(|p| p.as_ref())
26605 .collect::<Vec<_>>()
26606 })
26607 .collect();
26608 let unordered_list_markers: Vec<_> = language
26609 .unordered_list()
26610 .iter()
26611 .map(|marker| marker.as_ref())
26612 .collect();
26613 let all_prefixes: Vec<_> = task_list_prefixes
26614 .into_iter()
26615 .chain(unordered_list_markers)
26616 .collect();
26617 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26618 let candidate: String = snapshot
26619 .chars_for_range(range.clone())
26620 .skip(num_of_whitespaces)
26621 .take(max_prefix_len)
26622 .collect();
26623 if all_prefixes
26624 .iter()
26625 .any(|prefix| candidate.starts_with(*prefix))
26626 {
26627 return true;
26628 }
26629 }
26630
26631 let ordered_list_candidate: String = snapshot
26632 .chars_for_range(range)
26633 .skip(num_of_whitespaces)
26634 .take(ORDERED_LIST_MAX_MARKER_LEN)
26635 .collect();
26636 for ordered_config in language.ordered_list() {
26637 let regex = match Regex::new(&ordered_config.pattern) {
26638 Ok(r) => r,
26639 Err(_) => continue,
26640 };
26641 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26642 return captures.get(0).is_some();
26643 }
26644 }
26645
26646 false
26647}
26648
26649#[derive(Debug)]
26650enum NewlineConfig {
26651 /// Insert newline with optional additional indent and optional extra blank line
26652 Newline {
26653 additional_indent: IndentSize,
26654 extra_line_additional_indent: Option<IndentSize>,
26655 prevent_auto_indent: bool,
26656 },
26657 /// Clear the current line
26658 ClearCurrentLine,
26659 /// Unindent the current line and add continuation
26660 UnindentCurrentLine { continuation: Arc<str> },
26661}
26662
26663impl NewlineConfig {
26664 fn has_extra_line(&self) -> bool {
26665 matches!(
26666 self,
26667 Self::Newline {
26668 extra_line_additional_indent: Some(_),
26669 ..
26670 }
26671 )
26672 }
26673
26674 fn insert_extra_newline_brackets(
26675 buffer: &MultiBufferSnapshot,
26676 range: Range<MultiBufferOffset>,
26677 language: &language::LanguageScope,
26678 ) -> bool {
26679 let leading_whitespace_len = buffer
26680 .reversed_chars_at(range.start)
26681 .take_while(|c| c.is_whitespace() && *c != '\n')
26682 .map(|c| c.len_utf8())
26683 .sum::<usize>();
26684 let trailing_whitespace_len = buffer
26685 .chars_at(range.end)
26686 .take_while(|c| c.is_whitespace() && *c != '\n')
26687 .map(|c| c.len_utf8())
26688 .sum::<usize>();
26689 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26690
26691 language.brackets().any(|(pair, enabled)| {
26692 let pair_start = pair.start.trim_end();
26693 let pair_end = pair.end.trim_start();
26694
26695 enabled
26696 && pair.newline
26697 && buffer.contains_str_at(range.end, pair_end)
26698 && buffer.contains_str_at(
26699 range.start.saturating_sub_usize(pair_start.len()),
26700 pair_start,
26701 )
26702 })
26703 }
26704
26705 fn insert_extra_newline_tree_sitter(
26706 buffer: &MultiBufferSnapshot,
26707 range: Range<MultiBufferOffset>,
26708 ) -> bool {
26709 let (buffer, range) = match buffer
26710 .range_to_buffer_ranges(range.start..range.end)
26711 .as_slice()
26712 {
26713 [(buffer_snapshot, range, _)] => (buffer_snapshot.clone(), range.clone()),
26714 _ => return false,
26715 };
26716 let pair = {
26717 let mut result: Option<BracketMatch<usize>> = None;
26718
26719 for pair in buffer
26720 .all_bracket_ranges(range.start.0..range.end.0)
26721 .filter(move |pair| {
26722 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26723 })
26724 {
26725 let len = pair.close_range.end - pair.open_range.start;
26726
26727 if let Some(existing) = &result {
26728 let existing_len = existing.close_range.end - existing.open_range.start;
26729 if len > existing_len {
26730 continue;
26731 }
26732 }
26733
26734 result = Some(pair);
26735 }
26736
26737 result
26738 };
26739 let Some(pair) = pair else {
26740 return false;
26741 };
26742 pair.newline_only
26743 && buffer
26744 .chars_for_range(pair.open_range.end..range.start.0)
26745 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26746 .all(|c| c.is_whitespace() && c != '\n')
26747 }
26748}
26749
26750fn update_uncommitted_diff_for_buffer(
26751 editor: Entity<Editor>,
26752 project: &Entity<Project>,
26753 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26754 buffer: Entity<MultiBuffer>,
26755 cx: &mut App,
26756) -> Task<()> {
26757 let mut tasks = Vec::new();
26758 project.update(cx, |project, cx| {
26759 for buffer in buffers {
26760 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26761 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26762 }
26763 }
26764 });
26765 cx.spawn(async move |cx| {
26766 let diffs = future::join_all(tasks).await;
26767 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26768 return;
26769 }
26770
26771 buffer.update(cx, |buffer, cx| {
26772 for diff in diffs.into_iter().flatten() {
26773 buffer.add_diff(diff, cx);
26774 }
26775 });
26776 })
26777}
26778
26779fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26780 let tab_size = tab_size.get() as usize;
26781 let mut width = offset;
26782
26783 for ch in text.chars() {
26784 width += if ch == '\t' {
26785 tab_size - (width % tab_size)
26786 } else {
26787 1
26788 };
26789 }
26790
26791 width - offset
26792}
26793
26794#[cfg(test)]
26795mod tests {
26796 use super::*;
26797
26798 #[test]
26799 fn test_string_size_with_expanded_tabs() {
26800 let nz = |val| NonZeroU32::new(val).unwrap();
26801 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26802 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26803 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26804 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26805 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26806 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26807 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26808 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26809 }
26810}
26811
26812/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26813struct WordBreakingTokenizer<'a> {
26814 input: &'a str,
26815}
26816
26817impl<'a> WordBreakingTokenizer<'a> {
26818 fn new(input: &'a str) -> Self {
26819 Self { input }
26820 }
26821}
26822
26823fn is_char_ideographic(ch: char) -> bool {
26824 use unicode_script::Script::*;
26825 use unicode_script::UnicodeScript;
26826 matches!(ch.script(), Han | Tangut | Yi)
26827}
26828
26829fn is_grapheme_ideographic(text: &str) -> bool {
26830 text.chars().any(is_char_ideographic)
26831}
26832
26833fn is_grapheme_whitespace(text: &str) -> bool {
26834 text.chars().any(|x| x.is_whitespace())
26835}
26836
26837fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26838 text.chars()
26839 .next()
26840 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26841}
26842
26843#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26844enum WordBreakToken<'a> {
26845 Word { token: &'a str, grapheme_len: usize },
26846 InlineWhitespace { token: &'a str, grapheme_len: usize },
26847 Newline,
26848}
26849
26850impl<'a> Iterator for WordBreakingTokenizer<'a> {
26851 /// Yields a span, the count of graphemes in the token, and whether it was
26852 /// whitespace. Note that it also breaks at word boundaries.
26853 type Item = WordBreakToken<'a>;
26854
26855 fn next(&mut self) -> Option<Self::Item> {
26856 use unicode_segmentation::UnicodeSegmentation;
26857 if self.input.is_empty() {
26858 return None;
26859 }
26860
26861 let mut iter = self.input.graphemes(true).peekable();
26862 let mut offset = 0;
26863 let mut grapheme_len = 0;
26864 if let Some(first_grapheme) = iter.next() {
26865 let is_newline = first_grapheme == "\n";
26866 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26867 offset += first_grapheme.len();
26868 grapheme_len += 1;
26869 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26870 if let Some(grapheme) = iter.peek().copied()
26871 && should_stay_with_preceding_ideograph(grapheme)
26872 {
26873 offset += grapheme.len();
26874 grapheme_len += 1;
26875 }
26876 } else {
26877 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26878 let mut next_word_bound = words.peek().copied();
26879 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26880 next_word_bound = words.next();
26881 }
26882 while let Some(grapheme) = iter.peek().copied() {
26883 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26884 break;
26885 };
26886 if is_grapheme_whitespace(grapheme) != is_whitespace
26887 || (grapheme == "\n") != is_newline
26888 {
26889 break;
26890 };
26891 offset += grapheme.len();
26892 grapheme_len += 1;
26893 iter.next();
26894 }
26895 }
26896 let token = &self.input[..offset];
26897 self.input = &self.input[offset..];
26898 if token == "\n" {
26899 Some(WordBreakToken::Newline)
26900 } else if is_whitespace {
26901 Some(WordBreakToken::InlineWhitespace {
26902 token,
26903 grapheme_len,
26904 })
26905 } else {
26906 Some(WordBreakToken::Word {
26907 token,
26908 grapheme_len,
26909 })
26910 }
26911 } else {
26912 None
26913 }
26914 }
26915}
26916
26917#[test]
26918fn test_word_breaking_tokenizer() {
26919 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26920 ("", &[]),
26921 (" ", &[whitespace(" ", 2)]),
26922 ("Ʒ", &[word("Ʒ", 1)]),
26923 ("Ǽ", &[word("Ǽ", 1)]),
26924 ("⋑", &[word("⋑", 1)]),
26925 ("⋑⋑", &[word("⋑⋑", 2)]),
26926 (
26927 "原理,进而",
26928 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26929 ),
26930 (
26931 "hello world",
26932 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26933 ),
26934 (
26935 "hello, world",
26936 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26937 ),
26938 (
26939 " hello world",
26940 &[
26941 whitespace(" ", 2),
26942 word("hello", 5),
26943 whitespace(" ", 1),
26944 word("world", 5),
26945 ],
26946 ),
26947 (
26948 "这是什么 \n 钢笔",
26949 &[
26950 word("这", 1),
26951 word("是", 1),
26952 word("什", 1),
26953 word("么", 1),
26954 whitespace(" ", 1),
26955 newline(),
26956 whitespace(" ", 1),
26957 word("钢", 1),
26958 word("笔", 1),
26959 ],
26960 ),
26961 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26962 ];
26963
26964 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26965 WordBreakToken::Word {
26966 token,
26967 grapheme_len,
26968 }
26969 }
26970
26971 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26972 WordBreakToken::InlineWhitespace {
26973 token,
26974 grapheme_len,
26975 }
26976 }
26977
26978 fn newline() -> WordBreakToken<'static> {
26979 WordBreakToken::Newline
26980 }
26981
26982 for (input, result) in tests {
26983 assert_eq!(
26984 WordBreakingTokenizer::new(input)
26985 .collect::<Vec<_>>()
26986 .as_slice(),
26987 *result,
26988 );
26989 }
26990}
26991
26992fn wrap_with_prefix(
26993 first_line_prefix: String,
26994 subsequent_lines_prefix: String,
26995 unwrapped_text: String,
26996 wrap_column: usize,
26997 tab_size: NonZeroU32,
26998 preserve_existing_whitespace: bool,
26999) -> String {
27000 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
27001 let subsequent_lines_prefix_len =
27002 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
27003 let mut wrapped_text = String::new();
27004 let mut current_line = first_line_prefix;
27005 let mut is_first_line = true;
27006
27007 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
27008 let mut current_line_len = first_line_prefix_len;
27009 let mut in_whitespace = false;
27010 for token in tokenizer {
27011 let have_preceding_whitespace = in_whitespace;
27012 match token {
27013 WordBreakToken::Word {
27014 token,
27015 grapheme_len,
27016 } => {
27017 in_whitespace = false;
27018 let current_prefix_len = if is_first_line {
27019 first_line_prefix_len
27020 } else {
27021 subsequent_lines_prefix_len
27022 };
27023 if current_line_len + grapheme_len > wrap_column
27024 && current_line_len != current_prefix_len
27025 {
27026 wrapped_text.push_str(current_line.trim_end());
27027 wrapped_text.push('\n');
27028 is_first_line = false;
27029 current_line = subsequent_lines_prefix.clone();
27030 current_line_len = subsequent_lines_prefix_len;
27031 }
27032 current_line.push_str(token);
27033 current_line_len += grapheme_len;
27034 }
27035 WordBreakToken::InlineWhitespace {
27036 mut token,
27037 mut grapheme_len,
27038 } => {
27039 in_whitespace = true;
27040 if have_preceding_whitespace && !preserve_existing_whitespace {
27041 continue;
27042 }
27043 if !preserve_existing_whitespace {
27044 // Keep a single whitespace grapheme as-is
27045 if let Some(first) =
27046 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
27047 {
27048 token = first;
27049 } else {
27050 token = " ";
27051 }
27052 grapheme_len = 1;
27053 }
27054 let current_prefix_len = if is_first_line {
27055 first_line_prefix_len
27056 } else {
27057 subsequent_lines_prefix_len
27058 };
27059 if current_line_len + grapheme_len > wrap_column {
27060 wrapped_text.push_str(current_line.trim_end());
27061 wrapped_text.push('\n');
27062 is_first_line = false;
27063 current_line = subsequent_lines_prefix.clone();
27064 current_line_len = subsequent_lines_prefix_len;
27065 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
27066 current_line.push_str(token);
27067 current_line_len += grapheme_len;
27068 }
27069 }
27070 WordBreakToken::Newline => {
27071 in_whitespace = true;
27072 let current_prefix_len = if is_first_line {
27073 first_line_prefix_len
27074 } else {
27075 subsequent_lines_prefix_len
27076 };
27077 if preserve_existing_whitespace {
27078 wrapped_text.push_str(current_line.trim_end());
27079 wrapped_text.push('\n');
27080 is_first_line = false;
27081 current_line = subsequent_lines_prefix.clone();
27082 current_line_len = subsequent_lines_prefix_len;
27083 } else if have_preceding_whitespace {
27084 continue;
27085 } else if current_line_len + 1 > wrap_column
27086 && current_line_len != current_prefix_len
27087 {
27088 wrapped_text.push_str(current_line.trim_end());
27089 wrapped_text.push('\n');
27090 is_first_line = false;
27091 current_line = subsequent_lines_prefix.clone();
27092 current_line_len = subsequent_lines_prefix_len;
27093 } else if current_line_len != current_prefix_len {
27094 current_line.push(' ');
27095 current_line_len += 1;
27096 }
27097 }
27098 }
27099 }
27100
27101 if !current_line.is_empty() {
27102 wrapped_text.push_str(¤t_line);
27103 }
27104 wrapped_text
27105}
27106
27107#[test]
27108fn test_wrap_with_prefix() {
27109 assert_eq!(
27110 wrap_with_prefix(
27111 "# ".to_string(),
27112 "# ".to_string(),
27113 "abcdefg".to_string(),
27114 4,
27115 NonZeroU32::new(4).unwrap(),
27116 false,
27117 ),
27118 "# abcdefg"
27119 );
27120 assert_eq!(
27121 wrap_with_prefix(
27122 "".to_string(),
27123 "".to_string(),
27124 "\thello world".to_string(),
27125 8,
27126 NonZeroU32::new(4).unwrap(),
27127 false,
27128 ),
27129 "hello\nworld"
27130 );
27131 assert_eq!(
27132 wrap_with_prefix(
27133 "// ".to_string(),
27134 "// ".to_string(),
27135 "xx \nyy zz aa bb cc".to_string(),
27136 12,
27137 NonZeroU32::new(4).unwrap(),
27138 false,
27139 ),
27140 "// xx yy zz\n// aa bb cc"
27141 );
27142 assert_eq!(
27143 wrap_with_prefix(
27144 String::new(),
27145 String::new(),
27146 "这是什么 \n 钢笔".to_string(),
27147 3,
27148 NonZeroU32::new(4).unwrap(),
27149 false,
27150 ),
27151 "这是什\n么 钢\n笔"
27152 );
27153 assert_eq!(
27154 wrap_with_prefix(
27155 String::new(),
27156 String::new(),
27157 format!("foo{}bar", '\u{2009}'), // thin space
27158 80,
27159 NonZeroU32::new(4).unwrap(),
27160 false,
27161 ),
27162 format!("foo{}bar", '\u{2009}')
27163 );
27164}
27165
27166pub trait CollaborationHub {
27167 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
27168 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
27169 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
27170}
27171
27172impl CollaborationHub for Entity<Project> {
27173 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
27174 self.read(cx).collaborators()
27175 }
27176
27177 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
27178 self.read(cx).user_store().read(cx).participant_indices()
27179 }
27180
27181 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
27182 let this = self.read(cx);
27183 let user_ids = this.collaborators().values().map(|c| c.user_id);
27184 this.user_store().read(cx).participant_names(user_ids, cx)
27185 }
27186}
27187
27188pub trait SemanticsProvider {
27189 fn hover(
27190 &self,
27191 buffer: &Entity<Buffer>,
27192 position: text::Anchor,
27193 cx: &mut App,
27194 ) -> Option<Task<Option<Vec<project::Hover>>>>;
27195
27196 fn inline_values(
27197 &self,
27198 buffer_handle: Entity<Buffer>,
27199 range: Range<text::Anchor>,
27200 cx: &mut App,
27201 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
27202
27203 fn applicable_inlay_chunks(
27204 &self,
27205 buffer: &Entity<Buffer>,
27206 ranges: &[Range<text::Anchor>],
27207 cx: &mut App,
27208 ) -> Vec<Range<BufferRow>>;
27209
27210 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
27211
27212 fn inlay_hints(
27213 &self,
27214 invalidate: InvalidationStrategy,
27215 buffer: Entity<Buffer>,
27216 ranges: Vec<Range<text::Anchor>>,
27217 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27218 cx: &mut App,
27219 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
27220
27221 fn semantic_tokens(
27222 &self,
27223 buffer: Entity<Buffer>,
27224 refresh: Option<RefreshForServer>,
27225 cx: &mut App,
27226 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
27227
27228 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27229
27230 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27231
27232 fn document_highlights(
27233 &self,
27234 buffer: &Entity<Buffer>,
27235 position: text::Anchor,
27236 cx: &mut App,
27237 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
27238
27239 fn definitions(
27240 &self,
27241 buffer: &Entity<Buffer>,
27242 position: text::Anchor,
27243 kind: GotoDefinitionKind,
27244 cx: &mut App,
27245 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
27246
27247 fn range_for_rename(
27248 &self,
27249 buffer: &Entity<Buffer>,
27250 position: text::Anchor,
27251 cx: &mut App,
27252 ) -> Task<Result<Option<Range<text::Anchor>>>>;
27253
27254 fn perform_rename(
27255 &self,
27256 buffer: &Entity<Buffer>,
27257 position: text::Anchor,
27258 new_name: String,
27259 cx: &mut App,
27260 ) -> Option<Task<Result<ProjectTransaction>>>;
27261}
27262
27263pub trait CompletionProvider {
27264 fn completions(
27265 &self,
27266 buffer: &Entity<Buffer>,
27267 buffer_position: text::Anchor,
27268 trigger: CompletionContext,
27269 window: &mut Window,
27270 cx: &mut Context<Editor>,
27271 ) -> Task<Result<Vec<CompletionResponse>>>;
27272
27273 fn resolve_completions(
27274 &self,
27275 _buffer: Entity<Buffer>,
27276 _completion_indices: Vec<usize>,
27277 _completions: Rc<RefCell<Box<[Completion]>>>,
27278 _cx: &mut Context<Editor>,
27279 ) -> Task<Result<bool>> {
27280 Task::ready(Ok(false))
27281 }
27282
27283 fn apply_additional_edits_for_completion(
27284 &self,
27285 _buffer: Entity<Buffer>,
27286 _completions: Rc<RefCell<Box<[Completion]>>>,
27287 _completion_index: usize,
27288 _push_to_history: bool,
27289 _all_commit_ranges: Vec<Range<language::Anchor>>,
27290 _cx: &mut Context<Editor>,
27291 ) -> Task<Result<Option<language::Transaction>>> {
27292 Task::ready(Ok(None))
27293 }
27294
27295 fn is_completion_trigger(
27296 &self,
27297 buffer: &Entity<Buffer>,
27298 position: language::Anchor,
27299 text: &str,
27300 trigger_in_words: bool,
27301 cx: &mut Context<Editor>,
27302 ) -> bool;
27303
27304 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
27305
27306 fn sort_completions(&self) -> bool {
27307 true
27308 }
27309
27310 fn filter_completions(&self) -> bool {
27311 true
27312 }
27313
27314 fn show_snippets(&self) -> bool {
27315 false
27316 }
27317}
27318
27319pub trait CodeActionProvider {
27320 fn id(&self) -> Arc<str>;
27321
27322 fn code_actions(
27323 &self,
27324 buffer: &Entity<Buffer>,
27325 range: Range<text::Anchor>,
27326 window: &mut Window,
27327 cx: &mut App,
27328 ) -> Task<Result<Vec<CodeAction>>>;
27329
27330 fn apply_code_action(
27331 &self,
27332 buffer_handle: Entity<Buffer>,
27333 action: CodeAction,
27334 push_to_history: bool,
27335 window: &mut Window,
27336 cx: &mut App,
27337 ) -> Task<Result<ProjectTransaction>>;
27338}
27339
27340impl CodeActionProvider for Entity<Project> {
27341 fn id(&self) -> Arc<str> {
27342 "project".into()
27343 }
27344
27345 fn code_actions(
27346 &self,
27347 buffer: &Entity<Buffer>,
27348 range: Range<text::Anchor>,
27349 _window: &mut Window,
27350 cx: &mut App,
27351 ) -> Task<Result<Vec<CodeAction>>> {
27352 self.update(cx, |project, cx| {
27353 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27354 let code_actions = project.code_actions(buffer, range, None, cx);
27355 cx.background_spawn(async move {
27356 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27357 Ok(code_lens_actions
27358 .context("code lens fetch")?
27359 .into_iter()
27360 .flatten()
27361 .chain(
27362 code_actions
27363 .context("code action fetch")?
27364 .into_iter()
27365 .flatten(),
27366 )
27367 .collect())
27368 })
27369 })
27370 }
27371
27372 fn apply_code_action(
27373 &self,
27374 buffer_handle: Entity<Buffer>,
27375 action: CodeAction,
27376 push_to_history: bool,
27377 _window: &mut Window,
27378 cx: &mut App,
27379 ) -> Task<Result<ProjectTransaction>> {
27380 self.update(cx, |project, cx| {
27381 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27382 })
27383 }
27384}
27385
27386fn snippet_completions(
27387 project: &Project,
27388 buffer: &Entity<Buffer>,
27389 buffer_anchor: text::Anchor,
27390 classifier: CharClassifier,
27391 cx: &mut App,
27392) -> Task<Result<CompletionResponse>> {
27393 let languages = buffer.read(cx).languages_at(buffer_anchor);
27394 let snippet_store = project.snippets().read(cx);
27395
27396 let scopes: Vec<_> = languages
27397 .iter()
27398 .filter_map(|language| {
27399 let language_name = language.lsp_id();
27400 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27401
27402 if snippets.is_empty() {
27403 None
27404 } else {
27405 Some((language.default_scope(), snippets))
27406 }
27407 })
27408 .collect();
27409
27410 if scopes.is_empty() {
27411 return Task::ready(Ok(CompletionResponse {
27412 completions: vec![],
27413 display_options: CompletionDisplayOptions::default(),
27414 is_incomplete: false,
27415 }));
27416 }
27417
27418 let snapshot = buffer.read(cx).text_snapshot();
27419 let executor = cx.background_executor().clone();
27420
27421 cx.background_spawn(async move {
27422 let is_word_char = |c| classifier.is_word(c);
27423
27424 let mut is_incomplete = false;
27425 let mut completions: Vec<Completion> = Vec::new();
27426
27427 const MAX_PREFIX_LEN: usize = 128;
27428 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27429 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27430 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27431
27432 let max_buffer_window: String = snapshot
27433 .text_for_range(window_start..buffer_offset)
27434 .collect();
27435
27436 if max_buffer_window.is_empty() {
27437 return Ok(CompletionResponse {
27438 completions: vec![],
27439 display_options: CompletionDisplayOptions::default(),
27440 is_incomplete: true,
27441 });
27442 }
27443
27444 for (_scope, snippets) in scopes.into_iter() {
27445 // Sort snippets by word count to match longer snippet prefixes first.
27446 let mut sorted_snippet_candidates = snippets
27447 .iter()
27448 .enumerate()
27449 .flat_map(|(snippet_ix, snippet)| {
27450 snippet
27451 .prefix
27452 .iter()
27453 .enumerate()
27454 .map(move |(prefix_ix, prefix)| {
27455 let word_count =
27456 snippet_candidate_suffixes(prefix, &is_word_char).count();
27457 ((snippet_ix, prefix_ix), prefix, word_count)
27458 })
27459 })
27460 .collect_vec();
27461 sorted_snippet_candidates
27462 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27463
27464 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27465
27466 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27467 .take(
27468 sorted_snippet_candidates
27469 .first()
27470 .map(|(_, _, word_count)| *word_count)
27471 .unwrap_or_default(),
27472 )
27473 .collect_vec();
27474
27475 const MAX_RESULTS: usize = 100;
27476 // Each match also remembers how many characters from the buffer it consumed
27477 let mut matches: Vec<(StringMatch, usize)> = vec![];
27478
27479 let mut snippet_list_cutoff_index = 0;
27480 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27481 let word_count = buffer_index + 1;
27482 // Increase `snippet_list_cutoff_index` until we have all of the
27483 // snippets with sufficiently many words.
27484 while sorted_snippet_candidates
27485 .get(snippet_list_cutoff_index)
27486 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27487 *snippet_word_count >= word_count
27488 })
27489 {
27490 snippet_list_cutoff_index += 1;
27491 }
27492
27493 // Take only the candidates with at least `word_count` many words
27494 let snippet_candidates_at_word_len =
27495 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27496
27497 let candidates = snippet_candidates_at_word_len
27498 .iter()
27499 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27500 .enumerate() // index in `sorted_snippet_candidates`
27501 // First char must match
27502 .filter(|(_ix, prefix)| {
27503 itertools::equal(
27504 prefix
27505 .chars()
27506 .next()
27507 .into_iter()
27508 .flat_map(|c| c.to_lowercase()),
27509 buffer_window
27510 .chars()
27511 .next()
27512 .into_iter()
27513 .flat_map(|c| c.to_lowercase()),
27514 )
27515 })
27516 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27517 .collect::<Vec<StringMatchCandidate>>();
27518
27519 matches.extend(
27520 fuzzy::match_strings(
27521 &candidates,
27522 &buffer_window,
27523 buffer_window.chars().any(|c| c.is_uppercase()),
27524 true,
27525 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27526 &Default::default(),
27527 executor.clone(),
27528 )
27529 .await
27530 .into_iter()
27531 .map(|string_match| (string_match, buffer_window.len())),
27532 );
27533
27534 if matches.len() >= MAX_RESULTS {
27535 break;
27536 }
27537 }
27538
27539 let to_lsp = |point: &text::Anchor| {
27540 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27541 point_to_lsp(end)
27542 };
27543 let lsp_end = to_lsp(&buffer_anchor);
27544
27545 if matches.len() >= MAX_RESULTS {
27546 is_incomplete = true;
27547 }
27548
27549 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27550 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27551 sorted_snippet_candidates[string_match.candidate_id];
27552 let snippet = &snippets[snippet_index];
27553 let start = buffer_offset - buffer_window_len;
27554 let start = snapshot.anchor_before(start);
27555 let range = start..buffer_anchor;
27556 let lsp_start = to_lsp(&start);
27557 let lsp_range = lsp::Range {
27558 start: lsp_start,
27559 end: lsp_end,
27560 };
27561 Completion {
27562 replace_range: range,
27563 new_text: snippet.body.clone(),
27564 source: CompletionSource::Lsp {
27565 insert_range: None,
27566 server_id: LanguageServerId(usize::MAX),
27567 resolved: true,
27568 lsp_completion: Box::new(lsp::CompletionItem {
27569 label: snippet.prefix.first().unwrap().clone(),
27570 kind: Some(CompletionItemKind::SNIPPET),
27571 label_details: snippet.description.as_ref().map(|description| {
27572 lsp::CompletionItemLabelDetails {
27573 detail: Some(description.clone()),
27574 description: None,
27575 }
27576 }),
27577 insert_text_format: Some(InsertTextFormat::SNIPPET),
27578 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27579 lsp::InsertReplaceEdit {
27580 new_text: snippet.body.clone(),
27581 insert: lsp_range,
27582 replace: lsp_range,
27583 },
27584 )),
27585 filter_text: Some(snippet.body.clone()),
27586 sort_text: Some(char::MAX.to_string()),
27587 ..lsp::CompletionItem::default()
27588 }),
27589 lsp_defaults: None,
27590 },
27591 label: CodeLabel {
27592 text: matching_prefix.clone(),
27593 runs: Vec::new(),
27594 filter_range: 0..matching_prefix.len(),
27595 },
27596 icon_path: None,
27597 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27598 single_line: snippet.name.clone().into(),
27599 plain_text: snippet
27600 .description
27601 .clone()
27602 .map(|description| description.into()),
27603 }),
27604 insert_text_mode: None,
27605 confirm: None,
27606 match_start: Some(start),
27607 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27608 }
27609 }));
27610 }
27611
27612 Ok(CompletionResponse {
27613 completions,
27614 display_options: CompletionDisplayOptions::default(),
27615 is_incomplete,
27616 })
27617 })
27618}
27619
27620impl CompletionProvider for Entity<Project> {
27621 fn completions(
27622 &self,
27623 buffer: &Entity<Buffer>,
27624 buffer_position: text::Anchor,
27625 options: CompletionContext,
27626 _window: &mut Window,
27627 cx: &mut Context<Editor>,
27628 ) -> Task<Result<Vec<CompletionResponse>>> {
27629 self.update(cx, |project, cx| {
27630 let task = project.completions(buffer, buffer_position, options, cx);
27631 cx.background_spawn(task)
27632 })
27633 }
27634
27635 fn resolve_completions(
27636 &self,
27637 buffer: Entity<Buffer>,
27638 completion_indices: Vec<usize>,
27639 completions: Rc<RefCell<Box<[Completion]>>>,
27640 cx: &mut Context<Editor>,
27641 ) -> Task<Result<bool>> {
27642 self.update(cx, |project, cx| {
27643 project.lsp_store().update(cx, |lsp_store, cx| {
27644 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27645 })
27646 })
27647 }
27648
27649 fn apply_additional_edits_for_completion(
27650 &self,
27651 buffer: Entity<Buffer>,
27652 completions: Rc<RefCell<Box<[Completion]>>>,
27653 completion_index: usize,
27654 push_to_history: bool,
27655 all_commit_ranges: Vec<Range<language::Anchor>>,
27656 cx: &mut Context<Editor>,
27657 ) -> Task<Result<Option<language::Transaction>>> {
27658 self.update(cx, |project, cx| {
27659 project.lsp_store().update(cx, |lsp_store, cx| {
27660 lsp_store.apply_additional_edits_for_completion(
27661 buffer,
27662 completions,
27663 completion_index,
27664 push_to_history,
27665 all_commit_ranges,
27666 cx,
27667 )
27668 })
27669 })
27670 }
27671
27672 fn is_completion_trigger(
27673 &self,
27674 buffer: &Entity<Buffer>,
27675 position: language::Anchor,
27676 text: &str,
27677 trigger_in_words: bool,
27678 cx: &mut Context<Editor>,
27679 ) -> bool {
27680 let mut chars = text.chars();
27681 let char = if let Some(char) = chars.next() {
27682 char
27683 } else {
27684 return false;
27685 };
27686 if chars.next().is_some() {
27687 return false;
27688 }
27689
27690 let buffer = buffer.read(cx);
27691 let snapshot = buffer.snapshot();
27692 let classifier = snapshot
27693 .char_classifier_at(position)
27694 .scope_context(Some(CharScopeContext::Completion));
27695 if trigger_in_words && classifier.is_word(char) {
27696 return true;
27697 }
27698
27699 buffer.completion_triggers().contains(text)
27700 }
27701
27702 fn show_snippets(&self) -> bool {
27703 true
27704 }
27705}
27706
27707impl SemanticsProvider for WeakEntity<Project> {
27708 fn hover(
27709 &self,
27710 buffer: &Entity<Buffer>,
27711 position: text::Anchor,
27712 cx: &mut App,
27713 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27714 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27715 .ok()
27716 }
27717
27718 fn document_highlights(
27719 &self,
27720 buffer: &Entity<Buffer>,
27721 position: text::Anchor,
27722 cx: &mut App,
27723 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27724 self.update(cx, |project, cx| {
27725 project.document_highlights(buffer, position, cx)
27726 })
27727 .ok()
27728 }
27729
27730 fn definitions(
27731 &self,
27732 buffer: &Entity<Buffer>,
27733 position: text::Anchor,
27734 kind: GotoDefinitionKind,
27735 cx: &mut App,
27736 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27737 self.update(cx, |project, cx| match kind {
27738 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27739 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27740 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27741 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27742 })
27743 .ok()
27744 }
27745
27746 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27747 self.update(cx, |project, cx| {
27748 if project
27749 .active_debug_session(cx)
27750 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27751 {
27752 return true;
27753 }
27754
27755 buffer.update(cx, |buffer, cx| {
27756 project.any_language_server_supports_inlay_hints(buffer, cx)
27757 })
27758 })
27759 .unwrap_or(false)
27760 }
27761
27762 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27763 self.update(cx, |project, cx| {
27764 buffer.update(cx, |buffer, cx| {
27765 project.any_language_server_supports_semantic_tokens(buffer, cx)
27766 })
27767 })
27768 .unwrap_or(false)
27769 }
27770
27771 fn inline_values(
27772 &self,
27773 buffer_handle: Entity<Buffer>,
27774 range: Range<text::Anchor>,
27775 cx: &mut App,
27776 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27777 self.update(cx, |project, cx| {
27778 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27779
27780 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27781 })
27782 .ok()
27783 .flatten()
27784 }
27785
27786 fn applicable_inlay_chunks(
27787 &self,
27788 buffer: &Entity<Buffer>,
27789 ranges: &[Range<text::Anchor>],
27790 cx: &mut App,
27791 ) -> Vec<Range<BufferRow>> {
27792 self.update(cx, |project, cx| {
27793 project.lsp_store().update(cx, |lsp_store, cx| {
27794 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27795 })
27796 })
27797 .unwrap_or_default()
27798 }
27799
27800 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27801 self.update(cx, |project, cx| {
27802 project.lsp_store().update(cx, |lsp_store, _| {
27803 lsp_store.invalidate_inlay_hints(for_buffers)
27804 })
27805 })
27806 .ok();
27807 }
27808
27809 fn inlay_hints(
27810 &self,
27811 invalidate: InvalidationStrategy,
27812 buffer: Entity<Buffer>,
27813 ranges: Vec<Range<text::Anchor>>,
27814 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27815 cx: &mut App,
27816 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27817 self.update(cx, |project, cx| {
27818 project.lsp_store().update(cx, |lsp_store, cx| {
27819 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27820 })
27821 })
27822 .ok()
27823 }
27824
27825 fn semantic_tokens(
27826 &self,
27827 buffer: Entity<Buffer>,
27828 refresh: Option<RefreshForServer>,
27829 cx: &mut App,
27830 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27831 self.update(cx, |this, cx| {
27832 this.lsp_store().update(cx, |lsp_store, cx| {
27833 lsp_store.semantic_tokens(buffer, refresh, cx)
27834 })
27835 })
27836 .ok()
27837 }
27838
27839 fn range_for_rename(
27840 &self,
27841 buffer: &Entity<Buffer>,
27842 position: text::Anchor,
27843 cx: &mut App,
27844 ) -> Task<Result<Option<Range<text::Anchor>>>> {
27845 let Some(this) = self.upgrade() else {
27846 return Task::ready(Ok(None));
27847 };
27848
27849 this.update(cx, |project, cx| {
27850 let buffer = buffer.clone();
27851 let task = project.prepare_rename(buffer.clone(), position, cx);
27852 cx.spawn(async move |_, cx| {
27853 Ok(match task.await? {
27854 PrepareRenameResponse::Success(range) => Some(range),
27855 PrepareRenameResponse::InvalidPosition => None,
27856 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27857 // Fallback on using TreeSitter info to determine identifier range
27858 buffer.read_with(cx, |buffer, _| {
27859 let snapshot = buffer.snapshot();
27860 let (range, kind) = snapshot.surrounding_word(position, None);
27861 if kind != Some(CharKind::Word) {
27862 return None;
27863 }
27864 Some(
27865 snapshot.anchor_before(range.start)
27866 ..snapshot.anchor_after(range.end),
27867 )
27868 })
27869 }
27870 })
27871 })
27872 })
27873 }
27874
27875 fn perform_rename(
27876 &self,
27877 buffer: &Entity<Buffer>,
27878 position: text::Anchor,
27879 new_name: String,
27880 cx: &mut App,
27881 ) -> Option<Task<Result<ProjectTransaction>>> {
27882 self.update(cx, |project, cx| {
27883 project.perform_rename(buffer.clone(), position, new_name, cx)
27884 })
27885 .ok()
27886 }
27887}
27888
27889fn consume_contiguous_rows(
27890 contiguous_row_selections: &mut Vec<Selection<Point>>,
27891 selection: &Selection<Point>,
27892 display_map: &DisplaySnapshot,
27893 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27894) -> (MultiBufferRow, MultiBufferRow) {
27895 contiguous_row_selections.push(selection.clone());
27896 let start_row = starting_row(selection, display_map);
27897 let mut end_row = ending_row(selection, display_map);
27898
27899 while let Some(next_selection) = selections.peek() {
27900 if next_selection.start.row <= end_row.0 {
27901 end_row = ending_row(next_selection, display_map);
27902 contiguous_row_selections.push(selections.next().unwrap().clone());
27903 } else {
27904 break;
27905 }
27906 }
27907 (start_row, end_row)
27908}
27909
27910fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27911 if selection.start.column > 0 {
27912 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27913 } else {
27914 MultiBufferRow(selection.start.row)
27915 }
27916}
27917
27918fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27919 if next_selection.end.column > 0 || next_selection.is_empty() {
27920 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27921 } else {
27922 MultiBufferRow(next_selection.end.row)
27923 }
27924}
27925
27926impl EditorSnapshot {
27927 pub fn remote_selections_in_range<'a>(
27928 &'a self,
27929 range: &'a Range<Anchor>,
27930 collaboration_hub: &dyn CollaborationHub,
27931 cx: &'a App,
27932 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27933 let participant_names = collaboration_hub.user_names(cx);
27934 let participant_indices = collaboration_hub.user_participant_indices(cx);
27935 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27936 let collaborators_by_replica_id = collaborators_by_peer_id
27937 .values()
27938 .map(|collaborator| (collaborator.replica_id, collaborator))
27939 .collect::<HashMap<_, _>>();
27940 self.buffer_snapshot()
27941 .selections_in_range(range, false)
27942 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27943 if replica_id == ReplicaId::AGENT {
27944 Some(RemoteSelection {
27945 replica_id,
27946 selection,
27947 cursor_shape,
27948 line_mode,
27949 collaborator_id: CollaboratorId::Agent,
27950 user_name: Some("Agent".into()),
27951 color: cx.theme().players().agent(),
27952 })
27953 } else {
27954 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27955 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27956 let user_name = participant_names.get(&collaborator.user_id).cloned();
27957 Some(RemoteSelection {
27958 replica_id,
27959 selection,
27960 cursor_shape,
27961 line_mode,
27962 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27963 user_name,
27964 color: if let Some(index) = participant_index {
27965 cx.theme().players().color_for_participant(index.0)
27966 } else {
27967 cx.theme().players().absent()
27968 },
27969 })
27970 }
27971 })
27972 }
27973
27974 pub fn hunks_for_ranges(
27975 &self,
27976 ranges: impl IntoIterator<Item = Range<Point>>,
27977 ) -> Vec<MultiBufferDiffHunk> {
27978 let mut hunks = Vec::new();
27979 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27980 HashMap::default();
27981 for query_range in ranges {
27982 let query_rows =
27983 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27984 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27985 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27986 ) {
27987 // Include deleted hunks that are adjacent to the query range, because
27988 // otherwise they would be missed.
27989 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27990 if hunk.status().is_deleted() {
27991 intersects_range |= hunk.row_range.start == query_rows.end;
27992 intersects_range |= hunk.row_range.end == query_rows.start;
27993 }
27994 if intersects_range {
27995 if !processed_buffer_rows
27996 .entry(hunk.buffer_id)
27997 .or_default()
27998 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27999 {
28000 continue;
28001 }
28002 hunks.push(hunk);
28003 }
28004 }
28005 }
28006
28007 hunks
28008 }
28009
28010 fn display_diff_hunks_for_rows<'a>(
28011 &'a self,
28012 display_rows: Range<DisplayRow>,
28013 folded_buffers: &'a HashSet<BufferId>,
28014 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
28015 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
28016 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
28017
28018 self.buffer_snapshot()
28019 .diff_hunks_in_range(buffer_start..buffer_end)
28020 .filter_map(|hunk| {
28021 if folded_buffers.contains(&hunk.buffer_id)
28022 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
28023 {
28024 return None;
28025 }
28026
28027 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
28028 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
28029 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
28030 let line_len = self.buffer_snapshot().line_len(last_row);
28031 Point::new(last_row.0, line_len)
28032 } else {
28033 Point::new(hunk.row_range.end.0, 0)
28034 };
28035
28036 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
28037 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
28038
28039 let display_hunk = if hunk_display_start.column() != 0 {
28040 DisplayDiffHunk::Folded {
28041 display_row: hunk_display_start.row(),
28042 }
28043 } else {
28044 let mut end_row = hunk_display_end.row();
28045 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
28046 end_row.0 += 1;
28047 }
28048 let is_created_file = hunk.is_created_file();
28049 let multi_buffer_range = hunk.multi_buffer_range.clone();
28050
28051 DisplayDiffHunk::Unfolded {
28052 status: hunk.status(),
28053 diff_base_byte_range: hunk.diff_base_byte_range.start.0
28054 ..hunk.diff_base_byte_range.end.0,
28055 word_diffs: hunk.word_diffs,
28056 display_row_range: hunk_display_start.row()..end_row,
28057 multi_buffer_range,
28058 is_created_file,
28059 }
28060 };
28061
28062 Some(display_hunk)
28063 })
28064 }
28065
28066 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
28067 self.display_snapshot
28068 .buffer_snapshot()
28069 .language_at(position)
28070 }
28071
28072 pub fn is_focused(&self) -> bool {
28073 self.is_focused
28074 }
28075
28076 pub fn placeholder_text(&self) -> Option<String> {
28077 self.placeholder_display_snapshot
28078 .as_ref()
28079 .map(|display_map| display_map.text())
28080 }
28081
28082 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
28083 self.scroll_anchor.scroll_position(&self.display_snapshot)
28084 }
28085
28086 pub fn gutter_dimensions(
28087 &self,
28088 font_id: FontId,
28089 font_size: Pixels,
28090 style: &EditorStyle,
28091 window: &mut Window,
28092 cx: &App,
28093 ) -> GutterDimensions {
28094 if self.show_gutter
28095 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
28096 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
28097 {
28098 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
28099 matches!(
28100 ProjectSettings::get_global(cx).git.git_gutter,
28101 GitGutterSetting::TrackedFiles
28102 )
28103 });
28104 let gutter_settings = EditorSettings::get_global(cx).gutter;
28105 let show_line_numbers = self
28106 .show_line_numbers
28107 .unwrap_or(gutter_settings.line_numbers);
28108 let line_gutter_width = if show_line_numbers {
28109 // Avoid flicker-like gutter resizes when the line number gains another digit by
28110 // only resizing the gutter on files with > 10**min_line_number_digits lines.
28111 let min_width_for_number_on_gutter =
28112 ch_advance * gutter_settings.min_line_number_digits as f32;
28113 self.max_line_number_width(style, window)
28114 .max(min_width_for_number_on_gutter)
28115 } else {
28116 0.0.into()
28117 };
28118
28119 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
28120 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
28121
28122 let git_blame_entries_width =
28123 self.git_blame_gutter_max_author_length
28124 .map(|max_author_length| {
28125 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
28126 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
28127
28128 /// The number of characters to dedicate to gaps and margins.
28129 const SPACING_WIDTH: usize = 4;
28130
28131 let max_char_count = max_author_length.min(renderer.max_author_length())
28132 + ::git::SHORT_SHA_LENGTH
28133 + MAX_RELATIVE_TIMESTAMP.len()
28134 + SPACING_WIDTH;
28135
28136 ch_advance * max_char_count
28137 });
28138
28139 let is_singleton = self.buffer_snapshot().is_singleton();
28140
28141 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
28142 left_padding += if !is_singleton {
28143 ch_width * 4.0
28144 } else if show_runnables || show_breakpoints {
28145 ch_width * 3.0
28146 } else if show_git_gutter && show_line_numbers {
28147 ch_width * 2.0
28148 } else if show_git_gutter || show_line_numbers {
28149 ch_width
28150 } else {
28151 px(0.)
28152 };
28153
28154 let shows_folds = is_singleton && gutter_settings.folds;
28155
28156 let right_padding = if shows_folds && show_line_numbers {
28157 ch_width * 4.0
28158 } else if shows_folds || (!is_singleton && show_line_numbers) {
28159 ch_width * 3.0
28160 } else if show_line_numbers {
28161 ch_width
28162 } else {
28163 px(0.)
28164 };
28165
28166 GutterDimensions {
28167 left_padding,
28168 right_padding,
28169 width: line_gutter_width + left_padding + right_padding,
28170 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
28171 git_blame_entries_width,
28172 }
28173 } else if self.offset_content {
28174 GutterDimensions::default_with_margin(font_id, font_size, cx)
28175 } else {
28176 GutterDimensions::default()
28177 }
28178 }
28179
28180 pub fn render_crease_toggle(
28181 &self,
28182 buffer_row: MultiBufferRow,
28183 row_contains_cursor: bool,
28184 editor: Entity<Editor>,
28185 window: &mut Window,
28186 cx: &mut App,
28187 ) -> Option<AnyElement> {
28188 let folded = self.is_line_folded(buffer_row);
28189 let mut is_foldable = false;
28190
28191 if let Some(crease) = self
28192 .crease_snapshot
28193 .query_row(buffer_row, self.buffer_snapshot())
28194 {
28195 is_foldable = true;
28196 match crease {
28197 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
28198 if let Some(render_toggle) = render_toggle {
28199 let toggle_callback =
28200 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
28201 if folded {
28202 editor.update(cx, |editor, cx| {
28203 editor.fold_at(buffer_row, window, cx)
28204 });
28205 } else {
28206 editor.update(cx, |editor, cx| {
28207 editor.unfold_at(buffer_row, window, cx)
28208 });
28209 }
28210 });
28211 return Some((render_toggle)(
28212 buffer_row,
28213 folded,
28214 toggle_callback,
28215 window,
28216 cx,
28217 ));
28218 }
28219 }
28220 }
28221 }
28222
28223 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
28224
28225 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
28226 Some(
28227 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
28228 .toggle_state(folded)
28229 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
28230 if folded {
28231 this.unfold_at(buffer_row, window, cx);
28232 } else {
28233 this.fold_at(buffer_row, window, cx);
28234 }
28235 }))
28236 .into_any_element(),
28237 )
28238 } else {
28239 None
28240 }
28241 }
28242
28243 pub fn render_crease_trailer(
28244 &self,
28245 buffer_row: MultiBufferRow,
28246 window: &mut Window,
28247 cx: &mut App,
28248 ) -> Option<AnyElement> {
28249 let folded = self.is_line_folded(buffer_row);
28250 if let Crease::Inline { render_trailer, .. } = self
28251 .crease_snapshot
28252 .query_row(buffer_row, self.buffer_snapshot())?
28253 {
28254 let render_trailer = render_trailer.as_ref()?;
28255 Some(render_trailer(buffer_row, folded, window, cx))
28256 } else {
28257 None
28258 }
28259 }
28260
28261 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
28262 let digit_count = self.widest_line_number().ilog10() + 1;
28263 column_pixels(style, digit_count as usize, window)
28264 }
28265
28266 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
28267 ///
28268 /// This is positive if `base` is before `line`.
28269 fn relative_line_delta(
28270 &self,
28271 current_selection_head: DisplayRow,
28272 first_visible_row: DisplayRow,
28273 consider_wrapped_lines: bool,
28274 ) -> i64 {
28275 let current_selection_head = current_selection_head.as_display_point().to_point(self);
28276 let first_visible_row = first_visible_row.as_display_point().to_point(self);
28277
28278 if consider_wrapped_lines {
28279 let wrap_snapshot = self.wrap_snapshot();
28280 let base_wrap_row = wrap_snapshot
28281 .make_wrap_point(current_selection_head, Bias::Left)
28282 .row();
28283 let wrap_row = wrap_snapshot
28284 .make_wrap_point(first_visible_row, Bias::Left)
28285 .row();
28286
28287 wrap_row.0 as i64 - base_wrap_row.0 as i64
28288 } else {
28289 let fold_snapshot = self.fold_snapshot();
28290 let base_fold_row = fold_snapshot
28291 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
28292 .row();
28293 let fold_row = fold_snapshot
28294 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
28295 .row();
28296
28297 fold_row as i64 - base_fold_row as i64
28298 }
28299 }
28300
28301 /// Returns the unsigned relative line number to display for each row in `rows`.
28302 ///
28303 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
28304 pub fn calculate_relative_line_numbers(
28305 &self,
28306 rows: &Range<DisplayRow>,
28307 current_selection_head: DisplayRow,
28308 count_wrapped_lines: bool,
28309 ) -> HashMap<DisplayRow, u32> {
28310 let initial_offset =
28311 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
28312
28313 self.row_infos(rows.start)
28314 .take(rows.len())
28315 .enumerate()
28316 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
28317 .filter(|(_row, row_info)| {
28318 row_info.buffer_row.is_some()
28319 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
28320 })
28321 .enumerate()
28322 .filter_map(|(i, (row, row_info))| {
28323 // We want to ensure here that the current line has absolute
28324 // numbering, even if we are in a soft-wrapped line. With the
28325 // exception that if we are in a deleted line, we should number this
28326 // relative with 0, as otherwise it would have no line number at all
28327 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
28328
28329 (relative_line_number != 0
28330 || row_info
28331 .diff_status
28332 .is_some_and(|status| status.is_deleted()))
28333 .then_some((row, relative_line_number))
28334 })
28335 .collect()
28336 }
28337}
28338
28339pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28340 let font_size = style.text.font_size.to_pixels(window.rem_size());
28341 let layout = window.text_system().shape_line(
28342 SharedString::from(" ".repeat(column)),
28343 font_size,
28344 &[TextRun {
28345 len: column,
28346 font: style.text.font(),
28347 color: Hsla::default(),
28348 ..Default::default()
28349 }],
28350 None,
28351 );
28352
28353 layout.width
28354}
28355
28356impl Deref for EditorSnapshot {
28357 type Target = DisplaySnapshot;
28358
28359 fn deref(&self) -> &Self::Target {
28360 &self.display_snapshot
28361 }
28362}
28363
28364#[derive(Clone, Debug, PartialEq, Eq)]
28365pub enum EditorEvent {
28366 /// Emitted when the stored review comments change (added, removed, or updated).
28367 ReviewCommentsChanged {
28368 /// The new total count of review comments.
28369 total_count: usize,
28370 },
28371 InputIgnored {
28372 text: Arc<str>,
28373 },
28374 InputHandled {
28375 utf16_range_to_replace: Option<Range<isize>>,
28376 text: Arc<str>,
28377 },
28378 BufferRangesUpdated {
28379 buffer: Entity<Buffer>,
28380 path_key: PathKey,
28381 ranges: Vec<ExcerptRange<text::Anchor>>,
28382 },
28383 BuffersRemoved {
28384 removed_buffer_ids: Vec<BufferId>,
28385 },
28386 BuffersEdited {
28387 buffer_ids: Vec<BufferId>,
28388 },
28389 BufferFoldToggled {
28390 ids: Vec<BufferId>,
28391 folded: bool,
28392 },
28393 ExpandExcerptsRequested {
28394 excerpt_anchors: Vec<Anchor>,
28395 lines: u32,
28396 direction: ExpandExcerptDirection,
28397 },
28398 StageOrUnstageRequested {
28399 stage: bool,
28400 hunks: Vec<MultiBufferDiffHunk>,
28401 },
28402 OpenExcerptsRequested {
28403 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28404 split: bool,
28405 },
28406 RestoreRequested {
28407 hunks: Vec<MultiBufferDiffHunk>,
28408 },
28409 BufferEdited,
28410 Edited {
28411 transaction_id: clock::Lamport,
28412 },
28413 Reparsed(BufferId),
28414 Focused,
28415 FocusedIn,
28416 Blurred,
28417 DirtyChanged,
28418 Saved,
28419 TitleChanged,
28420 SelectionsChanged {
28421 local: bool,
28422 },
28423 ScrollPositionChanged {
28424 local: bool,
28425 autoscroll: bool,
28426 },
28427 TransactionUndone {
28428 transaction_id: clock::Lamport,
28429 },
28430 TransactionBegun {
28431 transaction_id: clock::Lamport,
28432 },
28433 CursorShapeChanged,
28434 BreadcrumbsChanged,
28435 OutlineSymbolsChanged,
28436 PushedToNavHistory {
28437 anchor: Anchor,
28438 is_deactivate: bool,
28439 },
28440}
28441
28442impl EventEmitter<EditorEvent> for Editor {}
28443
28444impl Focusable for Editor {
28445 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28446 self.focus_handle.clone()
28447 }
28448}
28449
28450impl Render for Editor {
28451 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28452 EditorElement::new(&cx.entity(), self.create_style(cx))
28453 }
28454}
28455
28456impl EntityInputHandler for Editor {
28457 fn text_for_range(
28458 &mut self,
28459 range_utf16: Range<usize>,
28460 adjusted_range: &mut Option<Range<usize>>,
28461 _: &mut Window,
28462 cx: &mut Context<Self>,
28463 ) -> Option<String> {
28464 let snapshot = self.buffer.read(cx).read(cx);
28465 let start = snapshot.clip_offset_utf16(
28466 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28467 Bias::Left,
28468 );
28469 let end = snapshot.clip_offset_utf16(
28470 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28471 Bias::Right,
28472 );
28473 if (start.0.0..end.0.0) != range_utf16 {
28474 adjusted_range.replace(start.0.0..end.0.0);
28475 }
28476 Some(snapshot.text_for_range(start..end).collect())
28477 }
28478
28479 fn selected_text_range(
28480 &mut self,
28481 ignore_disabled_input: bool,
28482 _: &mut Window,
28483 cx: &mut Context<Self>,
28484 ) -> Option<UTF16Selection> {
28485 // Prevent the IME menu from appearing when holding down an alphabetic key
28486 // while input is disabled.
28487 if !ignore_disabled_input && !self.input_enabled {
28488 return None;
28489 }
28490
28491 let selection = self
28492 .selections
28493 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28494 let range = selection.range();
28495
28496 Some(UTF16Selection {
28497 range: range.start.0.0..range.end.0.0,
28498 reversed: selection.reversed,
28499 })
28500 }
28501
28502 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28503 let snapshot = self.buffer.read(cx).read(cx);
28504 let range = self
28505 .text_highlights(HighlightKey::InputComposition, cx)?
28506 .1
28507 .first()?;
28508 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28509 }
28510
28511 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28512 self.clear_highlights(HighlightKey::InputComposition, cx);
28513 self.ime_transaction.take();
28514 }
28515
28516 fn replace_text_in_range(
28517 &mut self,
28518 range_utf16: Option<Range<usize>>,
28519 text: &str,
28520 window: &mut Window,
28521 cx: &mut Context<Self>,
28522 ) {
28523 if !self.input_enabled {
28524 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28525 return;
28526 }
28527
28528 self.transact(window, cx, |this, window, cx| {
28529 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28530 if let Some(marked_ranges) = this.marked_text_ranges(cx) {
28531 // During IME composition, macOS reports the replacement range
28532 // relative to the first marked region (the only one visible via
28533 // marked_text_range). The correct targets for replacement are the
28534 // marked ranges themselves — one per cursor — so use them directly.
28535 Some(marked_ranges)
28536 } else if range_utf16.start == range_utf16.end {
28537 // An empty replacement range means "insert at cursor" with no text
28538 // to replace. macOS reports the cursor position from its own
28539 // (single-cursor) view of the buffer, which diverges from our actual
28540 // cursor positions after multi-cursor edits have shifted offsets.
28541 // Treating this as range_utf16=None lets each cursor insert in place.
28542 None
28543 } else {
28544 // Outside of IME composition (e.g. Accessibility Keyboard word
28545 // completion), the range is an absolute document offset for the
28546 // newest cursor. Fan it out to all cursors via
28547 // selection_replacement_ranges, which applies the delta relative
28548 // to the newest selection to every cursor.
28549 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28550 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28551 Some(this.selection_replacement_ranges(range_utf16, cx))
28552 }
28553 } else {
28554 this.marked_text_ranges(cx)
28555 };
28556
28557 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28558 let newest_selection_id = this.selections.newest_anchor().id;
28559 this.selections
28560 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28561 .iter()
28562 .zip(ranges_to_replace.iter())
28563 .find_map(|(selection, range)| {
28564 if selection.id == newest_selection_id {
28565 Some(
28566 (range.start.0.0 as isize - selection.head().0.0 as isize)
28567 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28568 )
28569 } else {
28570 None
28571 }
28572 })
28573 });
28574
28575 cx.emit(EditorEvent::InputHandled {
28576 utf16_range_to_replace: range_to_replace,
28577 text: text.into(),
28578 });
28579
28580 if let Some(new_selected_ranges) = new_selected_ranges {
28581 // Only backspace if at least one range covers actual text. When all
28582 // ranges are empty (e.g. a trailing-space insertion from Accessibility
28583 // Keyboard sends replacementRange=cursor..cursor), backspace would
28584 // incorrectly delete the character just before the cursor.
28585 let should_backspace = new_selected_ranges.iter().any(|r| r.start != r.end);
28586 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28587 selections.select_ranges(new_selected_ranges)
28588 });
28589 if should_backspace {
28590 this.backspace(&Default::default(), window, cx);
28591 }
28592 }
28593
28594 this.handle_input(text, window, cx);
28595 });
28596
28597 if let Some(transaction) = self.ime_transaction {
28598 self.buffer.update(cx, |buffer, cx| {
28599 buffer.group_until_transaction(transaction, cx);
28600 });
28601 }
28602
28603 self.unmark_text(window, cx);
28604 }
28605
28606 fn replace_and_mark_text_in_range(
28607 &mut self,
28608 range_utf16: Option<Range<usize>>,
28609 text: &str,
28610 new_selected_range_utf16: Option<Range<usize>>,
28611 window: &mut Window,
28612 cx: &mut Context<Self>,
28613 ) {
28614 if !self.input_enabled {
28615 return;
28616 }
28617
28618 let transaction = self.transact(window, cx, |this, window, cx| {
28619 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28620 let snapshot = this.buffer.read(cx).read(cx);
28621 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28622 for marked_range in &mut marked_ranges {
28623 marked_range.end = marked_range.start + relative_range_utf16.end;
28624 marked_range.start += relative_range_utf16.start;
28625 marked_range.start =
28626 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28627 marked_range.end =
28628 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28629 }
28630 }
28631 Some(marked_ranges)
28632 } else if let Some(range_utf16) = range_utf16 {
28633 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28634 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28635 Some(this.selection_replacement_ranges(range_utf16, cx))
28636 } else {
28637 None
28638 };
28639
28640 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28641 let newest_selection_id = this.selections.newest_anchor().id;
28642 this.selections
28643 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28644 .iter()
28645 .zip(ranges_to_replace.iter())
28646 .find_map(|(selection, range)| {
28647 if selection.id == newest_selection_id {
28648 Some(
28649 (range.start.0.0 as isize - selection.head().0.0 as isize)
28650 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28651 )
28652 } else {
28653 None
28654 }
28655 })
28656 });
28657
28658 cx.emit(EditorEvent::InputHandled {
28659 utf16_range_to_replace: range_to_replace,
28660 text: text.into(),
28661 });
28662
28663 if let Some(ranges) = ranges_to_replace {
28664 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28665 s.select_ranges(ranges)
28666 });
28667 }
28668
28669 let marked_ranges = {
28670 let snapshot = this.buffer.read(cx).read(cx);
28671 this.selections
28672 .disjoint_anchors_arc()
28673 .iter()
28674 .map(|selection| {
28675 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28676 })
28677 .collect::<Vec<_>>()
28678 };
28679
28680 if text.is_empty() {
28681 this.unmark_text(window, cx);
28682 } else {
28683 this.highlight_text(
28684 HighlightKey::InputComposition,
28685 marked_ranges.clone(),
28686 HighlightStyle {
28687 underline: Some(UnderlineStyle {
28688 thickness: px(1.),
28689 color: None,
28690 wavy: false,
28691 }),
28692 ..Default::default()
28693 },
28694 cx,
28695 );
28696 }
28697
28698 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28699 let use_autoclose = this.use_autoclose;
28700 let use_auto_surround = this.use_auto_surround;
28701 this.set_use_autoclose(false);
28702 this.set_use_auto_surround(false);
28703 this.handle_input(text, window, cx);
28704 this.set_use_autoclose(use_autoclose);
28705 this.set_use_auto_surround(use_auto_surround);
28706
28707 if let Some(new_selected_range) = new_selected_range_utf16 {
28708 let snapshot = this.buffer.read(cx).read(cx);
28709 let new_selected_ranges = marked_ranges
28710 .into_iter()
28711 .map(|marked_range| {
28712 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28713 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28714 insertion_start.0 + new_selected_range.start,
28715 ));
28716 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28717 insertion_start.0 + new_selected_range.end,
28718 ));
28719 snapshot.clip_offset_utf16(new_start, Bias::Left)
28720 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28721 })
28722 .collect::<Vec<_>>();
28723
28724 drop(snapshot);
28725 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28726 selections.select_ranges(new_selected_ranges)
28727 });
28728 }
28729 });
28730
28731 self.ime_transaction = self.ime_transaction.or(transaction);
28732 if let Some(transaction) = self.ime_transaction {
28733 self.buffer.update(cx, |buffer, cx| {
28734 buffer.group_until_transaction(transaction, cx);
28735 });
28736 }
28737
28738 if self
28739 .text_highlights(HighlightKey::InputComposition, cx)
28740 .is_none()
28741 {
28742 self.ime_transaction.take();
28743 }
28744 }
28745
28746 fn bounds_for_range(
28747 &mut self,
28748 range_utf16: Range<usize>,
28749 element_bounds: gpui::Bounds<Pixels>,
28750 window: &mut Window,
28751 cx: &mut Context<Self>,
28752 ) -> Option<gpui::Bounds<Pixels>> {
28753 let text_layout_details = self.text_layout_details(window, cx);
28754 let CharacterDimensions {
28755 em_width,
28756 em_advance,
28757 line_height,
28758 } = self.character_dimensions(window, cx);
28759
28760 let snapshot = self.snapshot(window, cx);
28761 let scroll_position = snapshot.scroll_position();
28762 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28763
28764 let start =
28765 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28766 let x = Pixels::from(
28767 ScrollOffset::from(
28768 snapshot.x_for_display_point(start, &text_layout_details)
28769 + self.gutter_dimensions.full_width(),
28770 ) - scroll_left,
28771 );
28772 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28773
28774 Some(Bounds {
28775 origin: element_bounds.origin + point(x, y),
28776 size: size(em_width, line_height),
28777 })
28778 }
28779
28780 fn character_index_for_point(
28781 &mut self,
28782 point: gpui::Point<Pixels>,
28783 _window: &mut Window,
28784 _cx: &mut Context<Self>,
28785 ) -> Option<usize> {
28786 let position_map = self.last_position_map.as_ref()?;
28787 if !position_map.text_hitbox.contains(&point) {
28788 return None;
28789 }
28790 let display_point = position_map.point_for_position(point).previous_valid;
28791 let anchor = position_map
28792 .snapshot
28793 .display_point_to_anchor(display_point, Bias::Left);
28794 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28795 Some(utf16_offset.0.0)
28796 }
28797
28798 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28799 self.expects_character_input
28800 }
28801}
28802
28803trait SelectionExt {
28804 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28805 fn spanned_rows(
28806 &self,
28807 include_end_if_at_line_start: bool,
28808 map: &DisplaySnapshot,
28809 ) -> Range<MultiBufferRow>;
28810}
28811
28812impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28813 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28814 let start = self
28815 .start
28816 .to_point(map.buffer_snapshot())
28817 .to_display_point(map);
28818 let end = self
28819 .end
28820 .to_point(map.buffer_snapshot())
28821 .to_display_point(map);
28822 if self.reversed {
28823 end..start
28824 } else {
28825 start..end
28826 }
28827 }
28828
28829 fn spanned_rows(
28830 &self,
28831 include_end_if_at_line_start: bool,
28832 map: &DisplaySnapshot,
28833 ) -> Range<MultiBufferRow> {
28834 let start = self.start.to_point(map.buffer_snapshot());
28835 let mut end = self.end.to_point(map.buffer_snapshot());
28836 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28837 end.row -= 1;
28838 }
28839
28840 let buffer_start = map.prev_line_boundary(start).0;
28841 let buffer_end = map.next_line_boundary(end).0;
28842 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28843 }
28844}
28845
28846impl<T: InvalidationRegion> InvalidationStack<T> {
28847 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28848 where
28849 S: Clone + ToOffset,
28850 {
28851 while let Some(region) = self.last() {
28852 let all_selections_inside_invalidation_ranges =
28853 if selections.len() == region.ranges().len() {
28854 selections
28855 .iter()
28856 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28857 .all(|(selection, invalidation_range)| {
28858 let head = selection.head().to_offset(buffer);
28859 invalidation_range.start <= head && invalidation_range.end >= head
28860 })
28861 } else {
28862 false
28863 };
28864
28865 if all_selections_inside_invalidation_ranges {
28866 break;
28867 } else {
28868 self.pop();
28869 }
28870 }
28871 }
28872}
28873
28874#[derive(Clone)]
28875struct ErasedEditorImpl(Entity<Editor>);
28876
28877impl ui_input::ErasedEditor for ErasedEditorImpl {
28878 fn text(&self, cx: &App) -> String {
28879 self.0.read(cx).text(cx)
28880 }
28881
28882 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28883 self.0.update(cx, |this, cx| {
28884 this.set_text(text, window, cx);
28885 })
28886 }
28887
28888 fn clear(&self, window: &mut Window, cx: &mut App) {
28889 self.0.update(cx, |this, cx| this.clear(window, cx));
28890 }
28891
28892 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28893 self.0.update(cx, |this, cx| {
28894 this.set_placeholder_text(text, window, cx);
28895 });
28896 }
28897
28898 fn focus_handle(&self, cx: &App) -> FocusHandle {
28899 self.0.read(cx).focus_handle(cx)
28900 }
28901
28902 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28903 let settings = ThemeSettings::get_global(cx);
28904 let theme_color = cx.theme().colors();
28905
28906 let text_style = TextStyle {
28907 font_family: settings.ui_font.family.clone(),
28908 font_features: settings.ui_font.features.clone(),
28909 font_size: rems(0.875).into(),
28910 font_weight: settings.ui_font.weight,
28911 font_style: FontStyle::Normal,
28912 line_height: relative(1.2),
28913 color: theme_color.text,
28914 ..Default::default()
28915 };
28916 let editor_style = EditorStyle {
28917 background: theme_color.ghost_element_background,
28918 local_player: cx.theme().players().local(),
28919 syntax: cx.theme().syntax().clone(),
28920 text: text_style,
28921 ..Default::default()
28922 };
28923 EditorElement::new(&self.0, editor_style).into_any()
28924 }
28925
28926 fn as_any(&self) -> &dyn Any {
28927 &self.0
28928 }
28929
28930 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28931 self.0.update(cx, |editor, cx| {
28932 let editor_offset = editor.buffer().read(cx).len(cx);
28933 editor.change_selections(
28934 SelectionEffects::scroll(Autoscroll::Next),
28935 window,
28936 cx,
28937 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28938 );
28939 });
28940 }
28941
28942 fn subscribe(
28943 &self,
28944 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28945 window: &mut Window,
28946 cx: &mut App,
28947 ) -> Subscription {
28948 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28949 let event = match event {
28950 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28951 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28952 _ => return,
28953 };
28954 (callback)(event, window, cx);
28955 })
28956 }
28957
28958 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28959 self.0.update(cx, |editor, cx| {
28960 editor.set_masked(masked, cx);
28961 });
28962 }
28963}
28964impl<T> Default for InvalidationStack<T> {
28965 fn default() -> Self {
28966 Self(Default::default())
28967 }
28968}
28969
28970impl<T> Deref for InvalidationStack<T> {
28971 type Target = Vec<T>;
28972
28973 fn deref(&self) -> &Self::Target {
28974 &self.0
28975 }
28976}
28977
28978impl<T> DerefMut for InvalidationStack<T> {
28979 fn deref_mut(&mut self) -> &mut Self::Target {
28980 &mut self.0
28981 }
28982}
28983
28984impl InvalidationRegion for SnippetState {
28985 fn ranges(&self) -> &[Range<Anchor>] {
28986 &self.ranges[self.active_index]
28987 }
28988}
28989
28990fn edit_prediction_edit_text(
28991 current_snapshot: &BufferSnapshot,
28992 edits: &[(Range<Anchor>, impl AsRef<str>)],
28993 edit_preview: &EditPreview,
28994 include_deletions: bool,
28995 multibuffer_snapshot: &MultiBufferSnapshot,
28996 cx: &App,
28997) -> HighlightedText {
28998 let edits = edits
28999 .iter()
29000 .filter_map(|(anchor, text)| {
29001 Some((
29002 multibuffer_snapshot
29003 .anchor_range_to_buffer_anchor_range(anchor.clone())?
29004 .1,
29005 text,
29006 ))
29007 })
29008 .collect::<Vec<_>>();
29009
29010 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
29011}
29012
29013fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
29014 // Fallback for providers that don't provide edit_preview (like Copilot)
29015 // Just show the raw edit text with basic styling
29016 let mut text = String::new();
29017 let mut highlights = Vec::new();
29018
29019 let insertion_highlight_style = HighlightStyle {
29020 color: Some(cx.theme().colors().text),
29021 ..Default::default()
29022 };
29023
29024 for (_, edit_text) in edits {
29025 let start_offset = text.len();
29026 text.push_str(edit_text);
29027 let end_offset = text.len();
29028
29029 if start_offset < end_offset {
29030 highlights.push((start_offset..end_offset, insertion_highlight_style));
29031 }
29032 }
29033
29034 HighlightedText {
29035 text: text.into(),
29036 highlights,
29037 }
29038}
29039
29040pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
29041 match severity {
29042 lsp::DiagnosticSeverity::ERROR => colors.error,
29043 lsp::DiagnosticSeverity::WARNING => colors.warning,
29044 lsp::DiagnosticSeverity::INFORMATION => colors.info,
29045 lsp::DiagnosticSeverity::HINT => colors.info,
29046 _ => colors.ignored,
29047 }
29048}
29049
29050pub fn styled_runs_for_code_label<'a>(
29051 label: &'a CodeLabel,
29052 syntax_theme: &'a theme::SyntaxTheme,
29053 local_player: &'a theme::PlayerColor,
29054) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
29055 let fade_out = HighlightStyle {
29056 fade_out: Some(0.35),
29057 ..Default::default()
29058 };
29059
29060 if label.runs.is_empty() {
29061 let desc_start = label.filter_range.end;
29062 let fade_run =
29063 (desc_start < label.text.len()).then(|| (desc_start..label.text.len(), fade_out));
29064 return Either::Left(fade_run.into_iter());
29065 }
29066
29067 let mut prev_end = label.filter_range.end;
29068 Either::Right(
29069 label
29070 .runs
29071 .iter()
29072 .enumerate()
29073 .flat_map(move |(ix, (range, highlight_id))| {
29074 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
29075 HighlightStyle {
29076 color: Some(local_player.cursor),
29077 ..Default::default()
29078 }
29079 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
29080 HighlightStyle {
29081 background_color: Some(local_player.selection),
29082 ..Default::default()
29083 }
29084 } else if let Some(style) = syntax_theme.get(*highlight_id).cloned() {
29085 style
29086 } else {
29087 return Default::default();
29088 };
29089
29090 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
29091 let muted_style = style.highlight(fade_out);
29092 if range.start >= label.filter_range.end {
29093 if range.start > prev_end {
29094 runs.push((prev_end..range.start, fade_out));
29095 }
29096 runs.push((range.clone(), muted_style));
29097 } else if range.end <= label.filter_range.end {
29098 runs.push((range.clone(), style));
29099 } else {
29100 runs.push((range.start..label.filter_range.end, style));
29101 runs.push((label.filter_range.end..range.end, muted_style));
29102 }
29103 prev_end = cmp::max(prev_end, range.end);
29104
29105 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
29106 runs.push((prev_end..label.text.len(), fade_out));
29107 }
29108
29109 runs
29110 }),
29111 )
29112}
29113
29114pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
29115 let mut prev_index = 0;
29116 let mut prev_codepoint: Option<char> = None;
29117 text.char_indices()
29118 .chain([(text.len(), '\0')])
29119 .filter_map(move |(index, codepoint)| {
29120 let prev_codepoint = prev_codepoint.replace(codepoint)?;
29121 let is_boundary = index == text.len()
29122 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
29123 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
29124 if is_boundary {
29125 let chunk = &text[prev_index..index];
29126 prev_index = index;
29127 Some(chunk)
29128 } else {
29129 None
29130 }
29131 })
29132}
29133
29134/// Given a string of text immediately before the cursor, iterates over possible
29135/// strings a snippet could match to. More precisely: returns an iterator over
29136/// suffixes of `text` created by splitting at word boundaries (before & after
29137/// every non-word character).
29138///
29139/// Shorter suffixes are returned first.
29140pub(crate) fn snippet_candidate_suffixes<'a>(
29141 text: &'a str,
29142 is_word_char: &'a dyn Fn(char) -> bool,
29143) -> impl std::iter::Iterator<Item = &'a str> + 'a {
29144 let mut prev_index = text.len();
29145 let mut prev_codepoint = None;
29146 text.char_indices()
29147 .rev()
29148 .chain([(0, '\0')])
29149 .filter_map(move |(index, codepoint)| {
29150 let prev_index = std::mem::replace(&mut prev_index, index);
29151 let prev_codepoint = prev_codepoint.replace(codepoint)?;
29152 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
29153 None
29154 } else {
29155 let chunk = &text[prev_index..]; // go to end of string
29156 Some(chunk)
29157 }
29158 })
29159}
29160
29161pub trait RangeToAnchorExt: Sized {
29162 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
29163
29164 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
29165 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
29166 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
29167 }
29168}
29169
29170impl<T: ToOffset> RangeToAnchorExt for Range<T> {
29171 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
29172 let start_offset = self.start.to_offset(snapshot);
29173 let end_offset = self.end.to_offset(snapshot);
29174 if start_offset == end_offset {
29175 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
29176 } else {
29177 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
29178 }
29179 }
29180}
29181
29182pub trait RowExt {
29183 fn as_f64(&self) -> f64;
29184
29185 fn next_row(&self) -> Self;
29186
29187 fn previous_row(&self) -> Self;
29188
29189 fn minus(&self, other: Self) -> u32;
29190}
29191
29192impl RowExt for DisplayRow {
29193 fn as_f64(&self) -> f64 {
29194 self.0 as _
29195 }
29196
29197 fn next_row(&self) -> Self {
29198 Self(self.0 + 1)
29199 }
29200
29201 fn previous_row(&self) -> Self {
29202 Self(self.0.saturating_sub(1))
29203 }
29204
29205 fn minus(&self, other: Self) -> u32 {
29206 self.0 - other.0
29207 }
29208}
29209
29210impl RowExt for MultiBufferRow {
29211 fn as_f64(&self) -> f64 {
29212 self.0 as _
29213 }
29214
29215 fn next_row(&self) -> Self {
29216 Self(self.0 + 1)
29217 }
29218
29219 fn previous_row(&self) -> Self {
29220 Self(self.0.saturating_sub(1))
29221 }
29222
29223 fn minus(&self, other: Self) -> u32 {
29224 self.0 - other.0
29225 }
29226}
29227
29228trait RowRangeExt {
29229 type Row;
29230
29231 fn len(&self) -> usize;
29232
29233 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
29234}
29235
29236impl RowRangeExt for Range<MultiBufferRow> {
29237 type Row = MultiBufferRow;
29238
29239 fn len(&self) -> usize {
29240 (self.end.0 - self.start.0) as usize
29241 }
29242
29243 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
29244 (self.start.0..self.end.0).map(MultiBufferRow)
29245 }
29246}
29247
29248impl RowRangeExt for Range<DisplayRow> {
29249 type Row = DisplayRow;
29250
29251 fn len(&self) -> usize {
29252 (self.end.0 - self.start.0) as usize
29253 }
29254
29255 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
29256 (self.start.0..self.end.0).map(DisplayRow)
29257 }
29258}
29259
29260/// If select range has more than one line, we
29261/// just point the cursor to range.start.
29262fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
29263 if range.start.row == range.end.row {
29264 range
29265 } else {
29266 range.start..range.start
29267 }
29268}
29269pub struct KillRing(ClipboardItem);
29270impl Global for KillRing {}
29271
29272const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
29273
29274enum BreakpointPromptEditAction {
29275 Log,
29276 Condition,
29277 HitCondition,
29278}
29279
29280struct BreakpointPromptEditor {
29281 pub(crate) prompt: Entity<Editor>,
29282 editor: WeakEntity<Editor>,
29283 breakpoint_anchor: Anchor,
29284 breakpoint: Breakpoint,
29285 edit_action: BreakpointPromptEditAction,
29286 block_ids: HashSet<CustomBlockId>,
29287 editor_margins: Arc<Mutex<EditorMargins>>,
29288 _subscriptions: Vec<Subscription>,
29289}
29290
29291impl BreakpointPromptEditor {
29292 const MAX_LINES: u8 = 4;
29293
29294 fn new(
29295 editor: WeakEntity<Editor>,
29296 breakpoint_anchor: Anchor,
29297 breakpoint: Breakpoint,
29298 edit_action: BreakpointPromptEditAction,
29299 window: &mut Window,
29300 cx: &mut Context<Self>,
29301 ) -> Self {
29302 let base_text = match edit_action {
29303 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
29304 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
29305 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
29306 }
29307 .map(|msg| msg.to_string())
29308 .unwrap_or_default();
29309
29310 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
29311 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29312
29313 let prompt = cx.new(|cx| {
29314 let mut prompt = Editor::new(
29315 EditorMode::AutoHeight {
29316 min_lines: 1,
29317 max_lines: Some(Self::MAX_LINES as usize),
29318 },
29319 buffer,
29320 None,
29321 window,
29322 cx,
29323 );
29324 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
29325 prompt.set_show_cursor_when_unfocused(false, cx);
29326 prompt.set_placeholder_text(
29327 match edit_action {
29328 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
29329 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
29330 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
29331 },
29332 window,
29333 cx,
29334 );
29335
29336 prompt
29337 });
29338
29339 Self {
29340 prompt,
29341 editor,
29342 breakpoint_anchor,
29343 breakpoint,
29344 edit_action,
29345 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
29346 block_ids: Default::default(),
29347 _subscriptions: vec![],
29348 }
29349 }
29350
29351 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
29352 self.block_ids.extend(block_ids)
29353 }
29354
29355 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
29356 if let Some(editor) = self.editor.upgrade() {
29357 let message = self
29358 .prompt
29359 .read(cx)
29360 .buffer
29361 .read(cx)
29362 .as_singleton()
29363 .expect("A multi buffer in breakpoint prompt isn't possible")
29364 .read(cx)
29365 .as_rope()
29366 .to_string();
29367
29368 editor.update(cx, |editor, cx| {
29369 editor.edit_breakpoint_at_anchor(
29370 self.breakpoint_anchor,
29371 self.breakpoint.clone(),
29372 match self.edit_action {
29373 BreakpointPromptEditAction::Log => {
29374 BreakpointEditAction::EditLogMessage(message.into())
29375 }
29376 BreakpointPromptEditAction::Condition => {
29377 BreakpointEditAction::EditCondition(message.into())
29378 }
29379 BreakpointPromptEditAction::HitCondition => {
29380 BreakpointEditAction::EditHitCondition(message.into())
29381 }
29382 },
29383 cx,
29384 );
29385
29386 editor.remove_blocks(self.block_ids.clone(), None, cx);
29387 cx.focus_self(window);
29388 });
29389 }
29390 }
29391
29392 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29393 self.editor
29394 .update(cx, |editor, cx| {
29395 editor.remove_blocks(self.block_ids.clone(), None, cx);
29396 window.focus(&editor.focus_handle, cx);
29397 })
29398 .log_err();
29399 }
29400
29401 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29402 let settings = ThemeSettings::get_global(cx);
29403 let text_style = TextStyle {
29404 color: if self.prompt.read(cx).read_only(cx) {
29405 cx.theme().colors().text_disabled
29406 } else {
29407 cx.theme().colors().text
29408 },
29409 font_family: settings.buffer_font.family.clone(),
29410 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29411 font_size: settings.buffer_font_size(cx).into(),
29412 font_weight: settings.buffer_font.weight,
29413 line_height: relative(settings.buffer_line_height.value()),
29414 ..Default::default()
29415 };
29416 EditorElement::new(
29417 &self.prompt,
29418 EditorStyle {
29419 background: cx.theme().colors().editor_background,
29420 local_player: cx.theme().players().local(),
29421 text: text_style,
29422 ..Default::default()
29423 },
29424 )
29425 }
29426
29427 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29428 let focus_handle = self.prompt.focus_handle(cx);
29429 IconButton::new("cancel", IconName::Close)
29430 .icon_color(Color::Muted)
29431 .shape(IconButtonShape::Square)
29432 .tooltip(move |_window, cx| {
29433 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29434 })
29435 .on_click(cx.listener(|this, _, window, cx| {
29436 this.cancel(&menu::Cancel, window, cx);
29437 }))
29438 }
29439
29440 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29441 let focus_handle = self.prompt.focus_handle(cx);
29442 IconButton::new("confirm", IconName::Return)
29443 .icon_color(Color::Muted)
29444 .shape(IconButtonShape::Square)
29445 .tooltip(move |_window, cx| {
29446 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29447 })
29448 .on_click(cx.listener(|this, _, window, cx| {
29449 this.confirm(&menu::Confirm, window, cx);
29450 }))
29451 }
29452}
29453
29454impl Render for BreakpointPromptEditor {
29455 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29456 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29457 let editor_margins = *self.editor_margins.lock();
29458 let gutter_dimensions = editor_margins.gutter;
29459 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29460 let right_padding = editor_margins.right + px(9.);
29461 h_flex()
29462 .key_context("Editor")
29463 .bg(cx.theme().colors().editor_background)
29464 .border_y_1()
29465 .border_color(cx.theme().status().info_border)
29466 .size_full()
29467 .py(window.line_height() / 2.5)
29468 .pr(right_padding)
29469 .on_action(cx.listener(Self::confirm))
29470 .on_action(cx.listener(Self::cancel))
29471 .child(
29472 WithRemSize::new(ui_font_size)
29473 .h_full()
29474 .w(left_gutter_width)
29475 .flex()
29476 .flex_row()
29477 .flex_shrink_0()
29478 .items_center()
29479 .justify_center()
29480 .gap_1()
29481 .child(self.render_close_button(cx)),
29482 )
29483 .child(
29484 h_flex()
29485 .w_full()
29486 .justify_between()
29487 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29488 .child(
29489 WithRemSize::new(ui_font_size)
29490 .flex()
29491 .flex_row()
29492 .items_center()
29493 .child(self.render_confirm_button(cx)),
29494 ),
29495 )
29496 }
29497}
29498
29499impl Focusable for BreakpointPromptEditor {
29500 fn focus_handle(&self, cx: &App) -> FocusHandle {
29501 self.prompt.focus_handle(cx)
29502 }
29503}
29504
29505fn all_edits_insertions_or_deletions(
29506 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29507 snapshot: &MultiBufferSnapshot,
29508) -> bool {
29509 let mut all_insertions = true;
29510 let mut all_deletions = true;
29511
29512 for (range, new_text) in edits.iter() {
29513 let range_is_empty = range.to_offset(snapshot).is_empty();
29514 let text_is_empty = new_text.is_empty();
29515
29516 if range_is_empty != text_is_empty {
29517 if range_is_empty {
29518 all_deletions = false;
29519 } else {
29520 all_insertions = false;
29521 }
29522 } else {
29523 return false;
29524 }
29525
29526 if !all_insertions && !all_deletions {
29527 return false;
29528 }
29529 }
29530 all_insertions || all_deletions
29531}
29532
29533struct MissingEditPredictionKeybindingTooltip;
29534
29535impl Render for MissingEditPredictionKeybindingTooltip {
29536 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29537 ui::tooltip_container(cx, |container, cx| {
29538 container
29539 .flex_shrink_0()
29540 .max_w_80()
29541 .min_h(rems_from_px(124.))
29542 .justify_between()
29543 .child(
29544 v_flex()
29545 .flex_1()
29546 .text_ui_sm(cx)
29547 .child(Label::new("Conflict with Accept Keybinding"))
29548 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29549 )
29550 .child(
29551 h_flex()
29552 .pb_1()
29553 .gap_1()
29554 .items_end()
29555 .w_full()
29556 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29557 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29558 }))
29559 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29560 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29561 })),
29562 )
29563 })
29564 }
29565}
29566
29567#[derive(Debug, Clone, Copy, PartialEq)]
29568pub struct LineHighlight {
29569 pub background: Background,
29570 pub border: Option<gpui::Hsla>,
29571 pub include_gutter: bool,
29572 pub type_id: Option<TypeId>,
29573}
29574
29575struct LineManipulationResult {
29576 pub new_text: String,
29577 pub line_count_before: usize,
29578 pub line_count_after: usize,
29579}
29580
29581fn render_diff_hunk_controls(
29582 row: u32,
29583 status: &DiffHunkStatus,
29584 hunk_range: Range<Anchor>,
29585 is_created_file: bool,
29586 line_height: Pixels,
29587 editor: &Entity<Editor>,
29588 _window: &mut Window,
29589 cx: &mut App,
29590) -> AnyElement {
29591 h_flex()
29592 .h(line_height)
29593 .mr_1()
29594 .gap_1()
29595 .px_0p5()
29596 .pb_1()
29597 .border_x_1()
29598 .border_b_1()
29599 .border_color(cx.theme().colors().border_variant)
29600 .rounded_b_lg()
29601 .bg(cx.theme().colors().editor_background)
29602 .gap_1()
29603 .block_mouse_except_scroll()
29604 .shadow_md()
29605 .child(if status.has_secondary_hunk() {
29606 Button::new(("stage", row as u64), "Stage")
29607 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29608 .tooltip({
29609 let focus_handle = editor.focus_handle(cx);
29610 move |_window, cx| {
29611 Tooltip::for_action_in(
29612 "Stage Hunk",
29613 &::git::ToggleStaged,
29614 &focus_handle,
29615 cx,
29616 )
29617 }
29618 })
29619 .on_click({
29620 let editor = editor.clone();
29621 move |_event, _window, cx| {
29622 editor.update(cx, |editor, cx| {
29623 editor.stage_or_unstage_diff_hunks(
29624 true,
29625 vec![hunk_range.start..hunk_range.start],
29626 cx,
29627 );
29628 });
29629 }
29630 })
29631 } else {
29632 Button::new(("unstage", row as u64), "Unstage")
29633 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29634 .tooltip({
29635 let focus_handle = editor.focus_handle(cx);
29636 move |_window, cx| {
29637 Tooltip::for_action_in(
29638 "Unstage Hunk",
29639 &::git::ToggleStaged,
29640 &focus_handle,
29641 cx,
29642 )
29643 }
29644 })
29645 .on_click({
29646 let editor = editor.clone();
29647 move |_event, _window, cx| {
29648 editor.update(cx, |editor, cx| {
29649 editor.stage_or_unstage_diff_hunks(
29650 false,
29651 vec![hunk_range.start..hunk_range.start],
29652 cx,
29653 );
29654 });
29655 }
29656 })
29657 })
29658 .child(
29659 Button::new(("restore", row as u64), "Restore")
29660 .tooltip({
29661 let focus_handle = editor.focus_handle(cx);
29662 move |_window, cx| {
29663 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29664 }
29665 })
29666 .on_click({
29667 let editor = editor.clone();
29668 move |_event, window, cx| {
29669 editor.update(cx, |editor, cx| {
29670 let snapshot = editor.snapshot(window, cx);
29671 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29672 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29673 });
29674 }
29675 })
29676 .disabled(is_created_file),
29677 )
29678 .when(
29679 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29680 |el| {
29681 el.child(
29682 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29683 .shape(IconButtonShape::Square)
29684 .icon_size(IconSize::Small)
29685 // .disabled(!has_multiple_hunks)
29686 .tooltip({
29687 let focus_handle = editor.focus_handle(cx);
29688 move |_window, cx| {
29689 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29690 }
29691 })
29692 .on_click({
29693 let editor = editor.clone();
29694 move |_event, window, cx| {
29695 editor.update(cx, |editor, cx| {
29696 let snapshot = editor.snapshot(window, cx);
29697 let position =
29698 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29699 editor.go_to_hunk_before_or_after_position(
29700 &snapshot,
29701 position,
29702 Direction::Next,
29703 true,
29704 window,
29705 cx,
29706 );
29707 editor.expand_selected_diff_hunks(cx);
29708 });
29709 }
29710 }),
29711 )
29712 .child(
29713 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29714 .shape(IconButtonShape::Square)
29715 .icon_size(IconSize::Small)
29716 // .disabled(!has_multiple_hunks)
29717 .tooltip({
29718 let focus_handle = editor.focus_handle(cx);
29719 move |_window, cx| {
29720 Tooltip::for_action_in(
29721 "Previous Hunk",
29722 &GoToPreviousHunk,
29723 &focus_handle,
29724 cx,
29725 )
29726 }
29727 })
29728 .on_click({
29729 let editor = editor.clone();
29730 move |_event, window, cx| {
29731 editor.update(cx, |editor, cx| {
29732 let snapshot = editor.snapshot(window, cx);
29733 let point =
29734 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29735 editor.go_to_hunk_before_or_after_position(
29736 &snapshot,
29737 point,
29738 Direction::Prev,
29739 true,
29740 window,
29741 cx,
29742 );
29743 editor.expand_selected_diff_hunks(cx);
29744 });
29745 }
29746 }),
29747 )
29748 },
29749 )
29750 .into_any_element()
29751}
29752
29753pub fn multibuffer_context_lines(cx: &App) -> u32 {
29754 EditorSettings::try_get(cx)
29755 .map(|settings| settings.excerpt_context_lines)
29756 .unwrap_or(2)
29757 .min(32)
29758}