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_tests;
52mod signature_help;
53#[cfg(any(test, feature = "test-support"))]
54pub mod test;
55
56pub(crate) use actions::*;
57pub use display_map::{
58 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
59 SemanticTokenHighlight,
60};
61pub use edit_prediction_types::Direction;
62pub use editor_settings::{
63 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
64 EditorSettings, EditorSettingsScrollbarProxy, HideMouseMode, ScrollBeyondLastLine,
65 ScrollbarAxes, SearchSettings, ShowMinimap, ui_scrollbar_settings_from_raw,
66};
67pub use element::{
68 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
69 render_breadcrumb_text,
70};
71pub use git::blame::BlameRenderer;
72pub use hover_popover::hover_markdown_style;
73pub use inlays::Inlay;
74pub use items::MAX_TAB_TITLE_LEN;
75pub use linked_editing_ranges::LinkedEdits;
76pub use lsp::CompletionContext;
77pub use lsp_ext::lsp_tasks;
78pub use multi_buffer::{
79 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
80 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
81 ToPoint,
82};
83pub use split::{SplittableEditor, ToggleSplitDiff};
84pub use split_editor_view::SplitEditorView;
85pub use text::Bias;
86
87use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
88use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
89use anyhow::{Context as _, Result, anyhow, bail};
90use blink_manager::BlinkManager;
91use buffer_diff::DiffHunkStatus;
92use client::{Collaborator, ParticipantIndex, parse_zed_link};
93use clock::ReplicaId;
94use code_context_menus::{
95 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
96 CompletionsMenu, ContextMenuOrigin,
97};
98use collections::{BTreeMap, HashMap, HashSet, VecDeque};
99use convert_case::{Case, Casing};
100use dap::TelemetrySpawnLocation;
101use display_map::*;
102use document_colors::LspColorData;
103use edit_prediction_types::{
104 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
105 EditPredictionGranularity, SuggestionDisplayType,
106};
107use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
108use element::{LineWithInvisibles, PositionMap, layout_line};
109use futures::{
110 FutureExt,
111 future::{self, Shared, join},
112};
113use fuzzy::{StringMatch, StringMatchCandidate};
114use git::blame::{GitBlame, GlobalBlameRenderer};
115use gpui::{
116 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
117 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
118 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
119 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
120 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
121 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
122 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
123 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
124 pulsating_between, px, relative, size,
125};
126use hover_links::{HoverLink, HoveredLinkState, find_file};
127use hover_popover::{HoverState, hide_hover};
128use indent_guides::ActiveIndentGuidesState;
129use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
130use itertools::{Either, Itertools};
131use language::{
132 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
133 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
134 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
135 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, LocalFile, OffsetRangeExt,
136 OutlineItem, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
137 WordsQuery,
138 language_settings::{
139 self, AllLanguageSettings, LanguageSettings, LspInsertMode, RewrapBehavior,
140 WordsCompletionMode, all_language_settings,
141 },
142 point_from_lsp, point_to_lsp, text_diff_with_options,
143};
144use linked_editing_ranges::refresh_linked_ranges;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId,
148};
149use markdown::Markdown;
150use mouse_context_menu::MouseContextMenu;
151use movement::TextLayoutDetails;
152use multi_buffer::{
153 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
154};
155use parking_lot::Mutex;
156use persistence::EditorDb;
157use project::{
158 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
159 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
160 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
161 ProjectItem, ProjectPath, ProjectTransaction,
162 debugger::{
163 breakpoint_store::{
164 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
165 BreakpointStore, BreakpointStoreEvent,
166 },
167 session::{Session, SessionEvent},
168 },
169 git_store::GitStoreEvent,
170 lsp_store::{
171 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
172 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
173 },
174 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
175};
176use rand::seq::SliceRandom;
177use regex::Regex;
178use rpc::{ErrorCode, ErrorExt, proto::PeerId};
179use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
180use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
181use serde::{Deserialize, Serialize};
182use settings::{
183 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
184 update_settings_file,
185};
186use smallvec::{SmallVec, smallvec};
187use snippet::Snippet;
188use std::{
189 any::{Any, TypeId},
190 borrow::Cow,
191 cell::{OnceCell, RefCell},
192 cmp::{self, Ordering, Reverse},
193 collections::hash_map,
194 iter::{self, Peekable},
195 mem,
196 num::NonZeroU32,
197 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
198 path::{Path, PathBuf},
199 rc::Rc,
200 sync::Arc,
201 time::{Duration, Instant},
202};
203use task::TaskVariables;
204use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
205use theme::{
206 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
207};
208use theme_settings::{ThemeSettings, observe_buffer_font_size_adjustment};
209use ui::{
210 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
211 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
212 utils::WithRemSize,
213};
214use ui_input::ErasedEditor;
215use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
216use workspace::{
217 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
218 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
219 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
220 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
221 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
222 searchable::SearchEvent,
223};
224pub use zed_actions::editor::RevealInFileManager;
225use zed_actions::editor::{MoveDown, MoveUp};
226
227use crate::{
228 code_context_menus::CompletionsMenuSource,
229 editor_settings::MultiCursorModifier,
230 hover_links::{find_url, find_url_from_range},
231 inlays::{
232 InlineValueCache,
233 inlay_hints::{LspInlayHintData, inlay_hint_settings},
234 },
235 runnables::{ResolvedTasks, RunnableData, RunnableTasks},
236 scroll::{ScrollOffset, ScrollPixelOffset},
237 selections_collection::resolve_selections_wrapping_blocks,
238 semantic_tokens::SemanticTokenState,
239 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
240};
241
242pub const FILE_HEADER_HEIGHT: u32 = 2;
243pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
244pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
245const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
246const MAX_LINE_LEN: usize = 1024;
247const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
248const MAX_SELECTION_HISTORY_LEN: usize = 1024;
249pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
250#[doc(hidden)]
251pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
252pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
253
254pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
255pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
256pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
257pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
258
259pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
260pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
261
262pub type RenderDiffHunkControlsFn = Arc<
263 dyn Fn(
264 u32,
265 &DiffHunkStatus,
266 Range<Anchor>,
267 bool,
268 Pixels,
269 &Entity<Editor>,
270 &mut Window,
271 &mut App,
272 ) -> AnyElement,
273>;
274
275enum ReportEditorEvent {
276 Saved { auto_saved: bool },
277 EditorOpened,
278 Closed,
279}
280
281impl ReportEditorEvent {
282 pub fn event_type(&self) -> &'static str {
283 match self {
284 Self::Saved { .. } => "Editor Saved",
285 Self::EditorOpened => "Editor Opened",
286 Self::Closed => "Editor Closed",
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293
294pub enum ConflictsOuter {}
295pub enum ConflictsOurs {}
296pub enum ConflictsTheirs {}
297pub enum ConflictsOursMarker {}
298pub enum ConflictsTheirsMarker {}
299
300pub struct HunkAddedColor;
301pub struct HunkRemovedColor;
302
303#[derive(Debug, Copy, Clone, PartialEq, Eq)]
304pub enum Navigated {
305 Yes,
306 No,
307}
308
309impl Navigated {
310 pub fn from_bool(yes: bool) -> Navigated {
311 if yes { Navigated::Yes } else { Navigated::No }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316enum DisplayDiffHunk {
317 Folded {
318 display_row: DisplayRow,
319 },
320 Unfolded {
321 is_created_file: bool,
322 diff_base_byte_range: Range<usize>,
323 display_row_range: Range<DisplayRow>,
324 multi_buffer_range: Range<Anchor>,
325 status: DiffHunkStatus,
326 word_diffs: Vec<Range<MultiBufferOffset>>,
327 },
328}
329
330pub enum HideMouseCursorOrigin {
331 TypingAction,
332 MovementAction,
333}
334
335pub fn init(cx: &mut App) {
336 cx.set_global(GlobalBlameRenderer(Arc::new(())));
337 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
338
339 workspace::register_project_item::<Editor>(cx);
340 workspace::FollowableViewRegistry::register::<Editor>(cx);
341 workspace::register_serializable_item::<Editor>(cx);
342
343 cx.observe_new(
344 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
345 workspace.register_action(Editor::new_file);
346 workspace.register_action(Editor::new_file_split);
347 workspace.register_action(Editor::new_file_vertical);
348 workspace.register_action(Editor::new_file_horizontal);
349 workspace.register_action(Editor::cancel_language_server_work);
350 workspace.register_action(Editor::toggle_focus);
351 },
352 )
353 .detach();
354
355 cx.on_action(move |_: &workspace::NewFile, cx| {
356 let app_state = workspace::AppState::global(cx);
357 workspace::open_new(
358 Default::default(),
359 app_state,
360 cx,
361 |workspace, window, cx| Editor::new_file(workspace, &Default::default(), window, cx),
362 )
363 .detach_and_log_err(cx);
364 })
365 .on_action(move |_: &workspace::NewWindow, cx| {
366 let app_state = workspace::AppState::global(cx);
367 workspace::open_new(
368 Default::default(),
369 app_state,
370 cx,
371 |workspace, window, cx| {
372 cx.activate(true);
373 Editor::new_file(workspace, &Default::default(), window, cx)
374 },
375 )
376 .detach_and_log_err(cx);
377 });
378 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
379 Arc::new(ErasedEditorImpl(
380 cx.new(|cx| Editor::single_line(window, cx)),
381 )) as Arc<dyn ErasedEditor>
382 });
383 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
384}
385
386pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
387 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
388}
389
390pub trait DiagnosticRenderer {
391 fn render_group(
392 &self,
393 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
394 buffer_id: BufferId,
395 snapshot: EditorSnapshot,
396 editor: WeakEntity<Editor>,
397 language_registry: Option<Arc<LanguageRegistry>>,
398 cx: &mut App,
399 ) -> Vec<BlockProperties<Anchor>>;
400
401 fn render_hover(
402 &self,
403 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
404 range: Range<Point>,
405 buffer_id: BufferId,
406 language_registry: Option<Arc<LanguageRegistry>>,
407 cx: &mut App,
408 ) -> Option<Entity<markdown::Markdown>>;
409
410 fn open_link(
411 &self,
412 editor: &mut Editor,
413 link: SharedString,
414 window: &mut Window,
415 cx: &mut Context<Editor>,
416 );
417}
418
419pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
420
421impl GlobalDiagnosticRenderer {
422 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
423 cx.try_global::<Self>().map(|g| g.0.clone())
424 }
425}
426
427impl gpui::Global for GlobalDiagnosticRenderer {}
428pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
429 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
430}
431
432pub struct SearchWithinRange;
433
434trait InvalidationRegion {
435 fn ranges(&self) -> &[Range<Anchor>];
436}
437
438#[derive(Clone, Debug, PartialEq)]
439pub enum SelectPhase {
440 Begin {
441 position: DisplayPoint,
442 add: bool,
443 click_count: usize,
444 },
445 BeginColumnar {
446 position: DisplayPoint,
447 reset: bool,
448 mode: ColumnarMode,
449 goal_column: u32,
450 },
451 Extend {
452 position: DisplayPoint,
453 click_count: usize,
454 },
455 Update {
456 position: DisplayPoint,
457 goal_column: u32,
458 scroll_delta: gpui::Point<f32>,
459 },
460 End,
461}
462
463#[derive(Clone, Debug, PartialEq)]
464pub enum ColumnarMode {
465 FromMouse,
466 FromSelection,
467}
468
469#[derive(Clone, Debug)]
470pub enum SelectMode {
471 Character,
472 Word(Range<Anchor>),
473 Line(Range<Anchor>),
474 All,
475}
476
477#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
478pub enum SizingBehavior {
479 /// The editor will layout itself using `size_full` and will include the vertical
480 /// scroll margin as requested by user settings.
481 #[default]
482 Default,
483 /// The editor will layout itself using `size_full`, but will not have any
484 /// vertical overscroll.
485 ExcludeOverscrollMargin,
486 /// The editor will request a vertical size according to its content and will be
487 /// layouted without a vertical scroll margin.
488 SizeByContent,
489}
490
491#[derive(Clone, PartialEq, Eq, Debug)]
492pub enum EditorMode {
493 SingleLine,
494 AutoHeight {
495 min_lines: usize,
496 max_lines: Option<usize>,
497 },
498 Full {
499 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
500 scale_ui_elements_with_buffer_font_size: bool,
501 /// When set to `true`, the editor will render a background for the active line.
502 show_active_line_background: bool,
503 /// Determines the sizing behavior for this editor
504 sizing_behavior: SizingBehavior,
505 },
506 Minimap {
507 parent: WeakEntity<Editor>,
508 },
509}
510
511impl EditorMode {
512 pub fn full() -> Self {
513 Self::Full {
514 scale_ui_elements_with_buffer_font_size: true,
515 show_active_line_background: true,
516 sizing_behavior: SizingBehavior::Default,
517 }
518 }
519
520 #[inline]
521 pub fn is_full(&self) -> bool {
522 matches!(self, Self::Full { .. })
523 }
524
525 #[inline]
526 pub fn is_single_line(&self) -> bool {
527 matches!(self, Self::SingleLine { .. })
528 }
529
530 #[inline]
531 fn is_minimap(&self) -> bool {
532 matches!(self, Self::Minimap { .. })
533 }
534}
535
536#[derive(Copy, Clone, Debug)]
537pub enum SoftWrap {
538 /// Prefer not to wrap at all.
539 ///
540 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
541 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
542 GitDiff,
543 /// Prefer a single line generally, unless an overly long line is encountered.
544 None,
545 /// Soft wrap lines that exceed the editor width.
546 EditorWidth,
547 /// Soft wrap lines at the preferred line length.
548 Column(u32),
549 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
550 Bounded(u32),
551}
552
553#[derive(Clone)]
554pub struct EditorStyle {
555 pub background: Hsla,
556 pub border: Hsla,
557 pub local_player: PlayerColor,
558 pub text: TextStyle,
559 pub scrollbar_width: Pixels,
560 pub syntax: Arc<SyntaxTheme>,
561 pub status: StatusColors,
562 pub inlay_hints_style: HighlightStyle,
563 pub edit_prediction_styles: EditPredictionStyles,
564 pub unnecessary_code_fade: f32,
565 pub show_underlines: bool,
566}
567
568impl Default for EditorStyle {
569 fn default() -> Self {
570 Self {
571 background: Hsla::default(),
572 border: Hsla::default(),
573 local_player: PlayerColor::default(),
574 text: TextStyle::default(),
575 scrollbar_width: Pixels::default(),
576 syntax: Default::default(),
577 // HACK: Status colors don't have a real default.
578 // We should look into removing the status colors from the editor
579 // style and retrieve them directly from the theme.
580 status: StatusColors::dark(),
581 inlay_hints_style: HighlightStyle::default(),
582 edit_prediction_styles: EditPredictionStyles {
583 insertion: HighlightStyle::default(),
584 whitespace: HighlightStyle::default(),
585 },
586 unnecessary_code_fade: Default::default(),
587 show_underlines: true,
588 }
589 }
590}
591
592pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
593 let show_background = AllLanguageSettings::get_global(cx)
594 .defaults
595 .inlay_hints
596 .show_background;
597
598 let mut style = cx
599 .theme()
600 .syntax()
601 .style_for_name("hint")
602 .unwrap_or_default();
603
604 if style.color.is_none() {
605 style.color = Some(cx.theme().status().hint);
606 }
607
608 if !show_background {
609 style.background_color = None;
610 return style;
611 }
612
613 if style.background_color.is_none() {
614 style.background_color = Some(cx.theme().status().hint_background);
615 }
616
617 style
618}
619
620pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
621 EditPredictionStyles {
622 insertion: HighlightStyle {
623 color: Some(cx.theme().status().predictive),
624 ..HighlightStyle::default()
625 },
626 whitespace: HighlightStyle {
627 background_color: Some(cx.theme().status().created_background),
628 ..HighlightStyle::default()
629 },
630 }
631}
632
633type CompletionId = usize;
634
635pub(crate) enum EditDisplayMode {
636 TabAccept,
637 DiffPopover,
638 Inline,
639}
640
641enum EditPrediction {
642 Edit {
643 edits: Vec<(Range<Anchor>, Arc<str>)>,
644 /// Predicted cursor position as (anchor, offset_from_anchor).
645 /// The anchor is in multibuffer coordinates; after applying edits,
646 /// resolve the anchor and add the offset to get the final cursor position.
647 cursor_position: Option<(Anchor, usize)>,
648 edit_preview: Option<EditPreview>,
649 display_mode: EditDisplayMode,
650 snapshot: BufferSnapshot,
651 },
652 /// Move to a specific location in the active editor
653 MoveWithin {
654 target: Anchor,
655 snapshot: BufferSnapshot,
656 },
657 /// Move to a specific location in a different editor (not the active one)
658 MoveOutside {
659 target: language::Anchor,
660 snapshot: BufferSnapshot,
661 },
662}
663
664struct EditPredictionState {
665 inlay_ids: Vec<InlayId>,
666 completion: EditPrediction,
667 completion_id: Option<SharedString>,
668 invalidation_range: Option<Range<Anchor>>,
669}
670
671enum EditPredictionSettings {
672 Disabled,
673 Enabled {
674 show_in_menu: bool,
675 preview_requires_modifier: bool,
676 },
677}
678
679#[derive(Debug, Clone)]
680struct InlineDiagnostic {
681 message: SharedString,
682 group_id: usize,
683 is_primary: bool,
684 start: Point,
685 severity: lsp::DiagnosticSeverity,
686}
687
688pub enum MenuEditPredictionsPolicy {
689 Never,
690 ByProvider,
691}
692
693pub enum EditPredictionPreview {
694 /// Modifier is not pressed
695 Inactive { released_too_fast: bool },
696 /// Modifier pressed
697 Active {
698 since: Instant,
699 previous_scroll_position: Option<SharedScrollAnchor>,
700 },
701}
702
703#[derive(Copy, Clone, Eq, PartialEq)]
704enum EditPredictionKeybindSurface {
705 Inline,
706 CursorPopoverCompact,
707 CursorPopoverExpanded,
708}
709
710#[derive(Copy, Clone, Eq, PartialEq, Debug)]
711enum EditPredictionKeybindAction {
712 Accept,
713 Preview,
714}
715
716struct EditPredictionKeybindDisplay {
717 #[cfg(test)]
718 accept_keystroke: Option<gpui::KeybindingKeystroke>,
719 #[cfg(test)]
720 preview_keystroke: Option<gpui::KeybindingKeystroke>,
721 displayed_keystroke: Option<gpui::KeybindingKeystroke>,
722 action: EditPredictionKeybindAction,
723 missing_accept_keystroke: bool,
724 show_hold_label: bool,
725}
726
727impl EditPredictionPreview {
728 pub fn released_too_fast(&self) -> bool {
729 match self {
730 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
731 EditPredictionPreview::Active { .. } => false,
732 }
733 }
734
735 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
736 if let EditPredictionPreview::Active {
737 previous_scroll_position,
738 ..
739 } = self
740 {
741 *previous_scroll_position = scroll_position;
742 }
743 }
744}
745
746pub struct ContextMenuOptions {
747 pub min_entries_visible: usize,
748 pub max_entries_visible: usize,
749 pub placement: Option<ContextMenuPlacement>,
750}
751
752#[derive(Debug, Clone, PartialEq, Eq)]
753pub enum ContextMenuPlacement {
754 Above,
755 Below,
756}
757
758#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
759struct EditorActionId(usize);
760
761impl EditorActionId {
762 pub fn post_inc(&mut self) -> Self {
763 let answer = self.0;
764
765 *self = Self(answer + 1);
766
767 Self(answer)
768 }
769}
770
771// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
772// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
773
774type BackgroundHighlight = (
775 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
776 Arc<[Range<Anchor>]>,
777);
778type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
779
780#[derive(Default)]
781struct ScrollbarMarkerState {
782 scrollbar_size: Size<Pixels>,
783 dirty: bool,
784 markers: Arc<[PaintQuad]>,
785 pending_refresh: Option<Task<Result<()>>>,
786}
787
788impl ScrollbarMarkerState {
789 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
790 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
791 }
792}
793
794#[derive(Clone, Copy, PartialEq, Eq)]
795pub enum MinimapVisibility {
796 Disabled,
797 Enabled {
798 /// The configuration currently present in the users settings.
799 setting_configuration: bool,
800 /// Whether to override the currently set visibility from the users setting.
801 toggle_override: bool,
802 },
803}
804
805impl MinimapVisibility {
806 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
807 if mode.is_full() {
808 Self::Enabled {
809 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
810 toggle_override: false,
811 }
812 } else {
813 Self::Disabled
814 }
815 }
816
817 fn hidden(&self) -> Self {
818 match *self {
819 Self::Enabled {
820 setting_configuration,
821 ..
822 } => Self::Enabled {
823 setting_configuration,
824 toggle_override: setting_configuration,
825 },
826 Self::Disabled => Self::Disabled,
827 }
828 }
829
830 fn disabled(&self) -> bool {
831 matches!(*self, Self::Disabled)
832 }
833
834 fn settings_visibility(&self) -> bool {
835 match *self {
836 Self::Enabled {
837 setting_configuration,
838 ..
839 } => setting_configuration,
840 _ => false,
841 }
842 }
843
844 fn visible(&self) -> bool {
845 match *self {
846 Self::Enabled {
847 setting_configuration,
848 toggle_override,
849 } => setting_configuration ^ toggle_override,
850 _ => false,
851 }
852 }
853
854 fn toggle_visibility(&self) -> Self {
855 match *self {
856 Self::Enabled {
857 toggle_override,
858 setting_configuration,
859 } => Self::Enabled {
860 setting_configuration,
861 toggle_override: !toggle_override,
862 },
863 Self::Disabled => Self::Disabled,
864 }
865 }
866}
867
868#[derive(Debug, Clone, Copy, PartialEq, Eq)]
869pub enum BufferSerialization {
870 All,
871 NonDirtyBuffers,
872}
873
874impl BufferSerialization {
875 fn new(restore_unsaved_buffers: bool) -> Self {
876 if restore_unsaved_buffers {
877 Self::All
878 } else {
879 Self::NonDirtyBuffers
880 }
881 }
882}
883
884/// Addons allow storing per-editor state in other crates (e.g. Vim)
885pub trait Addon: 'static {
886 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
887
888 fn render_buffer_header_controls(
889 &self,
890 _: &ExcerptInfo,
891 _: &Window,
892 _: &App,
893 ) -> Option<AnyElement> {
894 None
895 }
896
897 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
898 None
899 }
900
901 fn to_any(&self) -> &dyn std::any::Any;
902
903 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
904 None
905 }
906}
907
908struct ChangeLocation {
909 current: Option<Vec<Anchor>>,
910 original: Vec<Anchor>,
911}
912impl ChangeLocation {
913 fn locations(&self) -> &[Anchor] {
914 self.current.as_ref().unwrap_or(&self.original)
915 }
916}
917
918/// A set of caret positions, registered when the editor was edited.
919pub struct ChangeList {
920 changes: Vec<ChangeLocation>,
921 /// Currently "selected" change.
922 position: Option<usize>,
923}
924
925impl ChangeList {
926 pub fn new() -> Self {
927 Self {
928 changes: Vec::new(),
929 position: None,
930 }
931 }
932
933 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
934 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
935 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
936 if self.changes.is_empty() {
937 return None;
938 }
939
940 let prev = self.position.unwrap_or(self.changes.len());
941 let next = if direction == Direction::Prev {
942 prev.saturating_sub(count)
943 } else {
944 (prev + count).min(self.changes.len() - 1)
945 };
946 self.position = Some(next);
947 self.changes.get(next).map(|change| change.locations())
948 }
949
950 /// Adds a new change to the list, resetting the change list position.
951 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
952 self.position.take();
953 if let Some(last) = self.changes.last_mut()
954 && group
955 {
956 last.current = Some(new_positions)
957 } else {
958 self.changes.push(ChangeLocation {
959 original: new_positions,
960 current: None,
961 });
962 }
963 }
964
965 pub fn last(&self) -> Option<&[Anchor]> {
966 self.changes.last().map(|change| change.locations())
967 }
968
969 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
970 self.changes.last().map(|change| change.original.as_slice())
971 }
972
973 pub fn invert_last_group(&mut self) {
974 if let Some(last) = self.changes.last_mut()
975 && let Some(current) = last.current.as_mut()
976 {
977 mem::swap(&mut last.original, current);
978 }
979 }
980}
981
982#[derive(Clone)]
983struct InlineBlamePopoverState {
984 scroll_handle: ScrollHandle,
985 commit_message: Option<ParsedCommitMessage>,
986 markdown: Entity<Markdown>,
987}
988
989struct InlineBlamePopover {
990 position: gpui::Point<Pixels>,
991 hide_task: Option<Task<()>>,
992 popover_bounds: Option<Bounds<Pixels>>,
993 popover_state: InlineBlamePopoverState,
994 keyboard_grace: bool,
995}
996
997enum SelectionDragState {
998 /// State when no drag related activity is detected.
999 None,
1000 /// State when the mouse is down on a selection that is about to be dragged.
1001 ReadyToDrag {
1002 selection: Selection<Anchor>,
1003 click_position: gpui::Point<Pixels>,
1004 mouse_down_time: Instant,
1005 },
1006 /// State when the mouse is dragging the selection in the editor.
1007 Dragging {
1008 selection: Selection<Anchor>,
1009 drop_cursor: Selection<Anchor>,
1010 hide_drop_cursor: bool,
1011 },
1012}
1013
1014enum ColumnarSelectionState {
1015 FromMouse {
1016 selection_tail: Anchor,
1017 display_point: Option<DisplayPoint>,
1018 },
1019 FromSelection {
1020 selection_tail: Anchor,
1021 },
1022}
1023
1024/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1025/// a breakpoint on them.
1026#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1027struct PhantomBreakpointIndicator {
1028 display_row: DisplayRow,
1029 /// There's a small debounce between hovering over the line and showing the indicator.
1030 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1031 is_active: bool,
1032 collides_with_existing_breakpoint: bool,
1033}
1034
1035/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1036/// in diff view mode.
1037#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1038pub(crate) struct PhantomDiffReviewIndicator {
1039 /// The starting anchor of the selection (or the only row if not dragging).
1040 pub start: Anchor,
1041 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1042 pub end: Anchor,
1043 /// There's a small debounce between hovering over the line and showing the indicator.
1044 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1045 pub is_active: bool,
1046}
1047
1048#[derive(Clone, Debug)]
1049pub(crate) struct DiffReviewDragState {
1050 pub start_anchor: Anchor,
1051 pub current_anchor: Anchor,
1052}
1053
1054impl DiffReviewDragState {
1055 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1056 let start = self.start_anchor.to_display_point(snapshot).row();
1057 let current = self.current_anchor.to_display_point(snapshot).row();
1058
1059 (start..=current).sorted()
1060 }
1061}
1062
1063/// Identifies a specific hunk in the diff buffer.
1064/// Used as a key to group comments by their location.
1065#[derive(Clone, Debug)]
1066pub struct DiffHunkKey {
1067 /// The file path (relative to worktree) this hunk belongs to.
1068 pub file_path: Arc<util::rel_path::RelPath>,
1069 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1070 pub hunk_start_anchor: Anchor,
1071}
1072
1073/// A review comment stored locally before being sent to the Agent panel.
1074#[derive(Clone)]
1075pub struct StoredReviewComment {
1076 /// Unique identifier for this comment (for edit/delete operations).
1077 pub id: usize,
1078 /// The comment text entered by the user.
1079 pub comment: String,
1080 /// Anchors for the code range being reviewed.
1081 pub range: Range<Anchor>,
1082 /// Timestamp when the comment was created (for chronological ordering).
1083 pub created_at: Instant,
1084 /// Whether this comment is currently being edited inline.
1085 pub is_editing: bool,
1086}
1087
1088impl StoredReviewComment {
1089 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1090 Self {
1091 id,
1092 comment,
1093 range: anchor_range,
1094 created_at: Instant::now(),
1095 is_editing: false,
1096 }
1097 }
1098}
1099
1100/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1101pub(crate) struct DiffReviewOverlay {
1102 pub anchor_range: Range<Anchor>,
1103 /// The block ID for the overlay.
1104 pub block_id: CustomBlockId,
1105 /// The editor entity for the review input.
1106 pub prompt_editor: Entity<Editor>,
1107 /// The hunk key this overlay belongs to.
1108 pub hunk_key: DiffHunkKey,
1109 /// Whether the comments section is expanded.
1110 pub comments_expanded: bool,
1111 /// Editors for comments currently being edited inline.
1112 /// Key: comment ID, Value: Editor entity for inline editing.
1113 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1114 /// Subscriptions for inline edit editors' action handlers.
1115 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1116 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1117 /// The current user's avatar URI for display in comment rows.
1118 pub user_avatar_uri: Option<SharedUri>,
1119 /// Subscription to keep the action handler alive.
1120 _subscription: Subscription,
1121}
1122
1123/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1124///
1125/// See the [module level documentation](self) for more information.
1126pub struct Editor {
1127 focus_handle: FocusHandle,
1128 last_focused_descendant: Option<WeakFocusHandle>,
1129 /// The text buffer being edited
1130 buffer: Entity<MultiBuffer>,
1131 /// Map of how text in the buffer should be displayed.
1132 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1133 pub display_map: Entity<DisplayMap>,
1134 placeholder_display_map: Option<Entity<DisplayMap>>,
1135 pub selections: SelectionsCollection,
1136 pub scroll_manager: ScrollManager,
1137 /// When inline assist editors are linked, they all render cursors because
1138 /// typing enters text into each of them, even the ones that aren't focused.
1139 pub(crate) show_cursor_when_unfocused: bool,
1140 columnar_selection_state: Option<ColumnarSelectionState>,
1141 add_selections_state: Option<AddSelectionsState>,
1142 select_next_state: Option<SelectNextState>,
1143 select_prev_state: Option<SelectNextState>,
1144 selection_history: SelectionHistory,
1145 defer_selection_effects: bool,
1146 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1147 autoclose_regions: Vec<AutocloseRegion>,
1148 snippet_stack: InvalidationStack<SnippetState>,
1149 select_syntax_node_history: SelectSyntaxNodeHistory,
1150 ime_transaction: Option<TransactionId>,
1151 pub diagnostics_max_severity: DiagnosticSeverity,
1152 active_diagnostics: ActiveDiagnostic,
1153 show_inline_diagnostics: bool,
1154 inline_diagnostics_update: Task<()>,
1155 inline_diagnostics_enabled: bool,
1156 diagnostics_enabled: bool,
1157 word_completions_enabled: bool,
1158 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1159 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1160 hard_wrap: Option<usize>,
1161 project: Option<Entity<Project>>,
1162 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1163 completion_provider: Option<Rc<dyn CompletionProvider>>,
1164 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1165 blink_manager: Entity<BlinkManager>,
1166 show_cursor_names: bool,
1167 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1168 pub show_local_selections: bool,
1169 mode: EditorMode,
1170 show_breadcrumbs: bool,
1171 show_gutter: bool,
1172 show_scrollbars: ScrollbarAxes,
1173 minimap_visibility: MinimapVisibility,
1174 offset_content: bool,
1175 disable_expand_excerpt_buttons: bool,
1176 delegate_expand_excerpts: bool,
1177 delegate_stage_and_restore: bool,
1178 delegate_open_excerpts: bool,
1179 enable_lsp_data: bool,
1180 enable_runnables: bool,
1181 show_line_numbers: Option<bool>,
1182 use_relative_line_numbers: Option<bool>,
1183 show_git_diff_gutter: Option<bool>,
1184 show_code_actions: Option<bool>,
1185 show_runnables: Option<bool>,
1186 show_breakpoints: Option<bool>,
1187 show_diff_review_button: bool,
1188 show_wrap_guides: Option<bool>,
1189 show_indent_guides: Option<bool>,
1190 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1191 highlight_order: usize,
1192 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1193 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1194 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1195 scrollbar_marker_state: ScrollbarMarkerState,
1196 active_indent_guides_state: ActiveIndentGuidesState,
1197 nav_history: Option<ItemNavHistory>,
1198 context_menu: RefCell<Option<CodeContextMenu>>,
1199 context_menu_options: Option<ContextMenuOptions>,
1200 mouse_context_menu: Option<MouseContextMenu>,
1201 completion_tasks: Vec<(CompletionId, Task<()>)>,
1202 inline_blame_popover: Option<InlineBlamePopover>,
1203 inline_blame_popover_show_task: Option<Task<()>>,
1204 signature_help_state: SignatureHelpState,
1205 auto_signature_help: Option<bool>,
1206 find_all_references_task_sources: Vec<Anchor>,
1207 next_completion_id: CompletionId,
1208 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1209 code_actions_task: Option<Task<Result<()>>>,
1210 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1211 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1212 debounced_selection_highlight_complete: bool,
1213 document_highlights_task: Option<Task<()>>,
1214 linked_editing_range_task: Option<Task<Option<()>>>,
1215 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1216 pending_rename: Option<RenameState>,
1217 searchable: bool,
1218 cursor_shape: CursorShape,
1219 /// Whether the cursor is offset one character to the left when something is
1220 /// selected (needed for vim visual mode)
1221 cursor_offset_on_selection: bool,
1222 current_line_highlight: Option<CurrentLineHighlight>,
1223 /// Whether to collapse search match ranges to just their start position.
1224 /// When true, navigating to a match positions the cursor at the match
1225 /// without selecting the matched text.
1226 collapse_matches: bool,
1227 autoindent_mode: Option<AutoindentMode>,
1228 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1229 input_enabled: bool,
1230 expects_character_input: bool,
1231 use_modal_editing: bool,
1232 read_only: bool,
1233 leader_id: Option<CollaboratorId>,
1234 remote_id: Option<ViewId>,
1235 pub hover_state: HoverState,
1236 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1237 prev_pressure_stage: Option<PressureStage>,
1238 gutter_hovered: bool,
1239 hovered_link_state: Option<HoveredLinkState>,
1240 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1241 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1242 active_edit_prediction: Option<EditPredictionState>,
1243 /// Used to prevent flickering as the user types while the menu is open
1244 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1245 edit_prediction_settings: EditPredictionSettings,
1246 edit_predictions_hidden_for_vim_mode: bool,
1247 show_edit_predictions_override: Option<bool>,
1248 show_completions_on_input_override: Option<bool>,
1249 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1250 edit_prediction_preview: EditPredictionPreview,
1251 in_leading_whitespace: bool,
1252 next_inlay_id: usize,
1253 next_color_inlay_id: usize,
1254 _subscriptions: Vec<Subscription>,
1255 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1256 gutter_dimensions: GutterDimensions,
1257 style: Option<EditorStyle>,
1258 text_style_refinement: Option<TextStyleRefinement>,
1259 next_editor_action_id: EditorActionId,
1260 editor_actions: Rc<
1261 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1262 >,
1263 use_autoclose: bool,
1264 use_auto_surround: bool,
1265 auto_replace_emoji_shortcode: bool,
1266 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1267 show_git_blame_gutter: bool,
1268 show_git_blame_inline: bool,
1269 show_git_blame_inline_delay_task: Option<Task<()>>,
1270 git_blame_inline_enabled: bool,
1271 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1272 buffer_serialization: Option<BufferSerialization>,
1273 show_selection_menu: Option<bool>,
1274 blame: Option<Entity<GitBlame>>,
1275 blame_subscription: Option<Subscription>,
1276 custom_context_menu: Option<
1277 Box<
1278 dyn 'static
1279 + Fn(
1280 &mut Self,
1281 DisplayPoint,
1282 &mut Window,
1283 &mut Context<Self>,
1284 ) -> Option<Entity<ui::ContextMenu>>,
1285 >,
1286 >,
1287 last_bounds: Option<Bounds<Pixels>>,
1288 last_position_map: Option<Rc<PositionMap>>,
1289 expect_bounds_change: Option<Bounds<Pixels>>,
1290 runnables: RunnableData,
1291 breakpoint_store: Option<Entity<BreakpointStore>>,
1292 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1293 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1294 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1295 /// Active diff review overlays. Multiple overlays can be open simultaneously
1296 /// when hunks have comments stored.
1297 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1298 /// Stored review comments grouped by hunk.
1299 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1300 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1301 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1302 /// Counter for generating unique comment IDs.
1303 next_review_comment_id: usize,
1304 hovered_diff_hunk_row: Option<DisplayRow>,
1305 pull_diagnostics_task: Task<()>,
1306 in_project_search: bool,
1307 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1308 breadcrumb_header: Option<String>,
1309 focused_block: Option<FocusedBlock>,
1310 next_scroll_position: NextScrollCursorCenterTopBottom,
1311 addons: HashMap<TypeId, Box<dyn Addon>>,
1312 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1313 load_diff_task: Option<Shared<Task<()>>>,
1314 /// Whether we are temporarily displaying a diff other than git's
1315 temporary_diff_override: bool,
1316 selection_mark_mode: bool,
1317 toggle_fold_multiple_buffers: Task<()>,
1318 _scroll_cursor_center_top_bottom_task: Task<()>,
1319 serialize_selections: Task<()>,
1320 serialize_folds: Task<()>,
1321 mouse_cursor_hidden: bool,
1322 minimap: Option<Entity<Self>>,
1323 hide_mouse_mode: HideMouseMode,
1324 pub change_list: ChangeList,
1325 inline_value_cache: InlineValueCache,
1326 number_deleted_lines: bool,
1327
1328 selection_drag_state: SelectionDragState,
1329 colors: Option<LspColorData>,
1330 post_scroll_update: Task<()>,
1331 refresh_colors_task: Task<()>,
1332 use_document_folding_ranges: bool,
1333 refresh_folding_ranges_task: Task<()>,
1334 inlay_hints: Option<LspInlayHintData>,
1335 folding_newlines: Task<()>,
1336 select_next_is_case_sensitive: Option<bool>,
1337 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1338 on_local_selections_changed:
1339 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1340 suppress_selection_callback: bool,
1341 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1342 accent_data: Option<AccentData>,
1343 bracket_fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1344 semantic_token_state: SemanticTokenState,
1345 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1346 refresh_document_symbols_task: Shared<Task<()>>,
1347 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1348 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1349 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1350 sticky_headers_task: Task<()>,
1351 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1352 pub(crate) colorize_brackets_task: Task<()>,
1353}
1354
1355#[derive(Debug, PartialEq)]
1356struct AccentData {
1357 colors: AccentColors,
1358 overrides: Vec<SharedString>,
1359}
1360
1361fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1362 if debounce_ms > 0 {
1363 Some(Duration::from_millis(debounce_ms))
1364 } else {
1365 None
1366 }
1367}
1368
1369#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1370enum NextScrollCursorCenterTopBottom {
1371 #[default]
1372 Center,
1373 Top,
1374 Bottom,
1375}
1376
1377impl NextScrollCursorCenterTopBottom {
1378 fn next(&self) -> Self {
1379 match self {
1380 Self::Center => Self::Top,
1381 Self::Top => Self::Bottom,
1382 Self::Bottom => Self::Center,
1383 }
1384 }
1385}
1386
1387#[derive(Clone)]
1388pub struct EditorSnapshot {
1389 pub mode: EditorMode,
1390 show_gutter: bool,
1391 offset_content: bool,
1392 show_line_numbers: Option<bool>,
1393 number_deleted_lines: bool,
1394 show_git_diff_gutter: Option<bool>,
1395 show_code_actions: Option<bool>,
1396 show_runnables: Option<bool>,
1397 show_breakpoints: Option<bool>,
1398 git_blame_gutter_max_author_length: Option<usize>,
1399 pub display_snapshot: DisplaySnapshot,
1400 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1401 is_focused: bool,
1402 scroll_anchor: SharedScrollAnchor,
1403 ongoing_scroll: OngoingScroll,
1404 current_line_highlight: CurrentLineHighlight,
1405 gutter_hovered: bool,
1406 semantic_tokens_enabled: bool,
1407}
1408
1409#[derive(Default, Debug, Clone, Copy)]
1410pub struct GutterDimensions {
1411 pub left_padding: Pixels,
1412 pub right_padding: Pixels,
1413 pub width: Pixels,
1414 pub margin: Pixels,
1415 pub git_blame_entries_width: Option<Pixels>,
1416}
1417
1418impl GutterDimensions {
1419 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1420 Self {
1421 margin: Self::default_gutter_margin(font_id, font_size, cx),
1422 ..Default::default()
1423 }
1424 }
1425
1426 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1427 -cx.text_system().descent(font_id, font_size)
1428 }
1429 /// The full width of the space taken up by the gutter.
1430 pub fn full_width(&self) -> Pixels {
1431 self.margin + self.width
1432 }
1433
1434 /// The width of the space reserved for the fold indicators,
1435 /// use alongside 'justify_end' and `gutter_width` to
1436 /// right align content with the line numbers
1437 pub fn fold_area_width(&self) -> Pixels {
1438 self.margin + self.right_padding
1439 }
1440}
1441
1442struct CharacterDimensions {
1443 em_width: Pixels,
1444 em_advance: Pixels,
1445 line_height: Pixels,
1446}
1447
1448#[derive(Debug)]
1449pub struct RemoteSelection {
1450 pub replica_id: ReplicaId,
1451 pub selection: Selection<Anchor>,
1452 pub cursor_shape: CursorShape,
1453 pub collaborator_id: CollaboratorId,
1454 pub line_mode: bool,
1455 pub user_name: Option<SharedString>,
1456 pub color: PlayerColor,
1457}
1458
1459#[derive(Clone, Debug)]
1460struct SelectionHistoryEntry {
1461 selections: Arc<[Selection<Anchor>]>,
1462 select_next_state: Option<SelectNextState>,
1463 select_prev_state: Option<SelectNextState>,
1464 add_selections_state: Option<AddSelectionsState>,
1465}
1466
1467#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1468enum SelectionHistoryMode {
1469 #[default]
1470 Normal,
1471 Undoing,
1472 Redoing,
1473 Skipping,
1474}
1475
1476#[derive(Clone, PartialEq, Eq, Hash)]
1477struct HoveredCursor {
1478 replica_id: ReplicaId,
1479 selection_id: usize,
1480}
1481
1482#[derive(Debug)]
1483/// SelectionEffects controls the side-effects of updating the selection.
1484///
1485/// The default behaviour does "what you mostly want":
1486/// - it pushes to the nav history if the cursor moved by >10 lines
1487/// - it re-triggers completion requests
1488/// - it scrolls to fit
1489///
1490/// You might want to modify these behaviours. For example when doing a "jump"
1491/// like go to definition, we always want to add to nav history; but when scrolling
1492/// in vim mode we never do.
1493///
1494/// Similarly, you might want to disable scrolling if you don't want the viewport to
1495/// move.
1496#[derive(Clone)]
1497pub struct SelectionEffects {
1498 nav_history: Option<bool>,
1499 completions: bool,
1500 scroll: Option<Autoscroll>,
1501}
1502
1503impl Default for SelectionEffects {
1504 fn default() -> Self {
1505 Self {
1506 nav_history: None,
1507 completions: true,
1508 scroll: Some(Autoscroll::fit()),
1509 }
1510 }
1511}
1512impl SelectionEffects {
1513 pub fn scroll(scroll: Autoscroll) -> Self {
1514 Self {
1515 scroll: Some(scroll),
1516 ..Default::default()
1517 }
1518 }
1519
1520 pub fn no_scroll() -> Self {
1521 Self {
1522 scroll: None,
1523 ..Default::default()
1524 }
1525 }
1526
1527 pub fn completions(self, completions: bool) -> Self {
1528 Self {
1529 completions,
1530 ..self
1531 }
1532 }
1533
1534 pub fn nav_history(self, nav_history: bool) -> Self {
1535 Self {
1536 nav_history: Some(nav_history),
1537 ..self
1538 }
1539 }
1540}
1541
1542struct DeferredSelectionEffectsState {
1543 changed: bool,
1544 effects: SelectionEffects,
1545 old_cursor_position: Anchor,
1546 history_entry: SelectionHistoryEntry,
1547}
1548
1549#[derive(Default)]
1550struct SelectionHistory {
1551 #[allow(clippy::type_complexity)]
1552 selections_by_transaction:
1553 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1554 mode: SelectionHistoryMode,
1555 undo_stack: VecDeque<SelectionHistoryEntry>,
1556 redo_stack: VecDeque<SelectionHistoryEntry>,
1557}
1558
1559impl SelectionHistory {
1560 #[track_caller]
1561 fn insert_transaction(
1562 &mut self,
1563 transaction_id: TransactionId,
1564 selections: Arc<[Selection<Anchor>]>,
1565 ) {
1566 if selections.is_empty() {
1567 log::error!(
1568 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1569 std::panic::Location::caller()
1570 );
1571 return;
1572 }
1573 self.selections_by_transaction
1574 .insert(transaction_id, (selections, None));
1575 }
1576
1577 #[allow(clippy::type_complexity)]
1578 fn transaction(
1579 &self,
1580 transaction_id: TransactionId,
1581 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1582 self.selections_by_transaction.get(&transaction_id)
1583 }
1584
1585 #[allow(clippy::type_complexity)]
1586 fn transaction_mut(
1587 &mut self,
1588 transaction_id: TransactionId,
1589 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1590 self.selections_by_transaction.get_mut(&transaction_id)
1591 }
1592
1593 fn push(&mut self, entry: SelectionHistoryEntry) {
1594 if !entry.selections.is_empty() {
1595 match self.mode {
1596 SelectionHistoryMode::Normal => {
1597 self.push_undo(entry);
1598 self.redo_stack.clear();
1599 }
1600 SelectionHistoryMode::Undoing => self.push_redo(entry),
1601 SelectionHistoryMode::Redoing => self.push_undo(entry),
1602 SelectionHistoryMode::Skipping => {}
1603 }
1604 }
1605 }
1606
1607 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1608 if self
1609 .undo_stack
1610 .back()
1611 .is_none_or(|e| e.selections != entry.selections)
1612 {
1613 self.undo_stack.push_back(entry);
1614 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1615 self.undo_stack.pop_front();
1616 }
1617 }
1618 }
1619
1620 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1621 if self
1622 .redo_stack
1623 .back()
1624 .is_none_or(|e| e.selections != entry.selections)
1625 {
1626 self.redo_stack.push_back(entry);
1627 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1628 self.redo_stack.pop_front();
1629 }
1630 }
1631 }
1632}
1633
1634#[derive(Clone, Copy)]
1635pub struct RowHighlightOptions {
1636 pub autoscroll: bool,
1637 pub include_gutter: bool,
1638}
1639
1640impl Default for RowHighlightOptions {
1641 fn default() -> Self {
1642 Self {
1643 autoscroll: Default::default(),
1644 include_gutter: true,
1645 }
1646 }
1647}
1648
1649struct RowHighlight {
1650 index: usize,
1651 range: Range<Anchor>,
1652 color: Hsla,
1653 options: RowHighlightOptions,
1654 type_id: TypeId,
1655}
1656
1657#[derive(Clone, Debug)]
1658struct AddSelectionsState {
1659 groups: Vec<AddSelectionsGroup>,
1660}
1661
1662#[derive(Clone, Debug)]
1663struct AddSelectionsGroup {
1664 above: bool,
1665 stack: Vec<usize>,
1666}
1667
1668#[derive(Clone)]
1669struct SelectNextState {
1670 query: AhoCorasick,
1671 wordwise: bool,
1672 done: bool,
1673}
1674
1675impl std::fmt::Debug for SelectNextState {
1676 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1677 f.debug_struct(std::any::type_name::<Self>())
1678 .field("wordwise", &self.wordwise)
1679 .field("done", &self.done)
1680 .finish()
1681 }
1682}
1683
1684#[derive(Debug)]
1685struct AutocloseRegion {
1686 selection_id: usize,
1687 range: Range<Anchor>,
1688 pair: BracketPair,
1689}
1690
1691#[derive(Debug)]
1692struct SnippetState {
1693 ranges: Vec<Vec<Range<Anchor>>>,
1694 active_index: usize,
1695 choices: Vec<Option<Vec<String>>>,
1696}
1697
1698#[doc(hidden)]
1699pub struct RenameState {
1700 pub range: Range<Anchor>,
1701 pub old_name: Arc<str>,
1702 pub editor: Entity<Editor>,
1703 block_id: CustomBlockId,
1704}
1705
1706struct InvalidationStack<T>(Vec<T>);
1707
1708struct RegisteredEditPredictionDelegate {
1709 provider: Arc<dyn EditPredictionDelegateHandle>,
1710 _subscription: Subscription,
1711}
1712
1713#[derive(Debug, PartialEq, Eq)]
1714pub struct ActiveDiagnosticGroup {
1715 pub active_range: Range<Anchor>,
1716 pub active_message: String,
1717 pub group_id: usize,
1718 pub blocks: HashSet<CustomBlockId>,
1719}
1720
1721#[derive(Debug, PartialEq, Eq)]
1722
1723pub(crate) enum ActiveDiagnostic {
1724 None,
1725 All,
1726 Group(ActiveDiagnosticGroup),
1727}
1728
1729#[derive(Serialize, Deserialize, Clone, Debug)]
1730pub struct ClipboardSelection {
1731 /// The number of bytes in this selection.
1732 pub len: usize,
1733 /// Whether this was a full-line selection.
1734 pub is_entire_line: bool,
1735 /// The indentation of the first line when this content was originally copied.
1736 pub first_line_indent: u32,
1737 #[serde(default)]
1738 pub file_path: Option<PathBuf>,
1739 #[serde(default)]
1740 pub line_range: Option<RangeInclusive<u32>>,
1741}
1742
1743impl ClipboardSelection {
1744 pub fn for_buffer(
1745 len: usize,
1746 is_entire_line: bool,
1747 range: Range<Point>,
1748 buffer: &MultiBufferSnapshot,
1749 project: Option<&Entity<Project>>,
1750 cx: &App,
1751 ) -> Self {
1752 let first_line_indent = buffer
1753 .indent_size_for_line(MultiBufferRow(range.start.row))
1754 .len;
1755
1756 let file_path = util::maybe!({
1757 let project = project?.read(cx);
1758 let file = buffer.file_at(range.start)?;
1759 let project_path = ProjectPath {
1760 worktree_id: file.worktree_id(cx),
1761 path: file.path().clone(),
1762 };
1763 project.absolute_path(&project_path, cx)
1764 });
1765
1766 let line_range = file_path.as_ref().and_then(|_| {
1767 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1768 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1769 if start_excerpt_id == end_excerpt_id {
1770 Some(start_point.row..=end_point.row)
1771 } else {
1772 None
1773 }
1774 });
1775
1776 Self {
1777 len,
1778 is_entire_line,
1779 first_line_indent,
1780 file_path,
1781 line_range,
1782 }
1783 }
1784}
1785
1786// selections, scroll behavior, was newest selection reversed
1787type SelectSyntaxNodeHistoryState = (
1788 Box<[Selection<Anchor>]>,
1789 SelectSyntaxNodeScrollBehavior,
1790 bool,
1791);
1792
1793#[derive(Default)]
1794struct SelectSyntaxNodeHistory {
1795 stack: Vec<SelectSyntaxNodeHistoryState>,
1796 // disable temporarily to allow changing selections without losing the stack
1797 pub disable_clearing: bool,
1798}
1799
1800impl SelectSyntaxNodeHistory {
1801 pub fn try_clear(&mut self) {
1802 if !self.disable_clearing {
1803 self.stack.clear();
1804 }
1805 }
1806
1807 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1808 self.stack.push(selection);
1809 }
1810
1811 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1812 self.stack.pop()
1813 }
1814}
1815
1816enum SelectSyntaxNodeScrollBehavior {
1817 CursorTop,
1818 FitSelection,
1819 CursorBottom,
1820}
1821
1822#[derive(Debug, Clone, Copy)]
1823pub(crate) struct NavigationData {
1824 cursor_anchor: Anchor,
1825 cursor_position: Point,
1826 scroll_anchor: ScrollAnchor,
1827 scroll_top_row: u32,
1828}
1829
1830#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1831pub enum GotoDefinitionKind {
1832 Symbol,
1833 Declaration,
1834 Type,
1835 Implementation,
1836}
1837
1838pub enum FormatTarget {
1839 Buffers(HashSet<Entity<Buffer>>),
1840 Ranges(Vec<Range<MultiBufferPoint>>),
1841}
1842
1843pub(crate) struct FocusedBlock {
1844 id: BlockId,
1845 focus_handle: WeakFocusHandle,
1846}
1847
1848#[derive(Clone, Debug)]
1849pub enum JumpData {
1850 MultiBufferRow {
1851 row: MultiBufferRow,
1852 line_offset_from_top: u32,
1853 },
1854 MultiBufferPoint {
1855 excerpt_id: ExcerptId,
1856 position: Point,
1857 anchor: text::Anchor,
1858 line_offset_from_top: u32,
1859 },
1860}
1861
1862pub enum MultibufferSelectionMode {
1863 First,
1864 All,
1865}
1866
1867#[derive(Clone, Copy, Debug, Default)]
1868pub struct RewrapOptions {
1869 pub override_language_settings: bool,
1870 pub preserve_existing_whitespace: bool,
1871 pub line_length: Option<usize>,
1872}
1873
1874impl Editor {
1875 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1876 let buffer = cx.new(|cx| Buffer::local("", cx));
1877 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1878 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1879 }
1880
1881 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1882 let buffer = cx.new(|cx| Buffer::local("", cx));
1883 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1884 Self::new(EditorMode::full(), buffer, None, window, cx)
1885 }
1886
1887 pub fn auto_height(
1888 min_lines: usize,
1889 max_lines: usize,
1890 window: &mut Window,
1891 cx: &mut Context<Self>,
1892 ) -> Self {
1893 let buffer = cx.new(|cx| Buffer::local("", cx));
1894 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1895 Self::new(
1896 EditorMode::AutoHeight {
1897 min_lines,
1898 max_lines: Some(max_lines),
1899 },
1900 buffer,
1901 None,
1902 window,
1903 cx,
1904 )
1905 }
1906
1907 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1908 /// The editor grows as tall as needed to fit its content.
1909 pub fn auto_height_unbounded(
1910 min_lines: usize,
1911 window: &mut Window,
1912 cx: &mut Context<Self>,
1913 ) -> Self {
1914 let buffer = cx.new(|cx| Buffer::local("", cx));
1915 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1916 Self::new(
1917 EditorMode::AutoHeight {
1918 min_lines,
1919 max_lines: None,
1920 },
1921 buffer,
1922 None,
1923 window,
1924 cx,
1925 )
1926 }
1927
1928 pub fn for_buffer(
1929 buffer: Entity<Buffer>,
1930 project: Option<Entity<Project>>,
1931 window: &mut Window,
1932 cx: &mut Context<Self>,
1933 ) -> Self {
1934 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1935 Self::new(EditorMode::full(), buffer, project, window, cx)
1936 }
1937
1938 pub fn for_multibuffer(
1939 buffer: Entity<MultiBuffer>,
1940 project: Option<Entity<Project>>,
1941 window: &mut Window,
1942 cx: &mut Context<Self>,
1943 ) -> Self {
1944 Self::new(EditorMode::full(), buffer, project, window, cx)
1945 }
1946
1947 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1948 let mut clone = Self::new(
1949 self.mode.clone(),
1950 self.buffer.clone(),
1951 self.project.clone(),
1952 window,
1953 cx,
1954 );
1955 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1956 let snapshot = display_map.snapshot(cx);
1957 clone.display_map.update(cx, |display_map, cx| {
1958 display_map.set_state(&snapshot, cx);
1959 });
1960 snapshot
1961 });
1962 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1963 clone.folds_did_change(cx);
1964 clone.selections.clone_state(&self.selections);
1965 clone
1966 .scroll_manager
1967 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1968 clone.searchable = self.searchable;
1969 clone.read_only = self.read_only;
1970 clone.buffers_with_disabled_indent_guides =
1971 self.buffers_with_disabled_indent_guides.clone();
1972 clone
1973 }
1974
1975 pub fn new(
1976 mode: EditorMode,
1977 buffer: Entity<MultiBuffer>,
1978 project: Option<Entity<Project>>,
1979 window: &mut Window,
1980 cx: &mut Context<Self>,
1981 ) -> Self {
1982 Editor::new_internal(mode, buffer, project, None, window, cx)
1983 }
1984
1985 pub fn refresh_sticky_headers(
1986 &mut self,
1987 display_snapshot: &DisplaySnapshot,
1988 cx: &mut Context<Editor>,
1989 ) {
1990 if !self.mode.is_full() {
1991 return;
1992 }
1993 let multi_buffer = display_snapshot.buffer_snapshot();
1994 let scroll_anchor = self
1995 .scroll_manager
1996 .native_anchor(display_snapshot, cx)
1997 .anchor;
1998 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
1999 return;
2000 };
2001 let buffer = buffer.clone();
2002
2003 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
2004 let max_row = buffer.max_point().row;
2005 let start_row = buffer_visible_start.row.min(max_row);
2006 let end_row = (buffer_visible_start.row + 10).min(max_row);
2007
2008 let syntax = self.style(cx).syntax.clone();
2009 let background_task = cx.background_spawn(async move {
2010 buffer
2011 .outline_items_containing(
2012 Point::new(start_row, 0)..Point::new(end_row, 0),
2013 true,
2014 Some(syntax.as_ref()),
2015 )
2016 .into_iter()
2017 .map(|outline_item| OutlineItem {
2018 depth: outline_item.depth,
2019 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2020 source_range_for_text: Anchor::range_in_buffer(
2021 excerpt_id,
2022 outline_item.source_range_for_text,
2023 ),
2024 text: outline_item.text,
2025 highlight_ranges: outline_item.highlight_ranges,
2026 name_ranges: outline_item.name_ranges,
2027 body_range: outline_item
2028 .body_range
2029 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2030 annotation_range: outline_item
2031 .annotation_range
2032 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2033 })
2034 .collect()
2035 });
2036 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2037 let sticky_headers = background_task.await;
2038 this.update(cx, |this, cx| {
2039 this.sticky_headers = Some(sticky_headers);
2040 cx.notify();
2041 })
2042 .ok();
2043 });
2044 }
2045
2046 fn new_internal(
2047 mode: EditorMode,
2048 multi_buffer: Entity<MultiBuffer>,
2049 project: Option<Entity<Project>>,
2050 display_map: Option<Entity<DisplayMap>>,
2051 window: &mut Window,
2052 cx: &mut Context<Self>,
2053 ) -> Self {
2054 debug_assert!(
2055 display_map.is_none() || mode.is_minimap(),
2056 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2057 );
2058
2059 let full_mode = mode.is_full();
2060 let is_minimap = mode.is_minimap();
2061 let diagnostics_max_severity = if full_mode {
2062 EditorSettings::get_global(cx)
2063 .diagnostics_max_severity
2064 .unwrap_or(DiagnosticSeverity::Hint)
2065 } else {
2066 DiagnosticSeverity::Off
2067 };
2068 let style = window.text_style();
2069 let font_size = style.font_size.to_pixels(window.rem_size());
2070 let editor = cx.entity().downgrade();
2071 let fold_placeholder = FoldPlaceholder {
2072 constrain_width: false,
2073 render: Arc::new(move |fold_id, fold_range, cx| {
2074 let editor = editor.clone();
2075 FoldPlaceholder::fold_element(fold_id, cx)
2076 .cursor_pointer()
2077 .child("⋯")
2078 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2079 .on_click(move |_, _window, cx| {
2080 editor
2081 .update(cx, |editor, cx| {
2082 editor.unfold_ranges(
2083 &[fold_range.start..fold_range.end],
2084 true,
2085 false,
2086 cx,
2087 );
2088 cx.stop_propagation();
2089 })
2090 .ok();
2091 })
2092 .into_any()
2093 }),
2094 merge_adjacent: true,
2095 ..FoldPlaceholder::default()
2096 };
2097 let display_map = display_map.unwrap_or_else(|| {
2098 cx.new(|cx| {
2099 DisplayMap::new(
2100 multi_buffer.clone(),
2101 style.font(),
2102 font_size,
2103 None,
2104 FILE_HEADER_HEIGHT,
2105 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2106 fold_placeholder,
2107 diagnostics_max_severity,
2108 cx,
2109 )
2110 })
2111 });
2112
2113 let selections = SelectionsCollection::new();
2114
2115 let blink_manager = cx.new(|cx| {
2116 let mut blink_manager = BlinkManager::new(
2117 CURSOR_BLINK_INTERVAL,
2118 |cx| EditorSettings::get_global(cx).cursor_blink,
2119 cx,
2120 );
2121 if is_minimap {
2122 blink_manager.disable(cx);
2123 }
2124 blink_manager
2125 });
2126
2127 let soft_wrap_mode_override =
2128 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2129
2130 let mut project_subscriptions = Vec::new();
2131 if full_mode && let Some(project) = project.as_ref() {
2132 project_subscriptions.push(cx.subscribe_in(
2133 project,
2134 window,
2135 |editor, _, event, window, cx| match event {
2136 project::Event::RefreshCodeLens => {
2137 // we always query lens with actions, without storing them, always refreshing them
2138 }
2139 project::Event::RefreshInlayHints {
2140 server_id,
2141 request_id,
2142 } => {
2143 editor.refresh_inlay_hints(
2144 InlayHintRefreshReason::RefreshRequested {
2145 server_id: *server_id,
2146 request_id: *request_id,
2147 },
2148 cx,
2149 );
2150 }
2151 project::Event::RefreshSemanticTokens {
2152 server_id,
2153 request_id,
2154 } => {
2155 editor.refresh_semantic_tokens(
2156 None,
2157 Some(RefreshForServer {
2158 server_id: *server_id,
2159 request_id: *request_id,
2160 }),
2161 cx,
2162 );
2163 }
2164 project::Event::LanguageServerRemoved(_) => {
2165 editor.registered_buffers.clear();
2166 editor.register_visible_buffers(cx);
2167 editor.invalidate_semantic_tokens(None);
2168 editor.refresh_runnables(None, window, cx);
2169 editor.update_lsp_data(None, window, cx);
2170 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2171 }
2172 project::Event::SnippetEdit(id, snippet_edits) => {
2173 // todo(lw): Non singletons
2174 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2175 let snapshot = buffer.read(cx).snapshot();
2176 let focus_handle = editor.focus_handle(cx);
2177 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2178 for (range, snippet) in snippet_edits {
2179 let buffer_range =
2180 language::range_from_lsp(*range).to_offset(&snapshot);
2181 editor
2182 .insert_snippet(
2183 &[MultiBufferOffset(buffer_range.start)
2184 ..MultiBufferOffset(buffer_range.end)],
2185 snippet.clone(),
2186 window,
2187 cx,
2188 )
2189 .ok();
2190 }
2191 }
2192 }
2193 }
2194 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2195 let buffer_id = *buffer_id;
2196 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2197 editor.register_buffer(buffer_id, cx);
2198 editor.refresh_runnables(Some(buffer_id), window, cx);
2199 editor.update_lsp_data(Some(buffer_id), window, cx);
2200 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2201 refresh_linked_ranges(editor, window, cx);
2202 editor.refresh_code_actions(window, cx);
2203 editor.refresh_document_highlights(cx);
2204 }
2205 }
2206
2207 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2208 let Some(workspace) = editor.workspace() else {
2209 return;
2210 };
2211 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2212 else {
2213 return;
2214 };
2215
2216 if active_editor.entity_id() == cx.entity_id() {
2217 let entity_id = cx.entity_id();
2218 workspace.update(cx, |this, cx| {
2219 this.panes_mut()
2220 .iter_mut()
2221 .filter(|pane| pane.entity_id() != entity_id)
2222 .for_each(|p| {
2223 p.update(cx, |pane, _| {
2224 pane.nav_history_mut().rename_item(
2225 entity_id,
2226 project_path.clone(),
2227 abs_path.clone().into(),
2228 );
2229 })
2230 });
2231 });
2232
2233 Self::open_transaction_for_hidden_buffers(
2234 workspace,
2235 transaction.clone(),
2236 "Rename".to_string(),
2237 window,
2238 cx,
2239 );
2240 }
2241 }
2242
2243 project::Event::WorkspaceEditApplied(transaction) => {
2244 let Some(workspace) = editor.workspace() else {
2245 return;
2246 };
2247 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2248 else {
2249 return;
2250 };
2251
2252 if active_editor.entity_id() == cx.entity_id() {
2253 Self::open_transaction_for_hidden_buffers(
2254 workspace,
2255 transaction.clone(),
2256 "LSP Edit".to_string(),
2257 window,
2258 cx,
2259 );
2260 }
2261 }
2262
2263 _ => {}
2264 },
2265 ));
2266 if let Some(task_inventory) = project
2267 .read(cx)
2268 .task_store()
2269 .read(cx)
2270 .task_inventory()
2271 .cloned()
2272 {
2273 project_subscriptions.push(cx.observe_in(
2274 &task_inventory,
2275 window,
2276 |editor, _, window, cx| {
2277 editor.refresh_runnables(None, window, cx);
2278 },
2279 ));
2280 };
2281
2282 project_subscriptions.push(cx.subscribe_in(
2283 &project.read(cx).breakpoint_store(),
2284 window,
2285 |editor, _, event, window, cx| match event {
2286 BreakpointStoreEvent::ClearDebugLines => {
2287 editor.clear_row_highlights::<ActiveDebugLine>();
2288 editor.refresh_inline_values(cx);
2289 }
2290 BreakpointStoreEvent::SetDebugLine => {
2291 if editor.go_to_active_debug_line(window, cx) {
2292 cx.stop_propagation();
2293 }
2294
2295 editor.refresh_inline_values(cx);
2296 }
2297 _ => {}
2298 },
2299 ));
2300 let git_store = project.read(cx).git_store().clone();
2301 let project = project.clone();
2302 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2303 if let GitStoreEvent::RepositoryAdded = event {
2304 this.load_diff_task = Some(
2305 update_uncommitted_diff_for_buffer(
2306 cx.entity(),
2307 &project,
2308 this.buffer.read(cx).all_buffers(),
2309 this.buffer.clone(),
2310 cx,
2311 )
2312 .shared(),
2313 );
2314 }
2315 }));
2316 }
2317
2318 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2319
2320 let inlay_hint_settings =
2321 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2322 let focus_handle = cx.focus_handle();
2323 if !is_minimap {
2324 cx.on_focus(&focus_handle, window, Self::handle_focus)
2325 .detach();
2326 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2327 .detach();
2328 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2329 .detach();
2330 cx.on_blur(&focus_handle, window, Self::handle_blur)
2331 .detach();
2332 cx.observe_pending_input(window, Self::observe_pending_input)
2333 .detach();
2334 }
2335
2336 let show_indent_guides =
2337 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2338 Some(false)
2339 } else {
2340 None
2341 };
2342
2343 let breakpoint_store = match (&mode, project.as_ref()) {
2344 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2345 _ => None,
2346 };
2347
2348 let mut code_action_providers = Vec::new();
2349 let mut load_uncommitted_diff = None;
2350 if let Some(project) = project.clone() {
2351 load_uncommitted_diff = Some(
2352 update_uncommitted_diff_for_buffer(
2353 cx.entity(),
2354 &project,
2355 multi_buffer.read(cx).all_buffers(),
2356 multi_buffer.clone(),
2357 cx,
2358 )
2359 .shared(),
2360 );
2361 code_action_providers.push(Rc::new(project) as Rc<_>);
2362 }
2363
2364 let mut editor = Self {
2365 focus_handle,
2366 show_cursor_when_unfocused: false,
2367 last_focused_descendant: None,
2368 buffer: multi_buffer.clone(),
2369 display_map: display_map.clone(),
2370 placeholder_display_map: None,
2371 selections,
2372 scroll_manager: ScrollManager::new(cx),
2373 columnar_selection_state: None,
2374 add_selections_state: None,
2375 select_next_state: None,
2376 select_prev_state: None,
2377 selection_history: SelectionHistory::default(),
2378 defer_selection_effects: false,
2379 deferred_selection_effects_state: None,
2380 autoclose_regions: Vec::new(),
2381 snippet_stack: InvalidationStack::default(),
2382 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2383 ime_transaction: None,
2384 active_diagnostics: ActiveDiagnostic::None,
2385 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2386 inline_diagnostics_update: Task::ready(()),
2387 inline_diagnostics: Vec::new(),
2388 soft_wrap_mode_override,
2389 diagnostics_max_severity,
2390 hard_wrap: None,
2391 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2392 semantics_provider: project
2393 .as_ref()
2394 .map(|project| Rc::new(project.downgrade()) as _),
2395 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2396 project,
2397 blink_manager: blink_manager.clone(),
2398 show_local_selections: true,
2399 show_scrollbars: ScrollbarAxes {
2400 horizontal: full_mode,
2401 vertical: full_mode,
2402 },
2403 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2404 offset_content: !matches!(mode, EditorMode::SingleLine),
2405 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2406 show_gutter: full_mode,
2407 show_line_numbers: (!full_mode).then_some(false),
2408 use_relative_line_numbers: None,
2409 disable_expand_excerpt_buttons: !full_mode,
2410 delegate_expand_excerpts: false,
2411 delegate_stage_and_restore: false,
2412 delegate_open_excerpts: false,
2413 enable_lsp_data: true,
2414 enable_runnables: true,
2415 show_git_diff_gutter: None,
2416 show_code_actions: None,
2417 show_runnables: None,
2418 show_breakpoints: None,
2419 show_diff_review_button: false,
2420 show_wrap_guides: None,
2421 show_indent_guides,
2422 buffers_with_disabled_indent_guides: HashSet::default(),
2423 highlight_order: 0,
2424 highlighted_rows: HashMap::default(),
2425 background_highlights: HashMap::default(),
2426 gutter_highlights: HashMap::default(),
2427 scrollbar_marker_state: ScrollbarMarkerState::default(),
2428 active_indent_guides_state: ActiveIndentGuidesState::default(),
2429 nav_history: None,
2430 context_menu: RefCell::new(None),
2431 context_menu_options: None,
2432 mouse_context_menu: None,
2433 completion_tasks: Vec::new(),
2434 inline_blame_popover: None,
2435 inline_blame_popover_show_task: None,
2436 signature_help_state: SignatureHelpState::default(),
2437 auto_signature_help: None,
2438 find_all_references_task_sources: Vec::new(),
2439 next_completion_id: 0,
2440 next_inlay_id: 0,
2441 code_action_providers,
2442 available_code_actions: None,
2443 code_actions_task: None,
2444 quick_selection_highlight_task: None,
2445 debounced_selection_highlight_task: None,
2446 debounced_selection_highlight_complete: false,
2447 document_highlights_task: None,
2448 linked_editing_range_task: None,
2449 pending_rename: None,
2450 searchable: !is_minimap,
2451 cursor_shape: EditorSettings::get_global(cx)
2452 .cursor_shape
2453 .unwrap_or_default(),
2454 cursor_offset_on_selection: false,
2455 current_line_highlight: None,
2456 autoindent_mode: Some(AutoindentMode::EachLine),
2457 collapse_matches: false,
2458 workspace: None,
2459 input_enabled: !is_minimap,
2460 expects_character_input: !is_minimap,
2461 use_modal_editing: full_mode,
2462 read_only: is_minimap,
2463 use_autoclose: true,
2464 use_auto_surround: true,
2465 auto_replace_emoji_shortcode: false,
2466 jsx_tag_auto_close_enabled_in_any_buffer: false,
2467 leader_id: None,
2468 remote_id: None,
2469 hover_state: HoverState::default(),
2470 pending_mouse_down: None,
2471 prev_pressure_stage: None,
2472 hovered_link_state: None,
2473 edit_prediction_provider: None,
2474 active_edit_prediction: None,
2475 stale_edit_prediction_in_menu: None,
2476 edit_prediction_preview: EditPredictionPreview::Inactive {
2477 released_too_fast: false,
2478 },
2479 inline_diagnostics_enabled: full_mode,
2480 diagnostics_enabled: full_mode,
2481 word_completions_enabled: full_mode,
2482 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2483 gutter_hovered: false,
2484 pixel_position_of_newest_cursor: None,
2485 last_bounds: None,
2486 last_position_map: None,
2487 expect_bounds_change: None,
2488 gutter_dimensions: GutterDimensions::default(),
2489 style: None,
2490 show_cursor_names: false,
2491 hovered_cursors: HashMap::default(),
2492 next_editor_action_id: EditorActionId::default(),
2493 editor_actions: Rc::default(),
2494 edit_predictions_hidden_for_vim_mode: false,
2495 show_edit_predictions_override: None,
2496 show_completions_on_input_override: None,
2497 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2498 edit_prediction_settings: EditPredictionSettings::Disabled,
2499 in_leading_whitespace: false,
2500 custom_context_menu: None,
2501 show_git_blame_gutter: false,
2502 show_git_blame_inline: false,
2503 show_selection_menu: None,
2504 show_git_blame_inline_delay_task: None,
2505 git_blame_inline_enabled: full_mode
2506 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2507 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2508 buffer_serialization: is_minimap.not().then(|| {
2509 BufferSerialization::new(
2510 ProjectSettings::get_global(cx)
2511 .session
2512 .restore_unsaved_buffers,
2513 )
2514 }),
2515 blame: None,
2516 blame_subscription: None,
2517
2518 breakpoint_store,
2519 gutter_breakpoint_indicator: (None, None),
2520 gutter_diff_review_indicator: (None, None),
2521 diff_review_drag_state: None,
2522 diff_review_overlays: Vec::new(),
2523 stored_review_comments: Vec::new(),
2524 next_review_comment_id: 0,
2525 hovered_diff_hunk_row: None,
2526 _subscriptions: (!is_minimap)
2527 .then(|| {
2528 vec![
2529 cx.observe(&multi_buffer, Self::on_buffer_changed),
2530 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2531 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2532 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2533 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2534 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2535 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2536 cx.observe_window_activation(window, |editor, window, cx| {
2537 let active = window.is_window_active();
2538 editor.blink_manager.update(cx, |blink_manager, cx| {
2539 if active {
2540 blink_manager.enable(cx);
2541 } else {
2542 blink_manager.disable(cx);
2543 }
2544 });
2545 if active {
2546 editor.show_mouse_cursor(cx);
2547 }
2548 }),
2549 ]
2550 })
2551 .unwrap_or_default(),
2552 runnables: RunnableData::new(),
2553 pull_diagnostics_task: Task::ready(()),
2554 colors: None,
2555 refresh_colors_task: Task::ready(()),
2556 use_document_folding_ranges: false,
2557 refresh_folding_ranges_task: Task::ready(()),
2558 inlay_hints: None,
2559 next_color_inlay_id: 0,
2560 post_scroll_update: Task::ready(()),
2561 linked_edit_ranges: Default::default(),
2562 in_project_search: false,
2563 previous_search_ranges: None,
2564 breadcrumb_header: None,
2565 focused_block: None,
2566 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2567 addons: HashMap::default(),
2568 registered_buffers: HashMap::default(),
2569 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2570 selection_mark_mode: false,
2571 toggle_fold_multiple_buffers: Task::ready(()),
2572 serialize_selections: Task::ready(()),
2573 serialize_folds: Task::ready(()),
2574 text_style_refinement: None,
2575 load_diff_task: load_uncommitted_diff,
2576 temporary_diff_override: false,
2577 mouse_cursor_hidden: false,
2578 minimap: None,
2579 hide_mouse_mode: EditorSettings::get_global(cx)
2580 .hide_mouse
2581 .unwrap_or_default(),
2582 change_list: ChangeList::new(),
2583 mode,
2584 selection_drag_state: SelectionDragState::None,
2585 folding_newlines: Task::ready(()),
2586 lookup_key: None,
2587 select_next_is_case_sensitive: None,
2588 on_local_selections_changed: None,
2589 suppress_selection_callback: false,
2590 applicable_language_settings: HashMap::default(),
2591 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2592 accent_data: None,
2593 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2594 number_deleted_lines: false,
2595 refresh_matching_bracket_highlights_task: Task::ready(()),
2596 refresh_document_symbols_task: Task::ready(()).shared(),
2597 lsp_document_symbols: HashMap::default(),
2598 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2599 outline_symbols_at_cursor: None,
2600 sticky_headers_task: Task::ready(()),
2601 sticky_headers: None,
2602 colorize_brackets_task: Task::ready(()),
2603 };
2604
2605 if is_minimap {
2606 return editor;
2607 }
2608
2609 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2610 editor.accent_data = editor.fetch_accent_data(cx);
2611
2612 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2613 editor
2614 ._subscriptions
2615 .push(cx.observe(breakpoints, |_, _, cx| {
2616 cx.notify();
2617 }));
2618 }
2619 editor._subscriptions.extend(project_subscriptions);
2620
2621 editor._subscriptions.push(cx.subscribe_in(
2622 &cx.entity(),
2623 window,
2624 |editor, _, e: &EditorEvent, window, cx| match e {
2625 EditorEvent::ScrollPositionChanged { local, .. } => {
2626 if *local {
2627 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2628 editor.inline_blame_popover.take();
2629 let snapshot = editor.snapshot(window, cx);
2630 let new_anchor = editor
2631 .scroll_manager
2632 .native_anchor(&snapshot.display_snapshot, cx);
2633 editor.update_restoration_data(cx, move |data| {
2634 data.scroll_position = (
2635 new_anchor.top_row(snapshot.buffer_snapshot()),
2636 new_anchor.offset,
2637 );
2638 });
2639
2640 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2641 cx.background_executor()
2642 .timer(Duration::from_millis(50))
2643 .await;
2644 editor
2645 .update_in(cx, |editor, window, cx| {
2646 editor.update_data_on_scroll(window, cx)
2647 })
2648 .ok();
2649 });
2650 }
2651 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2652 }
2653 EditorEvent::Edited { .. } => {
2654 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2655 .map(|vim_mode| vim_mode.0)
2656 .unwrap_or(false);
2657 if !vim_mode {
2658 let display_map = editor.display_snapshot(cx);
2659 let selections = editor.selections.all_adjusted_display(&display_map);
2660 let pop_state = editor
2661 .change_list
2662 .last()
2663 .map(|previous| {
2664 previous.len() == selections.len()
2665 && previous.iter().enumerate().all(|(ix, p)| {
2666 p.to_display_point(&display_map).row()
2667 == selections[ix].head().row()
2668 })
2669 })
2670 .unwrap_or(false);
2671 let new_positions = selections
2672 .into_iter()
2673 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2674 .collect();
2675 editor
2676 .change_list
2677 .push_to_change_list(pop_state, new_positions);
2678 }
2679 }
2680 _ => (),
2681 },
2682 ));
2683
2684 if let Some(dap_store) = editor
2685 .project
2686 .as_ref()
2687 .map(|project| project.read(cx).dap_store())
2688 {
2689 let weak_editor = cx.weak_entity();
2690
2691 editor
2692 ._subscriptions
2693 .push(
2694 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2695 let session_entity = cx.entity();
2696 weak_editor
2697 .update(cx, |editor, cx| {
2698 editor._subscriptions.push(
2699 cx.subscribe(&session_entity, Self::on_debug_session_event),
2700 );
2701 })
2702 .ok();
2703 }),
2704 );
2705
2706 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2707 editor
2708 ._subscriptions
2709 .push(cx.subscribe(&session, Self::on_debug_session_event));
2710 }
2711 }
2712
2713 // skip adding the initial selection to selection history
2714 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2715 editor.end_selection(window, cx);
2716 editor.selection_history.mode = SelectionHistoryMode::Normal;
2717
2718 editor.scroll_manager.show_scrollbars(window, cx);
2719 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2720
2721 if full_mode {
2722 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2723 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2724
2725 if editor.git_blame_inline_enabled {
2726 editor.start_git_blame_inline(false, window, cx);
2727 }
2728
2729 editor.go_to_active_debug_line(window, cx);
2730
2731 editor.minimap =
2732 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2733 editor.colors = Some(LspColorData::new(cx));
2734 editor.use_document_folding_ranges = true;
2735 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2736
2737 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2738 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2739 }
2740 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2741 }
2742
2743 editor
2744 }
2745
2746 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2747 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2748 }
2749
2750 pub fn deploy_mouse_context_menu(
2751 &mut self,
2752 position: gpui::Point<Pixels>,
2753 context_menu: Entity<ContextMenu>,
2754 window: &mut Window,
2755 cx: &mut Context<Self>,
2756 ) {
2757 self.mouse_context_menu = Some(MouseContextMenu::new(
2758 self,
2759 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2760 context_menu,
2761 window,
2762 cx,
2763 ));
2764 }
2765
2766 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2767 self.mouse_context_menu
2768 .as_ref()
2769 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2770 }
2771
2772 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2773 if self
2774 .selections
2775 .pending_anchor()
2776 .is_some_and(|pending_selection| {
2777 let snapshot = self.buffer().read(cx).snapshot(cx);
2778 pending_selection.range().includes(range, &snapshot)
2779 })
2780 {
2781 return true;
2782 }
2783
2784 self.selections
2785 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2786 .into_iter()
2787 .any(|selection| {
2788 // This is needed to cover a corner case, if we just check for an existing
2789 // selection in the fold range, having a cursor at the start of the fold
2790 // marks it as selected. Non-empty selections don't cause this.
2791 let length = selection.end - selection.start;
2792 length > 0
2793 })
2794 }
2795
2796 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2797 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2798 }
2799
2800 fn key_context_internal(
2801 &self,
2802 has_active_edit_prediction: bool,
2803 window: &mut Window,
2804 cx: &mut App,
2805 ) -> KeyContext {
2806 let mut key_context = KeyContext::new_with_defaults();
2807 key_context.add("Editor");
2808 let mode = match self.mode {
2809 EditorMode::SingleLine => "single_line",
2810 EditorMode::AutoHeight { .. } => "auto_height",
2811 EditorMode::Minimap { .. } => "minimap",
2812 EditorMode::Full { .. } => "full",
2813 };
2814
2815 if EditorSettings::jupyter_enabled(cx) {
2816 key_context.add("jupyter");
2817 }
2818
2819 key_context.set("mode", mode);
2820 if self.pending_rename.is_some() {
2821 key_context.add("renaming");
2822 }
2823
2824 if let Some(snippet_stack) = self.snippet_stack.last() {
2825 key_context.add("in_snippet");
2826
2827 if snippet_stack.active_index > 0 {
2828 key_context.add("has_previous_tabstop");
2829 }
2830
2831 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2832 key_context.add("has_next_tabstop");
2833 }
2834 }
2835
2836 match self.context_menu.borrow().as_ref() {
2837 Some(CodeContextMenu::Completions(menu)) => {
2838 if menu.visible() {
2839 key_context.add("menu");
2840 key_context.add("showing_completions");
2841 }
2842 }
2843 Some(CodeContextMenu::CodeActions(menu)) => {
2844 if menu.visible() {
2845 key_context.add("menu");
2846 key_context.add("showing_code_actions")
2847 }
2848 }
2849 None => {}
2850 }
2851
2852 if self.signature_help_state.has_multiple_signatures() {
2853 key_context.add("showing_signature_help");
2854 }
2855
2856 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2857 if !self.focus_handle(cx).contains_focused(window, cx)
2858 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2859 {
2860 for addon in self.addons.values() {
2861 addon.extend_key_context(&mut key_context, cx)
2862 }
2863 }
2864
2865 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2866 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2867 Some(
2868 file.full_path(cx)
2869 .extension()?
2870 .to_string_lossy()
2871 .to_lowercase(),
2872 )
2873 }) {
2874 key_context.set("extension", extension);
2875 }
2876 } else {
2877 key_context.add("multibuffer");
2878 }
2879
2880 if has_active_edit_prediction {
2881 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2882 key_context.add("copilot_suggestion");
2883 }
2884
2885 if self.in_leading_whitespace {
2886 key_context.add("in_leading_whitespace");
2887 }
2888 if self.edit_prediction_requires_modifier() {
2889 key_context.set("edit_prediction_mode", "subtle")
2890 } else {
2891 key_context.set("edit_prediction_mode", "eager");
2892 }
2893
2894 if self.selection_mark_mode {
2895 key_context.add("selection_mode");
2896 }
2897
2898 let disjoint = self.selections.disjoint_anchors();
2899 if matches!(
2900 &self.mode,
2901 EditorMode::SingleLine | EditorMode::AutoHeight { .. }
2902 ) && let [selection] = disjoint
2903 && selection.start == selection.end
2904 {
2905 let snapshot = self.snapshot(window, cx);
2906 let snapshot = snapshot.buffer_snapshot();
2907 let caret_offset = selection.end.to_offset(snapshot);
2908
2909 if caret_offset == MultiBufferOffset(0) {
2910 key_context.add("start_of_input");
2911 }
2912
2913 if caret_offset == snapshot.len() {
2914 key_context.add("end_of_input");
2915 }
2916 }
2917
2918 if self.has_any_expanded_diff_hunks(cx) {
2919 key_context.add("diffs_expanded");
2920 }
2921
2922 key_context
2923 }
2924
2925 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2926 self.last_bounds.as_ref()
2927 }
2928
2929 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2930 if self.mouse_cursor_hidden {
2931 self.mouse_cursor_hidden = false;
2932 cx.notify();
2933 }
2934 }
2935
2936 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2937 let hide_mouse_cursor = match origin {
2938 HideMouseCursorOrigin::TypingAction => {
2939 matches!(
2940 self.hide_mouse_mode,
2941 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2942 )
2943 }
2944 HideMouseCursorOrigin::MovementAction => {
2945 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2946 }
2947 };
2948 if self.mouse_cursor_hidden != hide_mouse_cursor {
2949 self.mouse_cursor_hidden = hide_mouse_cursor;
2950 cx.notify();
2951 }
2952 }
2953
2954 fn accept_edit_prediction_keystroke(
2955 &self,
2956 granularity: EditPredictionGranularity,
2957 window: &mut Window,
2958 cx: &mut App,
2959 ) -> Option<gpui::KeybindingKeystroke> {
2960 let key_context = self.key_context_internal(true, window, cx);
2961
2962 let bindings =
2963 match granularity {
2964 EditPredictionGranularity::Word => window
2965 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2966 EditPredictionGranularity::Line => window
2967 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2968 EditPredictionGranularity::Full => {
2969 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2970 }
2971 };
2972
2973 bindings
2974 .into_iter()
2975 .rev()
2976 .find_map(|binding| match binding.keystrokes() {
2977 [keystroke, ..] => Some(keystroke.clone()),
2978 _ => None,
2979 })
2980 }
2981
2982 fn preview_edit_prediction_keystroke(
2983 &self,
2984 window: &mut Window,
2985 cx: &mut App,
2986 ) -> Option<gpui::KeybindingKeystroke> {
2987 let key_context = self.key_context_internal(true, window, cx);
2988 let bindings = window.bindings_for_action_in_context(&AcceptEditPrediction, key_context);
2989 bindings
2990 .into_iter()
2991 .rev()
2992 .find_map(|binding| match binding.keystrokes() {
2993 [keystroke, ..] if keystroke.modifiers().modified() => Some(keystroke.clone()),
2994 _ => None,
2995 })
2996 }
2997
2998 fn edit_prediction_preview_modifiers_held(
2999 &self,
3000 modifiers: &Modifiers,
3001 window: &mut Window,
3002 cx: &mut App,
3003 ) -> bool {
3004 let key_context = self.key_context_internal(true, window, cx);
3005 let actions: [&dyn Action; 3] = [
3006 &AcceptEditPrediction,
3007 &AcceptNextWordEditPrediction,
3008 &AcceptNextLineEditPrediction,
3009 ];
3010
3011 actions.into_iter().any(|action| {
3012 window
3013 .bindings_for_action_in_context(action, key_context.clone())
3014 .into_iter()
3015 .rev()
3016 .any(|binding| {
3017 binding.keystrokes().first().is_some_and(|keystroke| {
3018 keystroke.modifiers().modified() && keystroke.modifiers() == modifiers
3019 })
3020 })
3021 })
3022 }
3023
3024 fn edit_prediction_cursor_popover_prefers_preview(
3025 &self,
3026 completion: &EditPredictionState,
3027 ) -> bool {
3028 match &completion.completion {
3029 EditPrediction::Edit {
3030 edits, snapshot, ..
3031 } => {
3032 let mut start_row: Option<u32> = None;
3033 let mut end_row: Option<u32> = None;
3034
3035 for (range, text) in edits {
3036 let edit_start_row = range.start.text_anchor.to_point(snapshot).row;
3037 let old_end_row = range.end.text_anchor.to_point(snapshot).row;
3038 let inserted_newline_count = text
3039 .as_ref()
3040 .chars()
3041 .filter(|character| *character == '\n')
3042 .count() as u32;
3043 let deleted_newline_count = old_end_row - edit_start_row;
3044 let preview_end_row = edit_start_row + inserted_newline_count;
3045
3046 start_row =
3047 Some(start_row.map_or(edit_start_row, |row| row.min(edit_start_row)));
3048 end_row = Some(end_row.map_or(preview_end_row, |row| row.max(preview_end_row)));
3049
3050 if deleted_newline_count > 1 {
3051 end_row = Some(end_row.map_or(old_end_row, |row| row.max(old_end_row)));
3052 }
3053 }
3054
3055 start_row
3056 .zip(end_row)
3057 .is_some_and(|(start_row, end_row)| end_row > start_row)
3058 }
3059 EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => false,
3060 }
3061 }
3062
3063 fn edit_prediction_keybind_display(
3064 &self,
3065 surface: EditPredictionKeybindSurface,
3066 window: &mut Window,
3067 cx: &mut App,
3068 ) -> EditPredictionKeybindDisplay {
3069 let accept_keystroke =
3070 self.accept_edit_prediction_keystroke(EditPredictionGranularity::Full, window, cx);
3071 let preview_keystroke = self.preview_edit_prediction_keystroke(window, cx);
3072
3073 let action = match surface {
3074 EditPredictionKeybindSurface::Inline
3075 | EditPredictionKeybindSurface::CursorPopoverCompact => {
3076 if self.edit_prediction_requires_modifier() {
3077 EditPredictionKeybindAction::Preview
3078 } else {
3079 EditPredictionKeybindAction::Accept
3080 }
3081 }
3082 EditPredictionKeybindSurface::CursorPopoverExpanded => self
3083 .active_edit_prediction
3084 .as_ref()
3085 .filter(|completion| {
3086 self.edit_prediction_cursor_popover_prefers_preview(completion)
3087 })
3088 .map_or(EditPredictionKeybindAction::Accept, |_| {
3089 EditPredictionKeybindAction::Preview
3090 }),
3091 };
3092 #[cfg(test)]
3093 let preview_copy = preview_keystroke.clone();
3094 #[cfg(test)]
3095 let accept_copy = accept_keystroke.clone();
3096
3097 let displayed_keystroke = match surface {
3098 EditPredictionKeybindSurface::Inline => match action {
3099 EditPredictionKeybindAction::Accept => accept_keystroke,
3100 EditPredictionKeybindAction::Preview => preview_keystroke,
3101 },
3102 EditPredictionKeybindSurface::CursorPopoverCompact
3103 | EditPredictionKeybindSurface::CursorPopoverExpanded => match action {
3104 EditPredictionKeybindAction::Accept => accept_keystroke,
3105 EditPredictionKeybindAction::Preview => {
3106 preview_keystroke.or_else(|| accept_keystroke.clone())
3107 }
3108 },
3109 };
3110
3111 let missing_accept_keystroke = displayed_keystroke.is_none();
3112
3113 EditPredictionKeybindDisplay {
3114 #[cfg(test)]
3115 accept_keystroke: accept_copy,
3116 #[cfg(test)]
3117 preview_keystroke: preview_copy,
3118 displayed_keystroke,
3119 action,
3120 missing_accept_keystroke,
3121 show_hold_label: matches!(surface, EditPredictionKeybindSurface::CursorPopoverCompact)
3122 && self.edit_prediction_preview.released_too_fast(),
3123 }
3124 }
3125
3126 pub fn new_file(
3127 workspace: &mut Workspace,
3128 _: &workspace::NewFile,
3129 window: &mut Window,
3130 cx: &mut Context<Workspace>,
3131 ) {
3132 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3133 "Failed to create buffer",
3134 window,
3135 cx,
3136 |e, _, _| match e.error_code() {
3137 ErrorCode::RemoteUpgradeRequired => Some(format!(
3138 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3139 e.error_tag("required").unwrap_or("the latest version")
3140 )),
3141 _ => None,
3142 },
3143 );
3144 }
3145
3146 pub fn new_in_workspace(
3147 workspace: &mut Workspace,
3148 window: &mut Window,
3149 cx: &mut Context<Workspace>,
3150 ) -> Task<Result<Entity<Editor>>> {
3151 let project = workspace.project().clone();
3152 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3153
3154 cx.spawn_in(window, async move |workspace, cx| {
3155 let buffer = create.await?;
3156 workspace.update_in(cx, |workspace, window, cx| {
3157 let editor =
3158 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3159 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3160 editor
3161 })
3162 })
3163 }
3164
3165 fn new_file_vertical(
3166 workspace: &mut Workspace,
3167 _: &workspace::NewFileSplitVertical,
3168 window: &mut Window,
3169 cx: &mut Context<Workspace>,
3170 ) {
3171 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3172 }
3173
3174 fn new_file_horizontal(
3175 workspace: &mut Workspace,
3176 _: &workspace::NewFileSplitHorizontal,
3177 window: &mut Window,
3178 cx: &mut Context<Workspace>,
3179 ) {
3180 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3181 }
3182
3183 fn new_file_split(
3184 workspace: &mut Workspace,
3185 action: &workspace::NewFileSplit,
3186 window: &mut Window,
3187 cx: &mut Context<Workspace>,
3188 ) {
3189 Self::new_file_in_direction(workspace, action.0, window, cx)
3190 }
3191
3192 fn new_file_in_direction(
3193 workspace: &mut Workspace,
3194 direction: SplitDirection,
3195 window: &mut Window,
3196 cx: &mut Context<Workspace>,
3197 ) {
3198 let project = workspace.project().clone();
3199 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3200
3201 cx.spawn_in(window, async move |workspace, cx| {
3202 let buffer = create.await?;
3203 workspace.update_in(cx, move |workspace, window, cx| {
3204 workspace.split_item(
3205 direction,
3206 Box::new(
3207 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3208 ),
3209 window,
3210 cx,
3211 )
3212 })?;
3213 anyhow::Ok(())
3214 })
3215 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3216 match e.error_code() {
3217 ErrorCode::RemoteUpgradeRequired => Some(format!(
3218 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3219 e.error_tag("required").unwrap_or("the latest version")
3220 )),
3221 _ => None,
3222 }
3223 });
3224 }
3225
3226 pub fn leader_id(&self) -> Option<CollaboratorId> {
3227 self.leader_id
3228 }
3229
3230 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3231 &self.buffer
3232 }
3233
3234 pub fn project(&self) -> Option<&Entity<Project>> {
3235 self.project.as_ref()
3236 }
3237
3238 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3239 self.workspace.as_ref()?.0.upgrade()
3240 }
3241
3242 /// Detaches a task and shows an error notification in the workspace if available,
3243 /// otherwise just logs the error.
3244 pub fn detach_and_notify_err<R, E>(
3245 &self,
3246 task: Task<Result<R, E>>,
3247 window: &mut Window,
3248 cx: &mut App,
3249 ) where
3250 E: std::fmt::Debug + std::fmt::Display + 'static,
3251 R: 'static,
3252 {
3253 if let Some(workspace) = self.workspace() {
3254 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3255 } else {
3256 task.detach_and_log_err(cx);
3257 }
3258 }
3259
3260 /// Returns the workspace serialization ID if this editor should be serialized.
3261 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3262 self.workspace
3263 .as_ref()
3264 .filter(|_| self.should_serialize_buffer())
3265 .and_then(|workspace| workspace.1)
3266 }
3267
3268 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3269 self.buffer().read(cx).title(cx)
3270 }
3271
3272 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3273 let git_blame_gutter_max_author_length = self
3274 .render_git_blame_gutter(cx)
3275 .then(|| {
3276 if let Some(blame) = self.blame.as_ref() {
3277 let max_author_length =
3278 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3279 Some(max_author_length)
3280 } else {
3281 None
3282 }
3283 })
3284 .flatten();
3285
3286 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3287
3288 EditorSnapshot {
3289 mode: self.mode.clone(),
3290 show_gutter: self.show_gutter,
3291 offset_content: self.offset_content,
3292 show_line_numbers: self.show_line_numbers,
3293 number_deleted_lines: self.number_deleted_lines,
3294 show_git_diff_gutter: self.show_git_diff_gutter,
3295 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3296 show_code_actions: self.show_code_actions,
3297 show_runnables: self.show_runnables,
3298 show_breakpoints: self.show_breakpoints,
3299 git_blame_gutter_max_author_length,
3300 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3301 display_snapshot,
3302 placeholder_display_snapshot: self
3303 .placeholder_display_map
3304 .as_ref()
3305 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3306 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3307 is_focused: self.focus_handle.is_focused(window),
3308 current_line_highlight: self
3309 .current_line_highlight
3310 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3311 gutter_hovered: self.gutter_hovered,
3312 }
3313 }
3314
3315 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3316 self.buffer.read(cx).language_at(point, cx)
3317 }
3318
3319 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3320 self.buffer.read(cx).read(cx).file_at(point).cloned()
3321 }
3322
3323 pub fn active_excerpt(
3324 &self,
3325 cx: &App,
3326 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3327 self.buffer
3328 .read(cx)
3329 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3330 }
3331
3332 pub fn mode(&self) -> &EditorMode {
3333 &self.mode
3334 }
3335
3336 pub fn set_mode(&mut self, mode: EditorMode) {
3337 self.mode = mode;
3338 }
3339
3340 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3341 self.collaboration_hub.as_deref()
3342 }
3343
3344 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3345 self.collaboration_hub = Some(hub);
3346 }
3347
3348 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3349 self.in_project_search = in_project_search;
3350 }
3351
3352 pub fn set_custom_context_menu(
3353 &mut self,
3354 f: impl 'static
3355 + Fn(
3356 &mut Self,
3357 DisplayPoint,
3358 &mut Window,
3359 &mut Context<Self>,
3360 ) -> Option<Entity<ui::ContextMenu>>,
3361 ) {
3362 self.custom_context_menu = Some(Box::new(f))
3363 }
3364
3365 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3366 self.completion_provider = provider;
3367 }
3368
3369 #[cfg(any(test, feature = "test-support"))]
3370 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3371 self.completion_provider.clone()
3372 }
3373
3374 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3375 self.semantics_provider.clone()
3376 }
3377
3378 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3379 self.semantics_provider = provider;
3380 }
3381
3382 pub fn set_edit_prediction_provider<T>(
3383 &mut self,
3384 provider: Option<Entity<T>>,
3385 window: &mut Window,
3386 cx: &mut Context<Self>,
3387 ) where
3388 T: EditPredictionDelegate,
3389 {
3390 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3391 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3392 if this.focus_handle.is_focused(window) {
3393 this.update_visible_edit_prediction(window, cx);
3394 }
3395 }),
3396 provider: Arc::new(provider),
3397 });
3398 self.update_edit_prediction_settings(cx);
3399 self.refresh_edit_prediction(false, false, window, cx);
3400 }
3401
3402 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3403 self.placeholder_display_map
3404 .as_ref()
3405 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3406 }
3407
3408 pub fn set_placeholder_text(
3409 &mut self,
3410 placeholder_text: &str,
3411 window: &mut Window,
3412 cx: &mut Context<Self>,
3413 ) {
3414 let multibuffer = cx
3415 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3416
3417 let style = window.text_style();
3418
3419 self.placeholder_display_map = Some(cx.new(|cx| {
3420 DisplayMap::new(
3421 multibuffer,
3422 style.font(),
3423 style.font_size.to_pixels(window.rem_size()),
3424 None,
3425 FILE_HEADER_HEIGHT,
3426 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3427 Default::default(),
3428 DiagnosticSeverity::Off,
3429 cx,
3430 )
3431 }));
3432 cx.notify();
3433 }
3434
3435 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3436 self.cursor_shape = cursor_shape;
3437
3438 // Disrupt blink for immediate user feedback that the cursor shape has changed
3439 self.blink_manager.update(cx, BlinkManager::show_cursor);
3440
3441 cx.notify();
3442 }
3443
3444 pub fn cursor_shape(&self) -> CursorShape {
3445 self.cursor_shape
3446 }
3447
3448 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3449 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3450 }
3451
3452 pub fn set_current_line_highlight(
3453 &mut self,
3454 current_line_highlight: Option<CurrentLineHighlight>,
3455 ) {
3456 self.current_line_highlight = current_line_highlight;
3457 }
3458
3459 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3460 self.collapse_matches = collapse_matches;
3461 }
3462
3463 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3464 if self.collapse_matches {
3465 return range.start..range.start;
3466 }
3467 range.clone()
3468 }
3469
3470 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3471 self.display_map.read(cx).clip_at_line_ends
3472 }
3473
3474 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3475 if self.display_map.read(cx).clip_at_line_ends != clip {
3476 self.display_map
3477 .update(cx, |map, _| map.clip_at_line_ends = clip);
3478 }
3479 }
3480
3481 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3482 self.input_enabled = input_enabled;
3483 }
3484
3485 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3486 self.expects_character_input = expects_character_input;
3487 }
3488
3489 pub fn set_edit_predictions_hidden_for_vim_mode(
3490 &mut self,
3491 hidden: bool,
3492 window: &mut Window,
3493 cx: &mut Context<Self>,
3494 ) {
3495 if hidden != self.edit_predictions_hidden_for_vim_mode {
3496 self.edit_predictions_hidden_for_vim_mode = hidden;
3497 if hidden {
3498 self.update_visible_edit_prediction(window, cx);
3499 } else {
3500 self.refresh_edit_prediction(true, false, window, cx);
3501 }
3502 }
3503 }
3504
3505 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3506 self.menu_edit_predictions_policy = value;
3507 }
3508
3509 pub fn set_autoindent(&mut self, autoindent: bool) {
3510 if autoindent {
3511 self.autoindent_mode = Some(AutoindentMode::EachLine);
3512 } else {
3513 self.autoindent_mode = None;
3514 }
3515 }
3516
3517 pub fn capability(&self, cx: &App) -> Capability {
3518 if self.read_only {
3519 Capability::ReadOnly
3520 } else {
3521 self.buffer.read(cx).capability()
3522 }
3523 }
3524
3525 pub fn read_only(&self, cx: &App) -> bool {
3526 self.read_only || self.buffer.read(cx).read_only()
3527 }
3528
3529 pub fn set_read_only(&mut self, read_only: bool) {
3530 self.read_only = read_only;
3531 }
3532
3533 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3534 self.use_autoclose = autoclose;
3535 }
3536
3537 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3538 self.use_auto_surround = auto_surround;
3539 }
3540
3541 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3542 self.auto_replace_emoji_shortcode = auto_replace;
3543 }
3544
3545 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3546 self.buffer_serialization = should_serialize.then(|| {
3547 BufferSerialization::new(
3548 ProjectSettings::get_global(cx)
3549 .session
3550 .restore_unsaved_buffers,
3551 )
3552 })
3553 }
3554
3555 fn should_serialize_buffer(&self) -> bool {
3556 self.buffer_serialization.is_some()
3557 }
3558
3559 pub fn toggle_edit_predictions(
3560 &mut self,
3561 _: &ToggleEditPrediction,
3562 window: &mut Window,
3563 cx: &mut Context<Self>,
3564 ) {
3565 if self.show_edit_predictions_override.is_some() {
3566 self.set_show_edit_predictions(None, window, cx);
3567 } else {
3568 let show_edit_predictions = !self.edit_predictions_enabled();
3569 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3570 }
3571 }
3572
3573 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3574 self.show_completions_on_input_override = show_completions_on_input;
3575 }
3576
3577 pub fn set_show_edit_predictions(
3578 &mut self,
3579 show_edit_predictions: Option<bool>,
3580 window: &mut Window,
3581 cx: &mut Context<Self>,
3582 ) {
3583 self.show_edit_predictions_override = show_edit_predictions;
3584 self.update_edit_prediction_settings(cx);
3585
3586 if let Some(false) = show_edit_predictions {
3587 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3588 } else {
3589 self.refresh_edit_prediction(false, true, window, cx);
3590 }
3591 }
3592
3593 fn edit_predictions_disabled_in_scope(
3594 &self,
3595 buffer: &Entity<Buffer>,
3596 buffer_position: language::Anchor,
3597 cx: &App,
3598 ) -> bool {
3599 let snapshot = buffer.read(cx).snapshot();
3600 let settings = snapshot.settings_at(buffer_position, cx);
3601
3602 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3603 return false;
3604 };
3605
3606 scope.override_name().is_some_and(|scope_name| {
3607 settings
3608 .edit_predictions_disabled_in
3609 .iter()
3610 .any(|s| s == scope_name)
3611 })
3612 }
3613
3614 pub fn set_use_modal_editing(&mut self, to: bool) {
3615 self.use_modal_editing = to;
3616 }
3617
3618 pub fn use_modal_editing(&self) -> bool {
3619 self.use_modal_editing
3620 }
3621
3622 fn selections_did_change(
3623 &mut self,
3624 local: bool,
3625 old_cursor_position: &Anchor,
3626 effects: SelectionEffects,
3627 window: &mut Window,
3628 cx: &mut Context<Self>,
3629 ) {
3630 window.invalidate_character_coordinates();
3631
3632 // Copy selections to primary selection buffer
3633 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3634 if local {
3635 let selections = self
3636 .selections
3637 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3638 let buffer_handle = self.buffer.read(cx).read(cx);
3639
3640 let mut text = String::new();
3641 for (index, selection) in selections.iter().enumerate() {
3642 let text_for_selection = buffer_handle
3643 .text_for_range(selection.start..selection.end)
3644 .collect::<String>();
3645
3646 text.push_str(&text_for_selection);
3647 if index != selections.len() - 1 {
3648 text.push('\n');
3649 }
3650 }
3651
3652 if !text.is_empty() {
3653 cx.write_to_primary(ClipboardItem::new_string(text));
3654 }
3655 }
3656
3657 let selection_anchors = self.selections.disjoint_anchors_arc();
3658
3659 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3660 self.buffer.update(cx, |buffer, cx| {
3661 buffer.set_active_selections(
3662 &selection_anchors,
3663 self.selections.line_mode(),
3664 self.cursor_shape,
3665 cx,
3666 )
3667 });
3668 }
3669 let display_map = self
3670 .display_map
3671 .update(cx, |display_map, cx| display_map.snapshot(cx));
3672 let buffer = display_map.buffer_snapshot();
3673 if self.selections.count() == 1 {
3674 self.add_selections_state = None;
3675 }
3676 self.select_next_state = None;
3677 self.select_prev_state = None;
3678 self.select_syntax_node_history.try_clear();
3679 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3680 self.snippet_stack.invalidate(&selection_anchors, buffer);
3681 self.take_rename(false, window, cx);
3682
3683 let newest_selection = self.selections.newest_anchor();
3684 let new_cursor_position = newest_selection.head();
3685 let selection_start = newest_selection.start;
3686
3687 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3688 self.push_to_nav_history(
3689 *old_cursor_position,
3690 Some(new_cursor_position.to_point(buffer)),
3691 false,
3692 effects.nav_history == Some(true),
3693 cx,
3694 );
3695 }
3696
3697 if local {
3698 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3699 self.register_buffer(buffer_id, cx);
3700 }
3701
3702 let mut context_menu = self.context_menu.borrow_mut();
3703 let completion_menu = match context_menu.as_ref() {
3704 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3705 Some(CodeContextMenu::CodeActions(_)) => {
3706 *context_menu = None;
3707 None
3708 }
3709 None => None,
3710 };
3711 let completion_position = completion_menu.map(|menu| menu.initial_position);
3712 drop(context_menu);
3713
3714 if effects.completions
3715 && let Some(completion_position) = completion_position
3716 {
3717 let start_offset = selection_start.to_offset(buffer);
3718 let position_matches = start_offset == completion_position.to_offset(buffer);
3719 let continue_showing = if let Some((snap, ..)) =
3720 buffer.point_to_buffer_offset(completion_position)
3721 && !snap.capability.editable()
3722 {
3723 false
3724 } else if position_matches {
3725 if self.snippet_stack.is_empty() {
3726 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3727 == Some(CharKind::Word)
3728 } else {
3729 // Snippet choices can be shown even when the cursor is in whitespace.
3730 // Dismissing the menu with actions like backspace is handled by
3731 // invalidation regions.
3732 true
3733 }
3734 } else {
3735 false
3736 };
3737
3738 if continue_showing {
3739 self.open_or_update_completions_menu(None, None, false, window, cx);
3740 } else {
3741 self.hide_context_menu(window, cx);
3742 }
3743 }
3744
3745 hide_hover(self, cx);
3746
3747 if old_cursor_position.to_display_point(&display_map).row()
3748 != new_cursor_position.to_display_point(&display_map).row()
3749 {
3750 self.available_code_actions.take();
3751 }
3752 self.refresh_code_actions(window, cx);
3753 self.refresh_document_highlights(cx);
3754 refresh_linked_ranges(self, window, cx);
3755
3756 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3757 self.refresh_matching_bracket_highlights(&display_map, cx);
3758 self.refresh_outline_symbols_at_cursor(cx);
3759 self.update_visible_edit_prediction(window, cx);
3760 self.inline_blame_popover.take();
3761 if self.git_blame_inline_enabled {
3762 self.start_inline_blame_timer(window, cx);
3763 }
3764 }
3765
3766 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3767
3768 if local && !self.suppress_selection_callback {
3769 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3770 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3771 callback(cursor_position, window, cx);
3772 }
3773 }
3774
3775 cx.emit(EditorEvent::SelectionsChanged { local });
3776
3777 let selections = &self.selections.disjoint_anchors_arc();
3778 if selections.len() == 1 {
3779 cx.emit(SearchEvent::ActiveMatchChanged)
3780 }
3781 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3782 let inmemory_selections = selections
3783 .iter()
3784 .map(|s| {
3785 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3786 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3787 })
3788 .collect();
3789 self.update_restoration_data(cx, |data| {
3790 data.selections = inmemory_selections;
3791 });
3792
3793 if WorkspaceSettings::get(None, cx).restore_on_startup
3794 != RestoreOnStartupBehavior::EmptyTab
3795 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3796 {
3797 let snapshot = self.buffer().read(cx).snapshot(cx);
3798 let selections = selections.clone();
3799 let background_executor = cx.background_executor().clone();
3800 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3801 let db = EditorDb::global(cx);
3802 self.serialize_selections = cx.background_spawn(async move {
3803 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3804 let db_selections = selections
3805 .iter()
3806 .map(|selection| {
3807 (
3808 selection.start.to_offset(&snapshot).0,
3809 selection.end.to_offset(&snapshot).0,
3810 )
3811 })
3812 .collect();
3813
3814 db.save_editor_selections(editor_id, workspace_id, db_selections)
3815 .await
3816 .with_context(|| {
3817 format!(
3818 "persisting editor selections for editor {editor_id}, \
3819 workspace {workspace_id:?}"
3820 )
3821 })
3822 .log_err();
3823 });
3824 }
3825 }
3826
3827 cx.notify();
3828 }
3829
3830 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3831 use text::ToOffset as _;
3832 use text::ToPoint as _;
3833
3834 if self.mode.is_minimap()
3835 || WorkspaceSettings::get(None, cx).restore_on_startup
3836 == RestoreOnStartupBehavior::EmptyTab
3837 {
3838 return;
3839 }
3840
3841 if !self.buffer().read(cx).is_singleton() {
3842 return;
3843 }
3844
3845 let display_snapshot = self
3846 .display_map
3847 .update(cx, |display_map, cx| display_map.snapshot(cx));
3848 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3849 return;
3850 };
3851 let inmemory_folds = display_snapshot
3852 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3853 .map(|fold| {
3854 fold.range.start.text_anchor.to_point(&snapshot)
3855 ..fold.range.end.text_anchor.to_point(&snapshot)
3856 })
3857 .collect();
3858 self.update_restoration_data(cx, |data| {
3859 data.folds = inmemory_folds;
3860 });
3861
3862 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3863 return;
3864 };
3865
3866 // Get file path for path-based fold storage (survives tab close)
3867 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3868 project::File::from_dyn(buffer.read(cx).file())
3869 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3870 }) else {
3871 return;
3872 };
3873
3874 let background_executor = cx.background_executor().clone();
3875 const FINGERPRINT_LEN: usize = 32;
3876 let db_folds = display_snapshot
3877 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3878 .map(|fold| {
3879 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3880 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3881
3882 // Extract fingerprints - content at fold boundaries for validation on restore
3883 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3884 // content that might change independently.
3885 // start_fp: first min(32, fold_len) bytes of fold content
3886 // end_fp: last min(32, fold_len) bytes of fold content
3887 // Clip to character boundaries to handle multibyte UTF-8 characters.
3888 let fold_len = end - start;
3889 let start_fp_end = snapshot
3890 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3891 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3892 let end_fp_start = snapshot
3893 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3894 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3895
3896 (start, end, start_fp, end_fp)
3897 })
3898 .collect::<Vec<_>>();
3899 let db = EditorDb::global(cx);
3900 self.serialize_folds = cx.background_spawn(async move {
3901 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3902 if db_folds.is_empty() {
3903 // No folds - delete any persisted folds for this file
3904 db.delete_file_folds(workspace_id, file_path)
3905 .await
3906 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3907 .log_err();
3908 } else {
3909 db.save_file_folds(workspace_id, file_path, db_folds)
3910 .await
3911 .with_context(|| {
3912 format!("persisting file folds for workspace {workspace_id:?}")
3913 })
3914 .log_err();
3915 }
3916 });
3917 }
3918
3919 pub fn sync_selections(
3920 &mut self,
3921 other: Entity<Editor>,
3922 cx: &mut Context<Self>,
3923 ) -> gpui::Subscription {
3924 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3925 if !other_selections.is_empty() {
3926 self.selections
3927 .change_with(&self.display_snapshot(cx), |selections| {
3928 selections.select_anchors(other_selections);
3929 });
3930 }
3931
3932 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3933 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3934 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3935 if other_selections.is_empty() {
3936 return;
3937 }
3938 let snapshot = this.display_snapshot(cx);
3939 this.selections.change_with(&snapshot, |selections| {
3940 selections.select_anchors(other_selections);
3941 });
3942 }
3943 });
3944
3945 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3946 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3947 let these_selections = this.selections.disjoint_anchors().to_vec();
3948 if these_selections.is_empty() {
3949 return;
3950 }
3951 other.update(cx, |other_editor, cx| {
3952 let snapshot = other_editor.display_snapshot(cx);
3953 other_editor
3954 .selections
3955 .change_with(&snapshot, |selections| {
3956 selections.select_anchors(these_selections);
3957 })
3958 });
3959 }
3960 });
3961
3962 Subscription::join(other_subscription, this_subscription)
3963 }
3964
3965 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3966 if self.buffer().read(cx).is_singleton() {
3967 return;
3968 }
3969 let snapshot = self.buffer.read(cx).snapshot(cx);
3970 let buffer_ids: HashSet<BufferId> = self
3971 .selections
3972 .disjoint_anchor_ranges()
3973 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3974 .collect();
3975 for buffer_id in buffer_ids {
3976 self.unfold_buffer(buffer_id, cx);
3977 }
3978 }
3979
3980 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3981 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3982 /// effects of selection change occur at the end of the transaction.
3983 pub fn change_selections<R>(
3984 &mut self,
3985 effects: SelectionEffects,
3986 window: &mut Window,
3987 cx: &mut Context<Self>,
3988 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3989 ) -> R {
3990 let snapshot = self.display_snapshot(cx);
3991 if let Some(state) = &mut self.deferred_selection_effects_state {
3992 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3993 state.effects.completions = effects.completions;
3994 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3995 let (changed, result) = self.selections.change_with(&snapshot, change);
3996 state.changed |= changed;
3997 return result;
3998 }
3999 let mut state = DeferredSelectionEffectsState {
4000 changed: false,
4001 effects,
4002 old_cursor_position: self.selections.newest_anchor().head(),
4003 history_entry: SelectionHistoryEntry {
4004 selections: self.selections.disjoint_anchors_arc(),
4005 select_next_state: self.select_next_state.clone(),
4006 select_prev_state: self.select_prev_state.clone(),
4007 add_selections_state: self.add_selections_state.clone(),
4008 },
4009 };
4010 let (changed, result) = self.selections.change_with(&snapshot, change);
4011 state.changed = state.changed || changed;
4012 if self.defer_selection_effects {
4013 self.deferred_selection_effects_state = Some(state);
4014 } else {
4015 self.apply_selection_effects(state, window, cx);
4016 }
4017 result
4018 }
4019
4020 /// Defers the effects of selection change, so that the effects of multiple calls to
4021 /// `change_selections` are applied at the end. This way these intermediate states aren't added
4022 /// to selection history and the state of popovers based on selection position aren't
4023 /// erroneously updated.
4024 pub fn with_selection_effects_deferred<R>(
4025 &mut self,
4026 window: &mut Window,
4027 cx: &mut Context<Self>,
4028 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
4029 ) -> R {
4030 let already_deferred = self.defer_selection_effects;
4031 self.defer_selection_effects = true;
4032 let result = update(self, window, cx);
4033 if !already_deferred {
4034 self.defer_selection_effects = false;
4035 if let Some(state) = self.deferred_selection_effects_state.take() {
4036 self.apply_selection_effects(state, window, cx);
4037 }
4038 }
4039 result
4040 }
4041
4042 fn apply_selection_effects(
4043 &mut self,
4044 state: DeferredSelectionEffectsState,
4045 window: &mut Window,
4046 cx: &mut Context<Self>,
4047 ) {
4048 if state.changed {
4049 self.selection_history.push(state.history_entry);
4050
4051 if let Some(autoscroll) = state.effects.scroll {
4052 self.request_autoscroll(autoscroll, cx);
4053 }
4054
4055 let old_cursor_position = &state.old_cursor_position;
4056
4057 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
4058
4059 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
4060 self.show_signature_help_auto(window, cx);
4061 }
4062 }
4063 }
4064
4065 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4066 where
4067 I: IntoIterator<Item = (Range<S>, T)>,
4068 S: ToOffset,
4069 T: Into<Arc<str>>,
4070 {
4071 if self.read_only(cx) {
4072 return;
4073 }
4074
4075 self.buffer
4076 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
4077 }
4078
4079 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4080 where
4081 I: IntoIterator<Item = (Range<S>, T)>,
4082 S: ToOffset,
4083 T: Into<Arc<str>>,
4084 {
4085 if self.read_only(cx) {
4086 return;
4087 }
4088
4089 self.buffer.update(cx, |buffer, cx| {
4090 buffer.edit(edits, self.autoindent_mode.clone(), cx)
4091 });
4092 }
4093
4094 pub fn edit_with_block_indent<I, S, T>(
4095 &mut self,
4096 edits: I,
4097 original_indent_columns: Vec<Option<u32>>,
4098 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.update(cx, |buffer, cx| {
4109 buffer.edit(
4110 edits,
4111 Some(AutoindentMode::Block {
4112 original_indent_columns,
4113 }),
4114 cx,
4115 )
4116 });
4117 }
4118
4119 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
4120 self.hide_context_menu(window, cx);
4121
4122 match phase {
4123 SelectPhase::Begin {
4124 position,
4125 add,
4126 click_count,
4127 } => self.begin_selection(position, add, click_count, window, cx),
4128 SelectPhase::BeginColumnar {
4129 position,
4130 goal_column,
4131 reset,
4132 mode,
4133 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4134 SelectPhase::Extend {
4135 position,
4136 click_count,
4137 } => self.extend_selection(position, click_count, window, cx),
4138 SelectPhase::Update {
4139 position,
4140 goal_column,
4141 scroll_delta,
4142 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4143 SelectPhase::End => self.end_selection(window, cx),
4144 }
4145 }
4146
4147 fn extend_selection(
4148 &mut self,
4149 position: DisplayPoint,
4150 click_count: usize,
4151 window: &mut Window,
4152 cx: &mut Context<Self>,
4153 ) {
4154 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4155 let tail = self
4156 .selections
4157 .newest::<MultiBufferOffset>(&display_map)
4158 .tail();
4159 let click_count = click_count.max(match self.selections.select_mode() {
4160 SelectMode::Character => 1,
4161 SelectMode::Word(_) => 2,
4162 SelectMode::Line(_) => 3,
4163 SelectMode::All => 4,
4164 });
4165 self.begin_selection(position, false, click_count, window, cx);
4166
4167 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4168
4169 let current_selection = match self.selections.select_mode() {
4170 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4171 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4172 };
4173
4174 let mut pending_selection = self
4175 .selections
4176 .pending_anchor()
4177 .cloned()
4178 .expect("extend_selection not called with pending selection");
4179
4180 if pending_selection
4181 .start
4182 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4183 == Ordering::Greater
4184 {
4185 pending_selection.start = current_selection.start;
4186 }
4187 if pending_selection
4188 .end
4189 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4190 == Ordering::Less
4191 {
4192 pending_selection.end = current_selection.end;
4193 pending_selection.reversed = true;
4194 }
4195
4196 let mut pending_mode = self.selections.pending_mode().unwrap();
4197 match &mut pending_mode {
4198 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4199 _ => {}
4200 }
4201
4202 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4203 SelectionEffects::scroll(Autoscroll::fit())
4204 } else {
4205 SelectionEffects::no_scroll()
4206 };
4207
4208 self.change_selections(effects, window, cx, |s| {
4209 s.set_pending(pending_selection.clone(), pending_mode);
4210 s.set_is_extending(true);
4211 });
4212 }
4213
4214 fn begin_selection(
4215 &mut self,
4216 position: DisplayPoint,
4217 add: bool,
4218 click_count: usize,
4219 window: &mut Window,
4220 cx: &mut Context<Self>,
4221 ) {
4222 if !self.focus_handle.is_focused(window) {
4223 self.last_focused_descendant = None;
4224 window.focus(&self.focus_handle, cx);
4225 }
4226
4227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4228 let buffer = display_map.buffer_snapshot();
4229 let position = display_map.clip_point(position, Bias::Left);
4230
4231 let start;
4232 let end;
4233 let mode;
4234 let mut auto_scroll;
4235 match click_count {
4236 1 => {
4237 start = buffer.anchor_before(position.to_point(&display_map));
4238 end = start;
4239 mode = SelectMode::Character;
4240 auto_scroll = true;
4241 }
4242 2 => {
4243 let position = display_map
4244 .clip_point(position, Bias::Left)
4245 .to_offset(&display_map, Bias::Left);
4246 let (range, _) = buffer.surrounding_word(position, None);
4247 start = buffer.anchor_before(range.start);
4248 end = buffer.anchor_before(range.end);
4249 mode = SelectMode::Word(start..end);
4250 auto_scroll = true;
4251 }
4252 3 => {
4253 let position = display_map
4254 .clip_point(position, Bias::Left)
4255 .to_point(&display_map);
4256 let line_start = display_map.prev_line_boundary(position).0;
4257 let next_line_start = buffer.clip_point(
4258 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4259 Bias::Left,
4260 );
4261 start = buffer.anchor_before(line_start);
4262 end = buffer.anchor_before(next_line_start);
4263 mode = SelectMode::Line(start..end);
4264 auto_scroll = true;
4265 }
4266 _ => {
4267 start = buffer.anchor_before(MultiBufferOffset(0));
4268 end = buffer.anchor_before(buffer.len());
4269 mode = SelectMode::All;
4270 auto_scroll = false;
4271 }
4272 }
4273 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4274
4275 let point_to_delete: Option<usize> = {
4276 let selected_points: Vec<Selection<Point>> =
4277 self.selections.disjoint_in_range(start..end, &display_map);
4278
4279 if !add || click_count > 1 {
4280 None
4281 } else if !selected_points.is_empty() {
4282 Some(selected_points[0].id)
4283 } else {
4284 let clicked_point_already_selected =
4285 self.selections.disjoint_anchors().iter().find(|selection| {
4286 selection.start.to_point(buffer) == start.to_point(buffer)
4287 || selection.end.to_point(buffer) == end.to_point(buffer)
4288 });
4289
4290 clicked_point_already_selected.map(|selection| selection.id)
4291 }
4292 };
4293
4294 let selections_count = self.selections.count();
4295 let effects = if auto_scroll {
4296 SelectionEffects::default()
4297 } else {
4298 SelectionEffects::no_scroll()
4299 };
4300
4301 self.change_selections(effects, window, cx, |s| {
4302 if let Some(point_to_delete) = point_to_delete {
4303 s.delete(point_to_delete);
4304
4305 if selections_count == 1 {
4306 s.set_pending_anchor_range(start..end, mode);
4307 }
4308 } else {
4309 if !add {
4310 s.clear_disjoint();
4311 }
4312
4313 s.set_pending_anchor_range(start..end, mode);
4314 }
4315 });
4316 }
4317
4318 fn begin_columnar_selection(
4319 &mut self,
4320 position: DisplayPoint,
4321 goal_column: u32,
4322 reset: bool,
4323 mode: ColumnarMode,
4324 window: &mut Window,
4325 cx: &mut Context<Self>,
4326 ) {
4327 if !self.focus_handle.is_focused(window) {
4328 self.last_focused_descendant = None;
4329 window.focus(&self.focus_handle, cx);
4330 }
4331
4332 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4333
4334 if reset {
4335 let pointer_position = display_map
4336 .buffer_snapshot()
4337 .anchor_before(position.to_point(&display_map));
4338
4339 self.change_selections(
4340 SelectionEffects::scroll(Autoscroll::newest()),
4341 window,
4342 cx,
4343 |s| {
4344 s.clear_disjoint();
4345 s.set_pending_anchor_range(
4346 pointer_position..pointer_position,
4347 SelectMode::Character,
4348 );
4349 },
4350 );
4351 };
4352
4353 let tail = self.selections.newest::<Point>(&display_map).tail();
4354 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4355 self.columnar_selection_state = match mode {
4356 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4357 selection_tail: selection_anchor,
4358 display_point: if reset {
4359 if position.column() != goal_column {
4360 Some(DisplayPoint::new(position.row(), goal_column))
4361 } else {
4362 None
4363 }
4364 } else {
4365 None
4366 },
4367 }),
4368 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4369 selection_tail: selection_anchor,
4370 }),
4371 };
4372
4373 if !reset {
4374 self.select_columns(position, goal_column, &display_map, window, cx);
4375 }
4376 }
4377
4378 fn update_selection(
4379 &mut self,
4380 position: DisplayPoint,
4381 goal_column: u32,
4382 scroll_delta: gpui::Point<f32>,
4383 window: &mut Window,
4384 cx: &mut Context<Self>,
4385 ) {
4386 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4387
4388 if self.columnar_selection_state.is_some() {
4389 self.select_columns(position, goal_column, &display_map, window, cx);
4390 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4391 let buffer = display_map.buffer_snapshot();
4392 let head;
4393 let tail;
4394 let mode = self.selections.pending_mode().unwrap();
4395 match &mode {
4396 SelectMode::Character => {
4397 head = position.to_point(&display_map);
4398 tail = pending.tail().to_point(buffer);
4399 }
4400 SelectMode::Word(original_range) => {
4401 let offset = display_map
4402 .clip_point(position, Bias::Left)
4403 .to_offset(&display_map, Bias::Left);
4404 let original_range = original_range.to_offset(buffer);
4405
4406 let head_offset = if buffer.is_inside_word(offset, None)
4407 || original_range.contains(&offset)
4408 {
4409 let (word_range, _) = buffer.surrounding_word(offset, None);
4410 if word_range.start < original_range.start {
4411 word_range.start
4412 } else {
4413 word_range.end
4414 }
4415 } else {
4416 offset
4417 };
4418
4419 head = head_offset.to_point(buffer);
4420 if head_offset <= original_range.start {
4421 tail = original_range.end.to_point(buffer);
4422 } else {
4423 tail = original_range.start.to_point(buffer);
4424 }
4425 }
4426 SelectMode::Line(original_range) => {
4427 let original_range = original_range.to_point(display_map.buffer_snapshot());
4428
4429 let position = display_map
4430 .clip_point(position, Bias::Left)
4431 .to_point(&display_map);
4432 let line_start = display_map.prev_line_boundary(position).0;
4433 let next_line_start = buffer.clip_point(
4434 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4435 Bias::Left,
4436 );
4437
4438 if line_start < original_range.start {
4439 head = line_start
4440 } else {
4441 head = next_line_start
4442 }
4443
4444 if head <= original_range.start {
4445 tail = original_range.end;
4446 } else {
4447 tail = original_range.start;
4448 }
4449 }
4450 SelectMode::All => {
4451 return;
4452 }
4453 };
4454
4455 if head < tail {
4456 pending.start = buffer.anchor_before(head);
4457 pending.end = buffer.anchor_before(tail);
4458 pending.reversed = true;
4459 } else {
4460 pending.start = buffer.anchor_before(tail);
4461 pending.end = buffer.anchor_before(head);
4462 pending.reversed = false;
4463 }
4464
4465 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4466 s.set_pending(pending.clone(), mode);
4467 });
4468 } else {
4469 log::error!("update_selection dispatched with no pending selection");
4470 return;
4471 }
4472
4473 self.apply_scroll_delta(scroll_delta, window, cx);
4474 cx.notify();
4475 }
4476
4477 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4478 self.columnar_selection_state.take();
4479 if let Some(pending_mode) = self.selections.pending_mode() {
4480 let selections = self
4481 .selections
4482 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4483 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4484 s.select(selections);
4485 s.clear_pending();
4486 if s.is_extending() {
4487 s.set_is_extending(false);
4488 } else {
4489 s.set_select_mode(pending_mode);
4490 }
4491 });
4492 }
4493 }
4494
4495 fn select_columns(
4496 &mut self,
4497 head: DisplayPoint,
4498 goal_column: u32,
4499 display_map: &DisplaySnapshot,
4500 window: &mut Window,
4501 cx: &mut Context<Self>,
4502 ) {
4503 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4504 return;
4505 };
4506
4507 let tail = match columnar_state {
4508 ColumnarSelectionState::FromMouse {
4509 selection_tail,
4510 display_point,
4511 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4512 ColumnarSelectionState::FromSelection { selection_tail } => {
4513 selection_tail.to_display_point(display_map)
4514 }
4515 };
4516
4517 let start_row = cmp::min(tail.row(), head.row());
4518 let end_row = cmp::max(tail.row(), head.row());
4519 let start_column = cmp::min(tail.column(), goal_column);
4520 let end_column = cmp::max(tail.column(), goal_column);
4521 let reversed = start_column < tail.column();
4522
4523 let selection_ranges = (start_row.0..=end_row.0)
4524 .map(DisplayRow)
4525 .filter_map(|row| {
4526 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4527 || start_column <= display_map.line_len(row))
4528 && !display_map.is_block_line(row)
4529 {
4530 let start = display_map
4531 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4532 .to_point(display_map);
4533 let end = display_map
4534 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4535 .to_point(display_map);
4536 if reversed {
4537 Some(end..start)
4538 } else {
4539 Some(start..end)
4540 }
4541 } else {
4542 None
4543 }
4544 })
4545 .collect::<Vec<_>>();
4546 if selection_ranges.is_empty() {
4547 return;
4548 }
4549
4550 let ranges = match columnar_state {
4551 ColumnarSelectionState::FromMouse { .. } => {
4552 let mut non_empty_ranges = selection_ranges
4553 .iter()
4554 .filter(|selection_range| selection_range.start != selection_range.end)
4555 .peekable();
4556 if non_empty_ranges.peek().is_some() {
4557 non_empty_ranges.cloned().collect()
4558 } else {
4559 selection_ranges
4560 }
4561 }
4562 _ => selection_ranges,
4563 };
4564
4565 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4566 s.select_ranges(ranges);
4567 });
4568 cx.notify();
4569 }
4570
4571 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4572 self.selections
4573 .all_adjusted(snapshot)
4574 .iter()
4575 .any(|selection| !selection.is_empty())
4576 }
4577
4578 pub fn has_pending_nonempty_selection(&self) -> bool {
4579 let pending_nonempty_selection = match self.selections.pending_anchor() {
4580 Some(Selection { start, end, .. }) => start != end,
4581 None => false,
4582 };
4583
4584 pending_nonempty_selection
4585 || (self.columnar_selection_state.is_some()
4586 && self.selections.disjoint_anchors().len() > 1)
4587 }
4588
4589 pub fn has_pending_selection(&self) -> bool {
4590 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4591 }
4592
4593 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4594 self.selection_mark_mode = false;
4595 self.selection_drag_state = SelectionDragState::None;
4596
4597 if self.dismiss_menus_and_popups(true, window, cx) {
4598 cx.notify();
4599 return;
4600 }
4601 if self.clear_expanded_diff_hunks(cx) {
4602 cx.notify();
4603 return;
4604 }
4605 if self.show_git_blame_gutter {
4606 self.show_git_blame_gutter = false;
4607 cx.notify();
4608 return;
4609 }
4610
4611 if self.mode.is_full()
4612 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4613 {
4614 cx.notify();
4615 return;
4616 }
4617
4618 cx.propagate();
4619 }
4620
4621 pub fn dismiss_menus_and_popups(
4622 &mut self,
4623 is_user_requested: bool,
4624 window: &mut Window,
4625 cx: &mut Context<Self>,
4626 ) -> bool {
4627 let mut dismissed = false;
4628
4629 dismissed |= self.take_rename(false, window, cx).is_some();
4630 dismissed |= self.hide_blame_popover(true, cx);
4631 dismissed |= hide_hover(self, cx);
4632 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4633 dismissed |= self.hide_context_menu(window, cx).is_some();
4634 dismissed |= self.mouse_context_menu.take().is_some();
4635 dismissed |= is_user_requested
4636 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4637 dismissed |= self.snippet_stack.pop().is_some();
4638 if self.diff_review_drag_state.is_some() {
4639 self.cancel_diff_review_drag(cx);
4640 dismissed = true;
4641 }
4642 if !self.diff_review_overlays.is_empty() {
4643 self.dismiss_all_diff_review_overlays(cx);
4644 dismissed = true;
4645 }
4646
4647 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4648 self.dismiss_diagnostics(cx);
4649 dismissed = true;
4650 }
4651
4652 dismissed
4653 }
4654
4655 fn linked_editing_ranges_for(
4656 &self,
4657 selection: Range<text::Anchor>,
4658 cx: &App,
4659 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4660 if self.linked_edit_ranges.is_empty() {
4661 return None;
4662 }
4663 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4664 selection.end.buffer_id.and_then(|end_buffer_id| {
4665 if selection.start.buffer_id != Some(end_buffer_id) {
4666 return None;
4667 }
4668 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4669 let snapshot = buffer.read(cx).snapshot();
4670 self.linked_edit_ranges
4671 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4672 .map(|ranges| (ranges, snapshot, buffer))
4673 })?;
4674 use text::ToOffset as TO;
4675 // find offset from the start of current range to current cursor position
4676 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4677
4678 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4679 let start_difference = start_offset - start_byte_offset;
4680 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4681 let end_difference = end_offset - start_byte_offset;
4682
4683 // Current range has associated linked ranges.
4684 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4685 for range in linked_ranges.iter() {
4686 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4687 let end_offset = start_offset + end_difference;
4688 let start_offset = start_offset + start_difference;
4689 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4690 continue;
4691 }
4692 if self.selections.disjoint_anchor_ranges().any(|s| {
4693 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4694 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4695 {
4696 return false;
4697 }
4698 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4699 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4700 }) {
4701 continue;
4702 }
4703 let start = buffer_snapshot.anchor_after(start_offset);
4704 let end = buffer_snapshot.anchor_after(end_offset);
4705 linked_edits
4706 .entry(buffer.clone())
4707 .or_default()
4708 .push(start..end);
4709 }
4710 Some(linked_edits)
4711 }
4712
4713 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4714 let text: Arc<str> = text.into();
4715
4716 if self.read_only(cx) {
4717 return;
4718 }
4719
4720 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4721
4722 self.unfold_buffers_with_selections(cx);
4723
4724 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4725 let mut bracket_inserted = false;
4726 let mut edits = Vec::new();
4727 let mut linked_edits = LinkedEdits::new();
4728 let mut new_selections = Vec::with_capacity(selections.len());
4729 let mut new_autoclose_regions = Vec::new();
4730 let snapshot = self.buffer.read(cx).read(cx);
4731 let mut clear_linked_edit_ranges = false;
4732 let mut all_selections_read_only = true;
4733 let mut has_adjacent_edits = false;
4734 let mut in_adjacent_group = false;
4735
4736 let mut regions = self
4737 .selections_with_autoclose_regions(selections, &snapshot)
4738 .peekable();
4739
4740 while let Some((selection, autoclose_region)) = regions.next() {
4741 if snapshot
4742 .point_to_buffer_point(selection.head())
4743 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4744 {
4745 continue;
4746 }
4747 if snapshot
4748 .point_to_buffer_point(selection.tail())
4749 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4750 {
4751 // note, ideally we'd clip the tail to the closest writeable region towards the head
4752 continue;
4753 }
4754 all_selections_read_only = false;
4755
4756 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4757 // Determine if the inserted text matches the opening or closing
4758 // bracket of any of this language's bracket pairs.
4759 let mut bracket_pair = None;
4760 let mut is_bracket_pair_start = false;
4761 let mut is_bracket_pair_end = false;
4762 if !text.is_empty() {
4763 let mut bracket_pair_matching_end = None;
4764 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4765 // and they are removing the character that triggered IME popup.
4766 for (pair, enabled) in scope.brackets() {
4767 if !pair.close && !pair.surround {
4768 continue;
4769 }
4770
4771 if enabled && pair.start.ends_with(text.as_ref()) {
4772 let prefix_len = pair.start.len() - text.len();
4773 let preceding_text_matches_prefix = prefix_len == 0
4774 || (selection.start.column >= (prefix_len as u32)
4775 && snapshot.contains_str_at(
4776 Point::new(
4777 selection.start.row,
4778 selection.start.column - (prefix_len as u32),
4779 ),
4780 &pair.start[..prefix_len],
4781 ));
4782 if preceding_text_matches_prefix {
4783 bracket_pair = Some(pair.clone());
4784 is_bracket_pair_start = true;
4785 break;
4786 }
4787 }
4788 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4789 {
4790 // take first bracket pair matching end, but don't break in case a later bracket
4791 // pair matches start
4792 bracket_pair_matching_end = Some(pair.clone());
4793 }
4794 }
4795 if let Some(end) = bracket_pair_matching_end
4796 && bracket_pair.is_none()
4797 {
4798 bracket_pair = Some(end);
4799 is_bracket_pair_end = true;
4800 }
4801 }
4802
4803 if let Some(bracket_pair) = bracket_pair {
4804 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4805 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4806 let auto_surround =
4807 self.use_auto_surround && snapshot_settings.use_auto_surround;
4808 if selection.is_empty() {
4809 if is_bracket_pair_start {
4810 // If the inserted text is a suffix of an opening bracket and the
4811 // selection is preceded by the rest of the opening bracket, then
4812 // insert the closing bracket.
4813 let following_text_allows_autoclose = snapshot
4814 .chars_at(selection.start)
4815 .next()
4816 .is_none_or(|c| scope.should_autoclose_before(c));
4817
4818 let preceding_text_allows_autoclose = selection.start.column == 0
4819 || snapshot
4820 .reversed_chars_at(selection.start)
4821 .next()
4822 .is_none_or(|c| {
4823 bracket_pair.start != bracket_pair.end
4824 || !snapshot
4825 .char_classifier_at(selection.start)
4826 .is_word(c)
4827 });
4828
4829 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4830 && bracket_pair.start.len() == 1
4831 {
4832 let target = bracket_pair.start.chars().next().unwrap();
4833 let mut byte_offset = 0u32;
4834 let current_line_count = snapshot
4835 .reversed_chars_at(selection.start)
4836 .take_while(|&c| c != '\n')
4837 .filter(|c| {
4838 byte_offset += c.len_utf8() as u32;
4839 if *c != target {
4840 return false;
4841 }
4842
4843 let point = Point::new(
4844 selection.start.row,
4845 selection.start.column.saturating_sub(byte_offset),
4846 );
4847
4848 let is_enabled = snapshot
4849 .language_scope_at(point)
4850 .and_then(|scope| {
4851 scope
4852 .brackets()
4853 .find(|(pair, _)| {
4854 pair.start == bracket_pair.start
4855 })
4856 .map(|(_, enabled)| enabled)
4857 })
4858 .unwrap_or(true);
4859
4860 let is_delimiter = snapshot
4861 .language_scope_at(Point::new(
4862 point.row,
4863 point.column + 1,
4864 ))
4865 .and_then(|scope| {
4866 scope
4867 .brackets()
4868 .find(|(pair, _)| {
4869 pair.start == bracket_pair.start
4870 })
4871 .map(|(_, enabled)| !enabled)
4872 })
4873 .unwrap_or(false);
4874
4875 is_enabled && !is_delimiter
4876 })
4877 .count();
4878 current_line_count % 2 == 1
4879 } else {
4880 false
4881 };
4882
4883 if autoclose
4884 && bracket_pair.close
4885 && following_text_allows_autoclose
4886 && preceding_text_allows_autoclose
4887 && !is_closing_quote
4888 {
4889 let anchor = snapshot.anchor_before(selection.end);
4890 new_selections.push((selection.map(|_| anchor), text.len()));
4891 new_autoclose_regions.push((
4892 anchor,
4893 text.len(),
4894 selection.id,
4895 bracket_pair.clone(),
4896 ));
4897 edits.push((
4898 selection.range(),
4899 format!("{}{}", text, bracket_pair.end).into(),
4900 ));
4901 bracket_inserted = true;
4902 continue;
4903 }
4904 }
4905
4906 if let Some(region) = autoclose_region {
4907 // If the selection is followed by an auto-inserted closing bracket,
4908 // then don't insert that closing bracket again; just move the selection
4909 // past the closing bracket.
4910 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4911 && text.as_ref() == region.pair.end.as_str()
4912 && snapshot.contains_str_at(region.range.end, text.as_ref());
4913 if should_skip {
4914 let anchor = snapshot.anchor_after(selection.end);
4915 new_selections
4916 .push((selection.map(|_| anchor), region.pair.end.len()));
4917 continue;
4918 }
4919 }
4920
4921 let always_treat_brackets_as_autoclosed = snapshot
4922 .language_settings_at(selection.start, cx)
4923 .always_treat_brackets_as_autoclosed;
4924 if always_treat_brackets_as_autoclosed
4925 && is_bracket_pair_end
4926 && snapshot.contains_str_at(selection.end, text.as_ref())
4927 {
4928 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4929 // and the inserted text is a closing bracket and the selection is followed
4930 // by the closing bracket then move the selection past the closing bracket.
4931 let anchor = snapshot.anchor_after(selection.end);
4932 new_selections.push((selection.map(|_| anchor), text.len()));
4933 continue;
4934 }
4935 }
4936 // If an opening bracket is 1 character long and is typed while
4937 // text is selected, then surround that text with the bracket pair.
4938 else if auto_surround
4939 && bracket_pair.surround
4940 && is_bracket_pair_start
4941 && bracket_pair.start.chars().count() == 1
4942 {
4943 edits.push((selection.start..selection.start, text.clone()));
4944 edits.push((
4945 selection.end..selection.end,
4946 bracket_pair.end.as_str().into(),
4947 ));
4948 bracket_inserted = true;
4949 new_selections.push((
4950 Selection {
4951 id: selection.id,
4952 start: snapshot.anchor_after(selection.start),
4953 end: snapshot.anchor_before(selection.end),
4954 reversed: selection.reversed,
4955 goal: selection.goal,
4956 },
4957 0,
4958 ));
4959 continue;
4960 }
4961 }
4962 }
4963
4964 if self.auto_replace_emoji_shortcode
4965 && selection.is_empty()
4966 && text.as_ref().ends_with(':')
4967 && let Some(possible_emoji_short_code) =
4968 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4969 && !possible_emoji_short_code.is_empty()
4970 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4971 {
4972 let emoji_shortcode_start = Point::new(
4973 selection.start.row,
4974 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4975 );
4976
4977 // Remove shortcode from buffer
4978 edits.push((
4979 emoji_shortcode_start..selection.start,
4980 "".to_string().into(),
4981 ));
4982 new_selections.push((
4983 Selection {
4984 id: selection.id,
4985 start: snapshot.anchor_after(emoji_shortcode_start),
4986 end: snapshot.anchor_before(selection.start),
4987 reversed: selection.reversed,
4988 goal: selection.goal,
4989 },
4990 0,
4991 ));
4992
4993 // Insert emoji
4994 let selection_start_anchor = snapshot.anchor_after(selection.start);
4995 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4996 edits.push((selection.start..selection.end, emoji.to_string().into()));
4997
4998 continue;
4999 }
5000
5001 let next_is_adjacent = regions
5002 .peek()
5003 .is_some_and(|(next, _)| selection.end == next.start);
5004
5005 // If not handling any auto-close operation, then just replace the selected
5006 // text with the given input and move the selection to the end of the
5007 // newly inserted text.
5008 let anchor = if in_adjacent_group || next_is_adjacent {
5009 // After edits the right bias would shift those anchor to the next visible fragment
5010 // but we want to resolve to the previous one
5011 snapshot.anchor_before(selection.end)
5012 } else {
5013 snapshot.anchor_after(selection.end)
5014 };
5015
5016 if !self.linked_edit_ranges.is_empty() {
5017 let start_anchor = snapshot.anchor_before(selection.start);
5018
5019 let is_word_char = text.chars().next().is_none_or(|char| {
5020 let classifier = snapshot
5021 .char_classifier_at(start_anchor.to_offset(&snapshot))
5022 .scope_context(Some(CharScopeContext::LinkedEdit));
5023 classifier.is_word(char)
5024 });
5025 let is_dot = text.as_ref() == ".";
5026 let should_apply_linked_edit = is_word_char || is_dot;
5027
5028 if should_apply_linked_edit {
5029 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
5030 linked_edits.push(&self, anchor_range, text.clone(), cx);
5031 } else {
5032 clear_linked_edit_ranges = true;
5033 }
5034 }
5035
5036 new_selections.push((selection.map(|_| anchor), 0));
5037 edits.push((selection.start..selection.end, text.clone()));
5038
5039 has_adjacent_edits |= next_is_adjacent;
5040 in_adjacent_group = next_is_adjacent;
5041 }
5042
5043 if all_selections_read_only {
5044 return;
5045 }
5046
5047 drop(regions);
5048 drop(snapshot);
5049
5050 self.transact(window, cx, |this, window, cx| {
5051 if clear_linked_edit_ranges {
5052 this.linked_edit_ranges.clear();
5053 }
5054 let initial_buffer_versions =
5055 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
5056
5057 this.buffer.update(cx, |buffer, cx| {
5058 if has_adjacent_edits {
5059 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
5060 } else {
5061 buffer.edit(edits, this.autoindent_mode.clone(), cx);
5062 }
5063 });
5064 linked_edits.apply(cx);
5065 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
5066 let new_selection_deltas = new_selections.iter().map(|e| e.1);
5067 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5068 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
5069 new_anchor_selections,
5070 &map,
5071 )
5072 .zip(new_selection_deltas)
5073 .map(|(selection, delta)| Selection {
5074 id: selection.id,
5075 start: selection.start + delta,
5076 end: selection.end + delta,
5077 reversed: selection.reversed,
5078 goal: SelectionGoal::None,
5079 })
5080 .collect::<Vec<_>>();
5081
5082 let mut i = 0;
5083 for (position, delta, selection_id, pair) in new_autoclose_regions {
5084 let position = position.to_offset(map.buffer_snapshot()) + delta;
5085 let start = map.buffer_snapshot().anchor_before(position);
5086 let end = map.buffer_snapshot().anchor_after(position);
5087 while let Some(existing_state) = this.autoclose_regions.get(i) {
5088 match existing_state
5089 .range
5090 .start
5091 .cmp(&start, map.buffer_snapshot())
5092 {
5093 Ordering::Less => i += 1,
5094 Ordering::Greater => break,
5095 Ordering::Equal => {
5096 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
5097 Ordering::Less => i += 1,
5098 Ordering::Equal => break,
5099 Ordering::Greater => break,
5100 }
5101 }
5102 }
5103 }
5104 this.autoclose_regions.insert(
5105 i,
5106 AutocloseRegion {
5107 selection_id,
5108 range: start..end,
5109 pair,
5110 },
5111 );
5112 }
5113
5114 let had_active_edit_prediction = this.has_active_edit_prediction();
5115 this.change_selections(
5116 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
5117 window,
5118 cx,
5119 |s| s.select(new_selections),
5120 );
5121
5122 if !bracket_inserted
5123 && let Some(on_type_format_task) =
5124 this.trigger_on_type_formatting(text.to_string(), window, cx)
5125 {
5126 on_type_format_task.detach_and_log_err(cx);
5127 }
5128
5129 let editor_settings = EditorSettings::get_global(cx);
5130 if bracket_inserted
5131 && (editor_settings.auto_signature_help
5132 || editor_settings.show_signature_help_after_edits)
5133 {
5134 this.show_signature_help(&ShowSignatureHelp, window, cx);
5135 }
5136
5137 let trigger_in_words =
5138 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5139 if this.hard_wrap.is_some() {
5140 let latest: Range<Point> = this.selections.newest(&map).range();
5141 if latest.is_empty()
5142 && this
5143 .buffer()
5144 .read(cx)
5145 .snapshot(cx)
5146 .line_len(MultiBufferRow(latest.start.row))
5147 == latest.start.column
5148 {
5149 this.rewrap_impl(
5150 RewrapOptions {
5151 override_language_settings: true,
5152 preserve_existing_whitespace: true,
5153 line_length: None,
5154 },
5155 cx,
5156 )
5157 }
5158 }
5159 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5160 refresh_linked_ranges(this, window, cx);
5161 this.refresh_edit_prediction(true, false, window, cx);
5162 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5163 });
5164 }
5165
5166 fn find_possible_emoji_shortcode_at_position(
5167 snapshot: &MultiBufferSnapshot,
5168 position: Point,
5169 ) -> Option<String> {
5170 let mut chars = Vec::new();
5171 let mut found_colon = false;
5172 for char in snapshot.reversed_chars_at(position).take(100) {
5173 // Found a possible emoji shortcode in the middle of the buffer
5174 if found_colon {
5175 if char.is_whitespace() {
5176 chars.reverse();
5177 return Some(chars.iter().collect());
5178 }
5179 // If the previous character is not a whitespace, we are in the middle of a word
5180 // and we only want to complete the shortcode if the word is made up of other emojis
5181 let mut containing_word = String::new();
5182 for ch in snapshot
5183 .reversed_chars_at(position)
5184 .skip(chars.len() + 1)
5185 .take(100)
5186 {
5187 if ch.is_whitespace() {
5188 break;
5189 }
5190 containing_word.push(ch);
5191 }
5192 let containing_word = containing_word.chars().rev().collect::<String>();
5193 if util::word_consists_of_emojis(containing_word.as_str()) {
5194 chars.reverse();
5195 return Some(chars.iter().collect());
5196 }
5197 }
5198
5199 if char.is_whitespace() || !char.is_ascii() {
5200 return None;
5201 }
5202 if char == ':' {
5203 found_colon = true;
5204 } else {
5205 chars.push(char);
5206 }
5207 }
5208 // Found a possible emoji shortcode at the beginning of the buffer
5209 chars.reverse();
5210 Some(chars.iter().collect())
5211 }
5212
5213 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5214 if self.read_only(cx) {
5215 return;
5216 }
5217
5218 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5219 self.transact(window, cx, |this, window, cx| {
5220 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5221 let selections = this
5222 .selections
5223 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5224 let multi_buffer = this.buffer.read(cx);
5225 let buffer = multi_buffer.snapshot(cx);
5226 selections
5227 .iter()
5228 .map(|selection| {
5229 let start_point = selection.start.to_point(&buffer);
5230 let mut existing_indent =
5231 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5232 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5233 let start = selection.start;
5234 let end = selection.end;
5235 let selection_is_empty = start == end;
5236 let language_scope = buffer.language_scope_at(start);
5237 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5238 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5239 &buffer,
5240 start..end,
5241 language,
5242 )
5243 || NewlineConfig::insert_extra_newline_tree_sitter(
5244 &buffer,
5245 start..end,
5246 );
5247
5248 let mut newline_config = NewlineConfig::Newline {
5249 additional_indent: IndentSize::spaces(0),
5250 extra_line_additional_indent: if needs_extra_newline {
5251 Some(IndentSize::spaces(0))
5252 } else {
5253 None
5254 },
5255 prevent_auto_indent: false,
5256 };
5257
5258 let comment_delimiter = maybe!({
5259 if !selection_is_empty {
5260 return None;
5261 }
5262
5263 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5264 return None;
5265 }
5266
5267 return comment_delimiter_for_newline(
5268 &start_point,
5269 &buffer,
5270 language,
5271 );
5272 });
5273
5274 let doc_delimiter = maybe!({
5275 if !selection_is_empty {
5276 return None;
5277 }
5278
5279 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5280 return None;
5281 }
5282
5283 return documentation_delimiter_for_newline(
5284 &start_point,
5285 &buffer,
5286 language,
5287 &mut newline_config,
5288 );
5289 });
5290
5291 let list_delimiter = maybe!({
5292 if !selection_is_empty {
5293 return None;
5294 }
5295
5296 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5297 return None;
5298 }
5299
5300 return list_delimiter_for_newline(
5301 &start_point,
5302 &buffer,
5303 language,
5304 &mut newline_config,
5305 );
5306 });
5307
5308 (
5309 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5310 newline_config,
5311 )
5312 } else {
5313 (
5314 None,
5315 NewlineConfig::Newline {
5316 additional_indent: IndentSize::spaces(0),
5317 extra_line_additional_indent: None,
5318 prevent_auto_indent: false,
5319 },
5320 )
5321 };
5322
5323 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5324 NewlineConfig::ClearCurrentLine => {
5325 let row_start =
5326 buffer.point_to_offset(Point::new(start_point.row, 0));
5327 (row_start, String::new(), false)
5328 }
5329 NewlineConfig::UnindentCurrentLine { continuation } => {
5330 let row_start =
5331 buffer.point_to_offset(Point::new(start_point.row, 0));
5332 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5333 let tab_size_indent = IndentSize::spaces(tab_size.get());
5334 let reduced_indent =
5335 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5336 let mut new_text = String::new();
5337 new_text.extend(reduced_indent.chars());
5338 new_text.push_str(continuation);
5339 (row_start, new_text, true)
5340 }
5341 NewlineConfig::Newline {
5342 additional_indent,
5343 extra_line_additional_indent,
5344 prevent_auto_indent,
5345 } => {
5346 let auto_indent_mode =
5347 buffer.language_settings_at(start, cx).auto_indent;
5348 let preserve_indent =
5349 auto_indent_mode != language::AutoIndentMode::None;
5350 let apply_syntax_indent =
5351 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5352 let capacity_for_delimiter =
5353 delimiter.as_deref().map(str::len).unwrap_or_default();
5354 let existing_indent_len = if preserve_indent {
5355 existing_indent.len as usize
5356 } else {
5357 0
5358 };
5359 let extra_line_len = extra_line_additional_indent
5360 .map(|i| 1 + existing_indent_len + i.len as usize)
5361 .unwrap_or(0);
5362 let mut new_text = String::with_capacity(
5363 1 + capacity_for_delimiter
5364 + existing_indent_len
5365 + additional_indent.len as usize
5366 + extra_line_len,
5367 );
5368 new_text.push('\n');
5369 if preserve_indent {
5370 new_text.extend(existing_indent.chars());
5371 }
5372 new_text.extend(additional_indent.chars());
5373 if let Some(delimiter) = &delimiter {
5374 new_text.push_str(delimiter);
5375 }
5376 if let Some(extra_indent) = extra_line_additional_indent {
5377 new_text.push('\n');
5378 if preserve_indent {
5379 new_text.extend(existing_indent.chars());
5380 }
5381 new_text.extend(extra_indent.chars());
5382 }
5383 (
5384 start,
5385 new_text,
5386 *prevent_auto_indent || !apply_syntax_indent,
5387 )
5388 }
5389 };
5390
5391 let anchor = buffer.anchor_after(end);
5392 let new_selection = selection.map(|_| anchor);
5393 (
5394 ((edit_start..end, new_text), prevent_auto_indent),
5395 (newline_config.has_extra_line(), new_selection),
5396 )
5397 })
5398 .unzip()
5399 };
5400
5401 let mut auto_indent_edits = Vec::new();
5402 let mut edits = Vec::new();
5403 for (edit, prevent_auto_indent) in edits_with_flags {
5404 if prevent_auto_indent {
5405 edits.push(edit);
5406 } else {
5407 auto_indent_edits.push(edit);
5408 }
5409 }
5410 if !edits.is_empty() {
5411 this.edit(edits, cx);
5412 }
5413 if !auto_indent_edits.is_empty() {
5414 this.edit_with_autoindent(auto_indent_edits, cx);
5415 }
5416
5417 let buffer = this.buffer.read(cx).snapshot(cx);
5418 let new_selections = selection_info
5419 .into_iter()
5420 .map(|(extra_newline_inserted, new_selection)| {
5421 let mut cursor = new_selection.end.to_point(&buffer);
5422 if extra_newline_inserted {
5423 cursor.row -= 1;
5424 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5425 }
5426 new_selection.map(|_| cursor)
5427 })
5428 .collect();
5429
5430 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5431 this.refresh_edit_prediction(true, false, window, cx);
5432 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5433 task.detach_and_log_err(cx);
5434 }
5435 });
5436 }
5437
5438 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5439 if self.read_only(cx) {
5440 return;
5441 }
5442
5443 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5444
5445 let buffer = self.buffer.read(cx);
5446 let snapshot = buffer.snapshot(cx);
5447
5448 let mut edits = Vec::new();
5449 let mut rows = Vec::new();
5450
5451 for (rows_inserted, selection) in self
5452 .selections
5453 .all_adjusted(&self.display_snapshot(cx))
5454 .into_iter()
5455 .enumerate()
5456 {
5457 let cursor = selection.head();
5458 let row = cursor.row;
5459
5460 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5461
5462 let newline = "\n".to_string();
5463 edits.push((start_of_line..start_of_line, newline));
5464
5465 rows.push(row + rows_inserted as u32);
5466 }
5467
5468 self.transact(window, cx, |editor, window, cx| {
5469 editor.edit(edits, cx);
5470
5471 editor.change_selections(Default::default(), window, cx, |s| {
5472 let mut index = 0;
5473 s.move_cursors_with(&mut |map, _, _| {
5474 let row = rows[index];
5475 index += 1;
5476
5477 let point = Point::new(row, 0);
5478 let boundary = map.next_line_boundary(point).1;
5479 let clipped = map.clip_point(boundary, Bias::Left);
5480
5481 (clipped, SelectionGoal::None)
5482 });
5483 });
5484
5485 let mut indent_edits = Vec::new();
5486 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5487 for row in rows {
5488 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5489 for (row, indent) in indents {
5490 if indent.len == 0 {
5491 continue;
5492 }
5493
5494 let text = match indent.kind {
5495 IndentKind::Space => " ".repeat(indent.len as usize),
5496 IndentKind::Tab => "\t".repeat(indent.len as usize),
5497 };
5498 let point = Point::new(row.0, 0);
5499 indent_edits.push((point..point, text));
5500 }
5501 }
5502 editor.edit(indent_edits, cx);
5503 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5504 format.detach_and_log_err(cx);
5505 }
5506 });
5507 }
5508
5509 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5510 if self.read_only(cx) {
5511 return;
5512 }
5513
5514 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5515
5516 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5517 let mut rows = Vec::new();
5518 let mut rows_inserted = 0;
5519
5520 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5521 let cursor = selection.head();
5522 let row = cursor.row;
5523
5524 let point = Point::new(row, 0);
5525 let Some((buffer_handle, buffer_point, _)) =
5526 self.buffer.read(cx).point_to_buffer_point(point, cx)
5527 else {
5528 continue;
5529 };
5530
5531 buffer_edits
5532 .entry(buffer_handle.entity_id())
5533 .or_insert_with(|| (buffer_handle, Vec::new()))
5534 .1
5535 .push(buffer_point);
5536
5537 rows_inserted += 1;
5538 rows.push(row + rows_inserted);
5539 }
5540
5541 self.transact(window, cx, |editor, window, cx| {
5542 for (_, (buffer_handle, points)) in &buffer_edits {
5543 buffer_handle.update(cx, |buffer, cx| {
5544 let edits: Vec<_> = points
5545 .iter()
5546 .map(|point| {
5547 let target = Point::new(point.row + 1, 0);
5548 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5549 (start_of_line..start_of_line, "\n")
5550 })
5551 .collect();
5552 buffer.edit(edits, None, cx);
5553 });
5554 }
5555
5556 editor.change_selections(Default::default(), window, cx, |s| {
5557 let mut index = 0;
5558 s.move_cursors_with(&mut |map, _, _| {
5559 let row = rows[index];
5560 index += 1;
5561
5562 let point = Point::new(row, 0);
5563 let boundary = map.next_line_boundary(point).1;
5564 let clipped = map.clip_point(boundary, Bias::Left);
5565
5566 (clipped, SelectionGoal::None)
5567 });
5568 });
5569
5570 let mut indent_edits = Vec::new();
5571 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5572 for row in rows {
5573 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5574 for (row, indent) in indents {
5575 if indent.len == 0 {
5576 continue;
5577 }
5578
5579 let text = match indent.kind {
5580 IndentKind::Space => " ".repeat(indent.len as usize),
5581 IndentKind::Tab => "\t".repeat(indent.len as usize),
5582 };
5583 let point = Point::new(row.0, 0);
5584 indent_edits.push((point..point, text));
5585 }
5586 }
5587 editor.edit(indent_edits, cx);
5588 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5589 format.detach_and_log_err(cx);
5590 }
5591 });
5592 }
5593
5594 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5595 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5596 original_indent_columns: Vec::new(),
5597 });
5598 self.replace_selections(text, autoindent, window, cx, false);
5599 }
5600
5601 /// Replaces the editor's selections with the provided `text`, applying the
5602 /// given `autoindent_mode` (`None` will skip autoindentation).
5603 ///
5604 /// Early returns if the editor is in read-only mode, without applying any
5605 /// edits.
5606 fn replace_selections(
5607 &mut self,
5608 text: &str,
5609 autoindent_mode: Option<AutoindentMode>,
5610 window: &mut Window,
5611 cx: &mut Context<Self>,
5612 apply_linked_edits: bool,
5613 ) {
5614 if self.read_only(cx) {
5615 return;
5616 }
5617
5618 let text: Arc<str> = text.into();
5619 self.transact(window, cx, |this, window, cx| {
5620 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5621 let linked_edits = if apply_linked_edits {
5622 this.linked_edits_for_selections(text.clone(), cx)
5623 } else {
5624 LinkedEdits::new()
5625 };
5626
5627 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5628 let anchors = {
5629 let snapshot = buffer.read(cx);
5630 old_selections
5631 .iter()
5632 .map(|s| {
5633 let anchor = snapshot.anchor_after(s.head());
5634 s.map(|_| anchor)
5635 })
5636 .collect::<Vec<_>>()
5637 };
5638 buffer.edit(
5639 old_selections
5640 .iter()
5641 .map(|s| (s.start..s.end, text.clone())),
5642 autoindent_mode,
5643 cx,
5644 );
5645 anchors
5646 });
5647
5648 linked_edits.apply(cx);
5649
5650 this.change_selections(Default::default(), window, cx, |s| {
5651 s.select_anchors(selection_anchors);
5652 });
5653
5654 if apply_linked_edits {
5655 refresh_linked_ranges(this, window, cx);
5656 }
5657
5658 cx.notify();
5659 });
5660 }
5661
5662 /// Collects linked edits for the current selections, pairing each linked
5663 /// range with `text`.
5664 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5665 let mut linked_edits = LinkedEdits::new();
5666 if !self.linked_edit_ranges.is_empty() {
5667 for selection in self.selections.disjoint_anchors() {
5668 let start = selection.start.text_anchor;
5669 let end = selection.end.text_anchor;
5670 linked_edits.push(self, start..end, text.clone(), cx);
5671 }
5672 }
5673 linked_edits
5674 }
5675
5676 /// Deletes the content covered by the current selections and applies
5677 /// linked edits.
5678 pub fn delete_selections_with_linked_edits(
5679 &mut self,
5680 window: &mut Window,
5681 cx: &mut Context<Self>,
5682 ) {
5683 self.replace_selections("", None, window, cx, true);
5684 }
5685
5686 #[cfg(any(test, feature = "test-support"))]
5687 pub fn set_linked_edit_ranges_for_testing(
5688 &mut self,
5689 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5690 cx: &mut Context<Self>,
5691 ) -> Option<()> {
5692 let Some((buffer, _)) = self
5693 .buffer
5694 .read(cx)
5695 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5696 else {
5697 return None;
5698 };
5699 let buffer = buffer.read(cx);
5700 let buffer_id = buffer.remote_id();
5701 let mut linked_ranges = Vec::with_capacity(ranges.len());
5702 for (base_range, linked_ranges_points) in ranges {
5703 let base_anchor =
5704 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5705 let linked_anchors = linked_ranges_points
5706 .into_iter()
5707 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5708 .collect();
5709 linked_ranges.push((base_anchor, linked_anchors));
5710 }
5711 let mut map = HashMap::default();
5712 map.insert(buffer_id, linked_ranges);
5713 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5714 Some(())
5715 }
5716
5717 fn trigger_completion_on_input(
5718 &mut self,
5719 text: &str,
5720 trigger_in_words: bool,
5721 window: &mut Window,
5722 cx: &mut Context<Self>,
5723 ) {
5724 let completions_source = self
5725 .context_menu
5726 .borrow()
5727 .as_ref()
5728 .and_then(|menu| match menu {
5729 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5730 CodeContextMenu::CodeActions(_) => None,
5731 });
5732
5733 match completions_source {
5734 Some(CompletionsMenuSource::Words { .. }) => {
5735 self.open_or_update_completions_menu(
5736 Some(CompletionsMenuSource::Words {
5737 ignore_threshold: false,
5738 }),
5739 None,
5740 trigger_in_words,
5741 window,
5742 cx,
5743 );
5744 }
5745 _ => self.open_or_update_completions_menu(
5746 None,
5747 Some(text.to_owned()).filter(|x| !x.is_empty()),
5748 true,
5749 window,
5750 cx,
5751 ),
5752 }
5753 }
5754
5755 /// If any empty selections is touching the start of its innermost containing autoclose
5756 /// region, expand it to select the brackets.
5757 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5758 let selections = self
5759 .selections
5760 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5761 let buffer = self.buffer.read(cx).read(cx);
5762 let new_selections = self
5763 .selections_with_autoclose_regions(selections, &buffer)
5764 .map(|(mut selection, region)| {
5765 if !selection.is_empty() {
5766 return selection;
5767 }
5768
5769 if let Some(region) = region {
5770 let mut range = region.range.to_offset(&buffer);
5771 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5772 range.start -= region.pair.start.len();
5773 if buffer.contains_str_at(range.start, ®ion.pair.start)
5774 && buffer.contains_str_at(range.end, ®ion.pair.end)
5775 {
5776 range.end += region.pair.end.len();
5777 selection.start = range.start;
5778 selection.end = range.end;
5779
5780 return selection;
5781 }
5782 }
5783 }
5784
5785 let always_treat_brackets_as_autoclosed = buffer
5786 .language_settings_at(selection.start, cx)
5787 .always_treat_brackets_as_autoclosed;
5788
5789 if !always_treat_brackets_as_autoclosed {
5790 return selection;
5791 }
5792
5793 if let Some(scope) = buffer.language_scope_at(selection.start) {
5794 for (pair, enabled) in scope.brackets() {
5795 if !enabled || !pair.close {
5796 continue;
5797 }
5798
5799 if buffer.contains_str_at(selection.start, &pair.end) {
5800 let pair_start_len = pair.start.len();
5801 if buffer.contains_str_at(
5802 selection.start.saturating_sub_usize(pair_start_len),
5803 &pair.start,
5804 ) {
5805 selection.start -= pair_start_len;
5806 selection.end += pair.end.len();
5807
5808 return selection;
5809 }
5810 }
5811 }
5812 }
5813
5814 selection
5815 })
5816 .collect();
5817
5818 drop(buffer);
5819 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5820 selections.select(new_selections)
5821 });
5822 }
5823
5824 /// Iterate the given selections, and for each one, find the smallest surrounding
5825 /// autoclose region. This uses the ordering of the selections and the autoclose
5826 /// regions to avoid repeated comparisons.
5827 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5828 &'a self,
5829 selections: impl IntoIterator<Item = Selection<D>>,
5830 buffer: &'a MultiBufferSnapshot,
5831 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5832 let mut i = 0;
5833 let mut regions = self.autoclose_regions.as_slice();
5834 selections.into_iter().map(move |selection| {
5835 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5836
5837 let mut enclosing = None;
5838 while let Some(pair_state) = regions.get(i) {
5839 if pair_state.range.end.to_offset(buffer) < range.start {
5840 regions = ®ions[i + 1..];
5841 i = 0;
5842 } else if pair_state.range.start.to_offset(buffer) > range.end {
5843 break;
5844 } else {
5845 if pair_state.selection_id == selection.id {
5846 enclosing = Some(pair_state);
5847 }
5848 i += 1;
5849 }
5850 }
5851
5852 (selection, enclosing)
5853 })
5854 }
5855
5856 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5857 fn invalidate_autoclose_regions(
5858 &mut self,
5859 mut selections: &[Selection<Anchor>],
5860 buffer: &MultiBufferSnapshot,
5861 ) {
5862 self.autoclose_regions.retain(|state| {
5863 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5864 return false;
5865 }
5866
5867 let mut i = 0;
5868 while let Some(selection) = selections.get(i) {
5869 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5870 selections = &selections[1..];
5871 continue;
5872 }
5873 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5874 break;
5875 }
5876 if selection.id == state.selection_id {
5877 return true;
5878 } else {
5879 i += 1;
5880 }
5881 }
5882 false
5883 });
5884 }
5885
5886 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5887 let offset = position.to_offset(buffer);
5888 let (word_range, kind) =
5889 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5890 if offset > word_range.start && kind == Some(CharKind::Word) {
5891 Some(
5892 buffer
5893 .text_for_range(word_range.start..offset)
5894 .collect::<String>(),
5895 )
5896 } else {
5897 None
5898 }
5899 }
5900
5901 pub fn visible_excerpts(
5902 &self,
5903 lsp_related_only: bool,
5904 cx: &mut Context<Editor>,
5905 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5906 let project = self.project().cloned();
5907 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5908 let multi_buffer = self.buffer().read(cx);
5909 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5910 multi_buffer_snapshot
5911 .range_to_buffer_ranges(
5912 self.multi_buffer_visible_range(&display_snapshot, cx)
5913 .to_inclusive(),
5914 )
5915 .into_iter()
5916 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5917 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5918 if !lsp_related_only {
5919 return Some((
5920 excerpt_id,
5921 (
5922 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5923 buffer.version().clone(),
5924 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5925 ),
5926 ));
5927 }
5928
5929 let project = project.as_ref()?.read(cx);
5930 let buffer_file = project::File::from_dyn(buffer.file())?;
5931 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5932 let worktree_entry = buffer_worktree
5933 .read(cx)
5934 .entry_for_id(buffer_file.project_entry_id()?)?;
5935 if worktree_entry.is_ignored {
5936 None
5937 } else {
5938 Some((
5939 excerpt_id,
5940 (
5941 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5942 buffer.version().clone(),
5943 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5944 ),
5945 ))
5946 }
5947 })
5948 .collect()
5949 }
5950
5951 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5952 TextLayoutDetails {
5953 text_system: window.text_system().clone(),
5954 editor_style: self.style.clone().unwrap(),
5955 rem_size: window.rem_size(),
5956 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5957 visible_rows: self.visible_line_count(),
5958 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5959 }
5960 }
5961
5962 fn trigger_on_type_formatting(
5963 &self,
5964 input: String,
5965 window: &mut Window,
5966 cx: &mut Context<Self>,
5967 ) -> Option<Task<Result<()>>> {
5968 if input.chars().count() != 1 {
5969 return None;
5970 }
5971
5972 let project = self.project()?;
5973 let position = self.selections.newest_anchor().head();
5974 let (buffer, buffer_position) = self
5975 .buffer
5976 .read(cx)
5977 .text_anchor_for_position(position, cx)?;
5978
5979 let settings = LanguageSettings::for_buffer_at(&buffer.read(cx), buffer_position, cx);
5980 if !settings.use_on_type_format {
5981 return None;
5982 }
5983
5984 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5985 // hence we do LSP request & edit on host side only — add formats to host's history.
5986 let push_to_lsp_host_history = true;
5987 // If this is not the host, append its history with new edits.
5988 let push_to_client_history = project.read(cx).is_via_collab();
5989
5990 let on_type_formatting = project.update(cx, |project, cx| {
5991 project.on_type_format(
5992 buffer.clone(),
5993 buffer_position,
5994 input,
5995 push_to_lsp_host_history,
5996 cx,
5997 )
5998 });
5999 Some(cx.spawn_in(window, async move |editor, cx| {
6000 if let Some(transaction) = on_type_formatting.await? {
6001 if push_to_client_history {
6002 buffer.update(cx, |buffer, _| {
6003 buffer.push_transaction(transaction, Instant::now());
6004 buffer.finalize_last_transaction();
6005 });
6006 }
6007 editor.update(cx, |editor, cx| {
6008 editor.refresh_document_highlights(cx);
6009 })?;
6010 }
6011 Ok(())
6012 }))
6013 }
6014
6015 pub fn show_word_completions(
6016 &mut self,
6017 _: &ShowWordCompletions,
6018 window: &mut Window,
6019 cx: &mut Context<Self>,
6020 ) {
6021 self.open_or_update_completions_menu(
6022 Some(CompletionsMenuSource::Words {
6023 ignore_threshold: true,
6024 }),
6025 None,
6026 false,
6027 window,
6028 cx,
6029 );
6030 }
6031
6032 pub fn show_completions(
6033 &mut self,
6034 _: &ShowCompletions,
6035 window: &mut Window,
6036 cx: &mut Context<Self>,
6037 ) {
6038 self.open_or_update_completions_menu(None, None, false, window, cx);
6039 }
6040
6041 fn open_or_update_completions_menu(
6042 &mut self,
6043 requested_source: Option<CompletionsMenuSource>,
6044 trigger: Option<String>,
6045 trigger_in_words: bool,
6046 window: &mut Window,
6047 cx: &mut Context<Self>,
6048 ) {
6049 if self.pending_rename.is_some() {
6050 return;
6051 }
6052
6053 let completions_source = self
6054 .context_menu
6055 .borrow()
6056 .as_ref()
6057 .and_then(|menu| match menu {
6058 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
6059 CodeContextMenu::CodeActions(_) => None,
6060 });
6061
6062 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
6063
6064 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
6065 // inserted and selected. To handle that case, the start of the selection is used so that
6066 // the menu starts with all choices.
6067 let position = self
6068 .selections
6069 .newest_anchor()
6070 .start
6071 .bias_right(&multibuffer_snapshot);
6072 if position.diff_base_anchor.is_some() {
6073 return;
6074 }
6075 let buffer_position = multibuffer_snapshot.anchor_before(position);
6076 let Some(buffer) = buffer_position
6077 .text_anchor
6078 .buffer_id
6079 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
6080 else {
6081 return;
6082 };
6083 let buffer_snapshot = buffer.read(cx).snapshot();
6084
6085 let menu_is_open = matches!(
6086 self.context_menu.borrow().as_ref(),
6087 Some(CodeContextMenu::Completions(_))
6088 );
6089
6090 let language = buffer_snapshot
6091 .language_at(buffer_position.text_anchor)
6092 .map(|language| language.name());
6093 let language_settings = multibuffer_snapshot.language_settings_at(buffer_position, cx);
6094 let completion_settings = language_settings.completions.clone();
6095
6096 let show_completions_on_input = self
6097 .show_completions_on_input_override
6098 .unwrap_or(language_settings.show_completions_on_input);
6099 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
6100 return;
6101 }
6102
6103 let query: Option<Arc<String>> =
6104 Self::completion_query(&multibuffer_snapshot, buffer_position)
6105 .map(|query| query.into());
6106
6107 drop(multibuffer_snapshot);
6108
6109 // Hide the current completions menu when query is empty. Without this, cached
6110 // completions from before the trigger char may be reused (#32774).
6111 if query.is_none() && menu_is_open {
6112 self.hide_context_menu(window, cx);
6113 }
6114
6115 let mut ignore_word_threshold = false;
6116 let provider = match requested_source {
6117 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
6118 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
6119 ignore_word_threshold = ignore_threshold;
6120 None
6121 }
6122 Some(CompletionsMenuSource::SnippetChoices)
6123 | Some(CompletionsMenuSource::SnippetsOnly) => {
6124 log::error!("bug: SnippetChoices requested_source is not handled");
6125 None
6126 }
6127 };
6128
6129 let sort_completions = provider
6130 .as_ref()
6131 .is_some_and(|provider| provider.sort_completions());
6132
6133 let filter_completions = provider
6134 .as_ref()
6135 .is_none_or(|provider| provider.filter_completions());
6136
6137 let was_snippets_only = matches!(
6138 completions_source,
6139 Some(CompletionsMenuSource::SnippetsOnly)
6140 );
6141
6142 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
6143 if filter_completions {
6144 menu.filter(
6145 query.clone().unwrap_or_default(),
6146 buffer_position.text_anchor,
6147 &buffer,
6148 provider.clone(),
6149 window,
6150 cx,
6151 );
6152 }
6153 // When `is_incomplete` is false, no need to re-query completions when the current query
6154 // is a suffix of the initial query.
6155 let was_complete = !menu.is_incomplete;
6156 if was_complete && !was_snippets_only {
6157 // If the new query is a suffix of the old query (typing more characters) and
6158 // the previous result was complete, the existing completions can be filtered.
6159 //
6160 // Note that snippet completions are always complete.
6161 let query_matches = match (&menu.initial_query, &query) {
6162 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6163 (None, _) => true,
6164 _ => false,
6165 };
6166 if query_matches {
6167 let position_matches = if menu.initial_position == position {
6168 true
6169 } else {
6170 let snapshot = self.buffer.read(cx).read(cx);
6171 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6172 };
6173 if position_matches {
6174 return;
6175 }
6176 }
6177 }
6178 };
6179
6180 let Anchor {
6181 excerpt_id: buffer_excerpt_id,
6182 text_anchor: buffer_position,
6183 ..
6184 } = buffer_position;
6185
6186 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6187 buffer_snapshot.surrounding_word(buffer_position, None)
6188 {
6189 let word_to_exclude = buffer_snapshot
6190 .text_for_range(word_range.clone())
6191 .collect::<String>();
6192 (
6193 buffer_snapshot.anchor_before(word_range.start)
6194 ..buffer_snapshot.anchor_after(buffer_position),
6195 Some(word_to_exclude),
6196 )
6197 } else {
6198 (buffer_position..buffer_position, None)
6199 };
6200
6201 let show_completion_documentation = buffer_snapshot
6202 .settings_at(buffer_position, cx)
6203 .show_completion_documentation;
6204
6205 // The document can be large, so stay in reasonable bounds when searching for words,
6206 // otherwise completion pop-up might be slow to appear.
6207 const WORD_LOOKUP_ROWS: u32 = 5_000;
6208 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6209 let min_word_search = buffer_snapshot.clip_point(
6210 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6211 Bias::Left,
6212 );
6213 let max_word_search = buffer_snapshot.clip_point(
6214 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6215 Bias::Right,
6216 );
6217 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6218 ..buffer_snapshot.point_to_offset(max_word_search);
6219
6220 let skip_digits = query
6221 .as_ref()
6222 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6223
6224 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6225 trigger.as_ref().is_none_or(|trigger| {
6226 provider.is_completion_trigger(
6227 &buffer,
6228 position.text_anchor,
6229 trigger,
6230 trigger_in_words,
6231 cx,
6232 )
6233 })
6234 });
6235
6236 let provider_responses = if let Some(provider) = &provider
6237 && load_provider_completions
6238 {
6239 let trigger_character =
6240 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6241 let completion_context = CompletionContext {
6242 trigger_kind: match &trigger_character {
6243 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6244 None => CompletionTriggerKind::INVOKED,
6245 },
6246 trigger_character,
6247 };
6248
6249 provider.completions(
6250 buffer_excerpt_id,
6251 &buffer,
6252 buffer_position,
6253 completion_context,
6254 window,
6255 cx,
6256 )
6257 } else {
6258 Task::ready(Ok(Vec::new()))
6259 };
6260
6261 let load_word_completions = if !self.word_completions_enabled {
6262 false
6263 } else if requested_source
6264 == Some(CompletionsMenuSource::Words {
6265 ignore_threshold: true,
6266 })
6267 {
6268 true
6269 } else {
6270 load_provider_completions
6271 && completion_settings.words != WordsCompletionMode::Disabled
6272 && (ignore_word_threshold || {
6273 let words_min_length = completion_settings.words_min_length;
6274 // check whether word has at least `words_min_length` characters
6275 let query_chars = query.iter().flat_map(|q| q.chars());
6276 query_chars.take(words_min_length).count() == words_min_length
6277 })
6278 };
6279
6280 let mut words = if load_word_completions {
6281 cx.background_spawn({
6282 let buffer_snapshot = buffer_snapshot.clone();
6283 async move {
6284 buffer_snapshot.words_in_range(WordsQuery {
6285 fuzzy_contents: None,
6286 range: word_search_range,
6287 skip_digits,
6288 })
6289 }
6290 })
6291 } else {
6292 Task::ready(BTreeMap::default())
6293 };
6294
6295 let snippets = if let Some(provider) = &provider
6296 && provider.show_snippets()
6297 && let Some(project) = self.project()
6298 {
6299 let char_classifier = buffer_snapshot
6300 .char_classifier_at(buffer_position)
6301 .scope_context(Some(CharScopeContext::Completion));
6302 project.update(cx, |project, cx| {
6303 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6304 })
6305 } else {
6306 Task::ready(Ok(CompletionResponse {
6307 completions: Vec::new(),
6308 display_options: Default::default(),
6309 is_incomplete: false,
6310 }))
6311 };
6312
6313 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6314
6315 let id = post_inc(&mut self.next_completion_id);
6316 let task = cx.spawn_in(window, async move |editor, cx| {
6317 let Ok(()) = editor.update(cx, |this, _| {
6318 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6319 }) else {
6320 return;
6321 };
6322
6323 // TODO: Ideally completions from different sources would be selectively re-queried, so
6324 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6325 let mut completions = Vec::new();
6326 let mut is_incomplete = false;
6327 let mut display_options: Option<CompletionDisplayOptions> = None;
6328 if let Some(provider_responses) = provider_responses.await.log_err()
6329 && !provider_responses.is_empty()
6330 {
6331 for response in provider_responses {
6332 completions.extend(response.completions);
6333 is_incomplete = is_incomplete || response.is_incomplete;
6334 match display_options.as_mut() {
6335 None => {
6336 display_options = Some(response.display_options);
6337 }
6338 Some(options) => options.merge(&response.display_options),
6339 }
6340 }
6341 if completion_settings.words == WordsCompletionMode::Fallback {
6342 words = Task::ready(BTreeMap::default());
6343 }
6344 }
6345 let display_options = display_options.unwrap_or_default();
6346
6347 let mut words = words.await;
6348 if let Some(word_to_exclude) = &word_to_exclude {
6349 words.remove(word_to_exclude);
6350 }
6351 for lsp_completion in &completions {
6352 words.remove(&lsp_completion.new_text);
6353 }
6354 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6355 replace_range: word_replace_range.clone(),
6356 new_text: word.clone(),
6357 label: CodeLabel::plain(word, None),
6358 match_start: None,
6359 snippet_deduplication_key: None,
6360 icon_path: None,
6361 documentation: None,
6362 source: CompletionSource::BufferWord {
6363 word_range,
6364 resolved: false,
6365 },
6366 insert_text_mode: Some(InsertTextMode::AS_IS),
6367 confirm: None,
6368 }));
6369
6370 completions.extend(
6371 snippets
6372 .await
6373 .into_iter()
6374 .flat_map(|response| response.completions),
6375 );
6376
6377 let menu = if completions.is_empty() {
6378 None
6379 } else {
6380 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6381 let languages = editor
6382 .workspace
6383 .as_ref()
6384 .and_then(|(workspace, _)| workspace.upgrade())
6385 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6386 let menu = CompletionsMenu::new(
6387 id,
6388 requested_source.unwrap_or(if load_provider_completions {
6389 CompletionsMenuSource::Normal
6390 } else {
6391 CompletionsMenuSource::SnippetsOnly
6392 }),
6393 sort_completions,
6394 show_completion_documentation,
6395 position,
6396 query.clone(),
6397 is_incomplete,
6398 buffer.clone(),
6399 completions.into(),
6400 editor
6401 .context_menu()
6402 .borrow_mut()
6403 .as_ref()
6404 .map(|menu| menu.primary_scroll_handle()),
6405 display_options,
6406 snippet_sort_order,
6407 languages,
6408 language,
6409 cx,
6410 );
6411
6412 let query = if filter_completions { query } else { None };
6413 let matches_task = menu.do_async_filtering(
6414 query.unwrap_or_default(),
6415 buffer_position,
6416 &buffer,
6417 cx,
6418 );
6419 (menu, matches_task)
6420 }) else {
6421 return;
6422 };
6423
6424 let matches = matches_task.await;
6425
6426 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6427 // Newer menu already set, so exit.
6428 if let Some(CodeContextMenu::Completions(prev_menu)) =
6429 editor.context_menu.borrow().as_ref()
6430 && prev_menu.id > id
6431 {
6432 return;
6433 };
6434
6435 // Only valid to take prev_menu because either the new menu is immediately set
6436 // below, or the menu is hidden.
6437 if let Some(CodeContextMenu::Completions(prev_menu)) =
6438 editor.context_menu.borrow_mut().take()
6439 {
6440 let position_matches =
6441 if prev_menu.initial_position == menu.initial_position {
6442 true
6443 } else {
6444 let snapshot = editor.buffer.read(cx).read(cx);
6445 prev_menu.initial_position.to_offset(&snapshot)
6446 == menu.initial_position.to_offset(&snapshot)
6447 };
6448 if position_matches {
6449 // Preserve markdown cache before `set_filter_results` because it will
6450 // try to populate the documentation cache.
6451 menu.preserve_markdown_cache(prev_menu);
6452 }
6453 };
6454
6455 menu.set_filter_results(matches, provider, window, cx);
6456 }) else {
6457 return;
6458 };
6459
6460 menu.visible().then_some(menu)
6461 };
6462
6463 editor
6464 .update_in(cx, |editor, window, cx| {
6465 if editor.focus_handle.is_focused(window)
6466 && let Some(menu) = menu
6467 {
6468 *editor.context_menu.borrow_mut() =
6469 Some(CodeContextMenu::Completions(menu));
6470
6471 crate::hover_popover::hide_hover(editor, cx);
6472 if editor.show_edit_predictions_in_menu() {
6473 editor.update_visible_edit_prediction(window, cx);
6474 } else {
6475 editor
6476 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6477 }
6478
6479 cx.notify();
6480 return;
6481 }
6482
6483 if editor.completion_tasks.len() <= 1 {
6484 // If there are no more completion tasks and the last menu was empty, we should hide it.
6485 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6486 // If it was already hidden and we don't show edit predictions in the menu,
6487 // we should also show the edit prediction when available.
6488 if was_hidden && editor.show_edit_predictions_in_menu() {
6489 editor.update_visible_edit_prediction(window, cx);
6490 }
6491 }
6492 })
6493 .ok();
6494 });
6495
6496 self.completion_tasks.push((id, task));
6497 }
6498
6499 #[cfg(any(test, feature = "test-support"))]
6500 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6501 let menu = self.context_menu.borrow();
6502 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6503 let completions = menu.completions.borrow();
6504 Some(completions.to_vec())
6505 } else {
6506 None
6507 }
6508 }
6509
6510 pub fn with_completions_menu_matching_id<R>(
6511 &self,
6512 id: CompletionId,
6513 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6514 ) -> R {
6515 let mut context_menu = self.context_menu.borrow_mut();
6516 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6517 return f(None);
6518 };
6519 if completions_menu.id != id {
6520 return f(None);
6521 }
6522 f(Some(completions_menu))
6523 }
6524
6525 pub fn confirm_completion(
6526 &mut self,
6527 action: &ConfirmCompletion,
6528 window: &mut Window,
6529 cx: &mut Context<Self>,
6530 ) -> Option<Task<Result<()>>> {
6531 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6532 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6533 }
6534
6535 pub fn confirm_completion_insert(
6536 &mut self,
6537 _: &ConfirmCompletionInsert,
6538 window: &mut Window,
6539 cx: &mut Context<Self>,
6540 ) -> Option<Task<Result<()>>> {
6541 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6542 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6543 }
6544
6545 pub fn confirm_completion_replace(
6546 &mut self,
6547 _: &ConfirmCompletionReplace,
6548 window: &mut Window,
6549 cx: &mut Context<Self>,
6550 ) -> Option<Task<Result<()>>> {
6551 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6552 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6553 }
6554
6555 pub fn compose_completion(
6556 &mut self,
6557 action: &ComposeCompletion,
6558 window: &mut Window,
6559 cx: &mut Context<Self>,
6560 ) -> Option<Task<Result<()>>> {
6561 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6562 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6563 }
6564
6565 fn do_completion(
6566 &mut self,
6567 item_ix: Option<usize>,
6568 intent: CompletionIntent,
6569 window: &mut Window,
6570 cx: &mut Context<Editor>,
6571 ) -> Option<Task<Result<()>>> {
6572 use language::ToOffset as _;
6573
6574 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6575 else {
6576 return None;
6577 };
6578
6579 let candidate_id = {
6580 let entries = completions_menu.entries.borrow();
6581 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6582 if self.show_edit_predictions_in_menu() {
6583 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6584 }
6585 mat.candidate_id
6586 };
6587
6588 let completion = completions_menu
6589 .completions
6590 .borrow()
6591 .get(candidate_id)?
6592 .clone();
6593 cx.stop_propagation();
6594
6595 let buffer_handle = completions_menu.buffer.clone();
6596
6597 let CompletionEdit {
6598 new_text,
6599 snippet,
6600 replace_range,
6601 } = process_completion_for_edit(
6602 &completion,
6603 intent,
6604 &buffer_handle,
6605 &completions_menu.initial_position.text_anchor,
6606 cx,
6607 );
6608
6609 let buffer = buffer_handle.read(cx);
6610 let snapshot = self.buffer.read(cx).snapshot(cx);
6611 let newest_anchor = self.selections.newest_anchor();
6612 let replace_range_multibuffer = {
6613 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6614 excerpt.map_range_from_buffer(replace_range.clone())
6615 };
6616 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6617 return None;
6618 }
6619
6620 let old_text = buffer
6621 .text_for_range(replace_range.clone())
6622 .collect::<String>();
6623 let lookbehind = newest_anchor
6624 .start
6625 .text_anchor
6626 .to_offset(buffer)
6627 .saturating_sub(replace_range.start.0);
6628 let lookahead = replace_range
6629 .end
6630 .0
6631 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6632 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6633 let suffix = &old_text[lookbehind.min(old_text.len())..];
6634
6635 let selections = self
6636 .selections
6637 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6638 let mut ranges = Vec::new();
6639 let mut all_commit_ranges = Vec::new();
6640 let mut linked_edits = LinkedEdits::new();
6641
6642 let text: Arc<str> = new_text.clone().into();
6643 for selection in &selections {
6644 let range = if selection.id == newest_anchor.id {
6645 replace_range_multibuffer.clone()
6646 } else {
6647 let mut range = selection.range();
6648
6649 // if prefix is present, don't duplicate it
6650 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6651 range.start = range.start.saturating_sub_usize(lookbehind);
6652
6653 // if suffix is also present, mimic the newest cursor and replace it
6654 if selection.id != newest_anchor.id
6655 && snapshot.contains_str_at(range.end, suffix)
6656 {
6657 range.end += lookahead;
6658 }
6659 }
6660 range
6661 };
6662
6663 ranges.push(range.clone());
6664
6665 let start_anchor = snapshot.anchor_before(range.start);
6666 let end_anchor = snapshot.anchor_after(range.end);
6667 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6668 all_commit_ranges.push(anchor_range.clone());
6669
6670 if !self.linked_edit_ranges.is_empty() {
6671 linked_edits.push(&self, anchor_range, text.clone(), cx);
6672 }
6673 }
6674
6675 let common_prefix_len = old_text
6676 .chars()
6677 .zip(new_text.chars())
6678 .take_while(|(a, b)| a == b)
6679 .map(|(a, _)| a.len_utf8())
6680 .sum::<usize>();
6681
6682 cx.emit(EditorEvent::InputHandled {
6683 utf16_range_to_replace: None,
6684 text: new_text[common_prefix_len..].into(),
6685 });
6686
6687 let tx_id = self.transact(window, cx, |editor, window, cx| {
6688 if let Some(mut snippet) = snippet {
6689 snippet.text = new_text.to_string();
6690 editor
6691 .insert_snippet(&ranges, snippet, window, cx)
6692 .log_err();
6693 } else {
6694 editor.buffer.update(cx, |multi_buffer, cx| {
6695 let auto_indent = match completion.insert_text_mode {
6696 Some(InsertTextMode::AS_IS) => None,
6697 _ => editor.autoindent_mode.clone(),
6698 };
6699 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6700 multi_buffer.edit(edits, auto_indent, cx);
6701 });
6702 }
6703 linked_edits.apply(cx);
6704 editor.refresh_edit_prediction(true, false, window, cx);
6705 });
6706 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6707
6708 let show_new_completions_on_confirm = completion
6709 .confirm
6710 .as_ref()
6711 .is_some_and(|confirm| confirm(intent, window, cx));
6712 if show_new_completions_on_confirm {
6713 self.open_or_update_completions_menu(None, None, false, window, cx);
6714 }
6715
6716 let provider = self.completion_provider.as_ref()?;
6717
6718 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6719 let command = lsp_store.as_ref().and_then(|lsp_store| {
6720 let CompletionSource::Lsp {
6721 lsp_completion,
6722 server_id,
6723 ..
6724 } = &completion.source
6725 else {
6726 return None;
6727 };
6728 let lsp_command = lsp_completion.command.as_ref()?;
6729 let available_commands = lsp_store
6730 .read(cx)
6731 .lsp_server_capabilities
6732 .get(server_id)
6733 .and_then(|server_capabilities| {
6734 server_capabilities
6735 .execute_command_provider
6736 .as_ref()
6737 .map(|options| options.commands.as_slice())
6738 })?;
6739 if available_commands.contains(&lsp_command.command) {
6740 Some(CodeAction {
6741 server_id: *server_id,
6742 range: language::Anchor::MIN..language::Anchor::MIN,
6743 lsp_action: LspAction::Command(lsp_command.clone()),
6744 resolved: false,
6745 })
6746 } else {
6747 None
6748 }
6749 });
6750
6751 drop(completion);
6752 let apply_edits = provider.apply_additional_edits_for_completion(
6753 buffer_handle.clone(),
6754 completions_menu.completions.clone(),
6755 candidate_id,
6756 true,
6757 all_commit_ranges,
6758 cx,
6759 );
6760
6761 let editor_settings = EditorSettings::get_global(cx);
6762 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6763 // After the code completion is finished, users often want to know what signatures are needed.
6764 // so we should automatically call signature_help
6765 self.show_signature_help(&ShowSignatureHelp, window, cx);
6766 }
6767
6768 Some(cx.spawn_in(window, async move |editor, cx| {
6769 let additional_edits_tx = apply_edits.await?;
6770
6771 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6772 let title = command.lsp_action.title().to_owned();
6773 let project_transaction = lsp_store
6774 .update(cx, |lsp_store, cx| {
6775 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6776 })
6777 .await
6778 .context("applying post-completion command")?;
6779 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6780 Self::open_project_transaction(
6781 &editor,
6782 workspace.downgrade(),
6783 project_transaction,
6784 title,
6785 cx,
6786 )
6787 .await?;
6788 }
6789 }
6790
6791 if let Some(tx_id) = tx_id
6792 && let Some(additional_edits_tx) = additional_edits_tx
6793 {
6794 editor
6795 .update(cx, |editor, cx| {
6796 editor.buffer.update(cx, |buffer, cx| {
6797 buffer.merge_transactions(additional_edits_tx.id, tx_id, cx)
6798 });
6799 })
6800 .context("merge transactions")?;
6801 }
6802
6803 Ok(())
6804 }))
6805 }
6806
6807 pub fn toggle_code_actions(
6808 &mut self,
6809 action: &ToggleCodeActions,
6810 window: &mut Window,
6811 cx: &mut Context<Self>,
6812 ) {
6813 let quick_launch = action.quick_launch;
6814 let mut context_menu = self.context_menu.borrow_mut();
6815 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6816 if code_actions.deployed_from == action.deployed_from {
6817 // Toggle if we're selecting the same one
6818 *context_menu = None;
6819 cx.notify();
6820 return;
6821 } else {
6822 // Otherwise, clear it and start a new one
6823 *context_menu = None;
6824 cx.notify();
6825 }
6826 }
6827 drop(context_menu);
6828 let snapshot = self.snapshot(window, cx);
6829 let deployed_from = action.deployed_from.clone();
6830 let action = action.clone();
6831 self.completion_tasks.clear();
6832 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6833
6834 let multibuffer_point = match &action.deployed_from {
6835 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6836 DisplayPoint::new(*row, 0).to_point(&snapshot)
6837 }
6838 _ => self
6839 .selections
6840 .newest::<Point>(&snapshot.display_snapshot)
6841 .head(),
6842 };
6843 let Some((buffer, buffer_row)) = snapshot
6844 .buffer_snapshot()
6845 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6846 .and_then(|(buffer_snapshot, range)| {
6847 self.buffer()
6848 .read(cx)
6849 .buffer(buffer_snapshot.remote_id())
6850 .map(|buffer| (buffer, range.start.row))
6851 })
6852 else {
6853 return;
6854 };
6855 let buffer_id = buffer.read(cx).remote_id();
6856 let tasks = self
6857 .runnables
6858 .runnables((buffer_id, buffer_row))
6859 .map(|t| Arc::new(t.to_owned()));
6860
6861 if !self.focus_handle.is_focused(window) {
6862 return;
6863 }
6864 let project = self.project.clone();
6865
6866 let code_actions_task = match deployed_from {
6867 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6868 _ => self.code_actions(buffer_row, window, cx),
6869 };
6870
6871 let runnable_task = match deployed_from {
6872 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6873 _ => {
6874 let mut task_context_task = Task::ready(None);
6875 if let Some(tasks) = &tasks
6876 && let Some(project) = project
6877 {
6878 task_context_task =
6879 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6880 }
6881
6882 cx.spawn_in(window, {
6883 let buffer = buffer.clone();
6884 async move |editor, cx| {
6885 let task_context = task_context_task.await;
6886
6887 let resolved_tasks =
6888 tasks
6889 .zip(task_context.clone())
6890 .map(|(tasks, task_context)| ResolvedTasks {
6891 templates: tasks.resolve(&task_context).collect(),
6892 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6893 multibuffer_point.row,
6894 tasks.column,
6895 )),
6896 });
6897 let debug_scenarios = editor
6898 .update(cx, |editor, cx| {
6899 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6900 })?
6901 .await;
6902 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6903 }
6904 })
6905 }
6906 };
6907
6908 cx.spawn_in(window, async move |editor, cx| {
6909 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6910 let code_actions = code_actions_task.await;
6911 let spawn_straight_away = quick_launch
6912 && resolved_tasks
6913 .as_ref()
6914 .is_some_and(|tasks| tasks.templates.len() == 1)
6915 && code_actions
6916 .as_ref()
6917 .is_none_or(|actions| actions.is_empty())
6918 && debug_scenarios.is_empty();
6919
6920 editor.update_in(cx, |editor, window, cx| {
6921 crate::hover_popover::hide_hover(editor, cx);
6922 let actions = CodeActionContents::new(
6923 resolved_tasks,
6924 code_actions,
6925 debug_scenarios,
6926 task_context.unwrap_or_default(),
6927 );
6928
6929 // Don't show the menu if there are no actions available
6930 if actions.is_empty() {
6931 cx.notify();
6932 return Task::ready(Ok(()));
6933 }
6934
6935 *editor.context_menu.borrow_mut() =
6936 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6937 buffer,
6938 actions,
6939 selected_item: Default::default(),
6940 scroll_handle: UniformListScrollHandle::default(),
6941 deployed_from,
6942 }));
6943 cx.notify();
6944 if spawn_straight_away
6945 && let Some(task) = editor.confirm_code_action(
6946 &ConfirmCodeAction { item_ix: Some(0) },
6947 window,
6948 cx,
6949 )
6950 {
6951 return task;
6952 }
6953
6954 Task::ready(Ok(()))
6955 })
6956 })
6957 .detach_and_log_err(cx);
6958 }
6959
6960 fn debug_scenarios(
6961 &mut self,
6962 resolved_tasks: &Option<ResolvedTasks>,
6963 buffer: &Entity<Buffer>,
6964 cx: &mut App,
6965 ) -> Task<Vec<task::DebugScenario>> {
6966 maybe!({
6967 let project = self.project()?;
6968 let dap_store = project.read(cx).dap_store();
6969 let mut scenarios = vec![];
6970 let resolved_tasks = resolved_tasks.as_ref()?;
6971 let buffer = buffer.read(cx);
6972 let language = buffer.language()?;
6973 let debug_adapter = LanguageSettings::for_buffer(&buffer, cx)
6974 .debuggers
6975 .first()
6976 .map(SharedString::from)
6977 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6978
6979 dap_store.update(cx, |dap_store, cx| {
6980 for (_, task) in &resolved_tasks.templates {
6981 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6982 task.original_task().clone(),
6983 debug_adapter.clone().into(),
6984 task.display_label().to_owned().into(),
6985 cx,
6986 );
6987 scenarios.push(maybe_scenario);
6988 }
6989 });
6990 Some(cx.background_spawn(async move {
6991 futures::future::join_all(scenarios)
6992 .await
6993 .into_iter()
6994 .flatten()
6995 .collect::<Vec<_>>()
6996 }))
6997 })
6998 .unwrap_or_else(|| Task::ready(vec![]))
6999 }
7000
7001 fn code_actions(
7002 &mut self,
7003 buffer_row: u32,
7004 window: &mut Window,
7005 cx: &mut Context<Self>,
7006 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
7007 let mut task = self.code_actions_task.take();
7008 cx.spawn_in(window, async move |editor, cx| {
7009 while let Some(prev_task) = task {
7010 prev_task.await.log_err();
7011 task = editor
7012 .update(cx, |this, _| this.code_actions_task.take())
7013 .ok()?;
7014 }
7015
7016 editor
7017 .update(cx, |editor, cx| {
7018 editor
7019 .available_code_actions
7020 .clone()
7021 .and_then(|(location, code_actions)| {
7022 let snapshot = location.buffer.read(cx).snapshot();
7023 let point_range = location.range.to_point(&snapshot);
7024 let point_range = point_range.start.row..=point_range.end.row;
7025 if point_range.contains(&buffer_row) {
7026 Some(code_actions)
7027 } else {
7028 None
7029 }
7030 })
7031 })
7032 .ok()
7033 .flatten()
7034 })
7035 }
7036
7037 pub fn confirm_code_action(
7038 &mut self,
7039 action: &ConfirmCodeAction,
7040 window: &mut Window,
7041 cx: &mut Context<Self>,
7042 ) -> Option<Task<Result<()>>> {
7043 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
7044
7045 let actions_menu =
7046 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
7047 menu
7048 } else {
7049 return None;
7050 };
7051
7052 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
7053 let action = actions_menu.actions.get(action_ix)?;
7054 let title = action.label();
7055 let buffer = actions_menu.buffer;
7056 let workspace = self.workspace()?;
7057
7058 match action {
7059 CodeActionsItem::Task(task_source_kind, resolved_task) => {
7060 workspace.update(cx, |workspace, cx| {
7061 workspace.schedule_resolved_task(
7062 task_source_kind,
7063 resolved_task,
7064 false,
7065 window,
7066 cx,
7067 );
7068
7069 Some(Task::ready(Ok(())))
7070 })
7071 }
7072 CodeActionsItem::CodeAction {
7073 excerpt_id,
7074 action,
7075 provider,
7076 } => {
7077 let apply_code_action =
7078 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
7079 let workspace = workspace.downgrade();
7080 Some(cx.spawn_in(window, async move |editor, cx| {
7081 let project_transaction = apply_code_action.await?;
7082 Self::open_project_transaction(
7083 &editor,
7084 workspace,
7085 project_transaction,
7086 title,
7087 cx,
7088 )
7089 .await
7090 }))
7091 }
7092 CodeActionsItem::DebugScenario(scenario) => {
7093 let context = actions_menu.actions.context.into();
7094
7095 workspace.update(cx, |workspace, cx| {
7096 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
7097 workspace.start_debug_session(
7098 scenario,
7099 context,
7100 Some(buffer),
7101 None,
7102 window,
7103 cx,
7104 );
7105 });
7106 Some(Task::ready(Ok(())))
7107 }
7108 }
7109 }
7110
7111 fn open_transaction_for_hidden_buffers(
7112 workspace: Entity<Workspace>,
7113 transaction: ProjectTransaction,
7114 title: String,
7115 window: &mut Window,
7116 cx: &mut Context<Self>,
7117 ) {
7118 if transaction.0.is_empty() {
7119 return;
7120 }
7121
7122 let edited_buffers_already_open = {
7123 let other_editors: Vec<Entity<Editor>> = workspace
7124 .read(cx)
7125 .panes()
7126 .iter()
7127 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
7128 .filter(|editor| editor.entity_id() != cx.entity_id())
7129 .collect();
7130
7131 transaction.0.keys().all(|buffer| {
7132 other_editors.iter().any(|editor| {
7133 let multi_buffer = editor.read(cx).buffer();
7134 multi_buffer.read(cx).is_singleton()
7135 && multi_buffer
7136 .read(cx)
7137 .as_singleton()
7138 .map_or(false, |singleton| {
7139 singleton.entity_id() == buffer.entity_id()
7140 })
7141 })
7142 })
7143 };
7144 if !edited_buffers_already_open {
7145 let workspace = workspace.downgrade();
7146 cx.defer_in(window, move |_, window, cx| {
7147 cx.spawn_in(window, async move |editor, cx| {
7148 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
7149 .await
7150 .ok()
7151 })
7152 .detach();
7153 });
7154 }
7155 }
7156
7157 pub async fn open_project_transaction(
7158 editor: &WeakEntity<Editor>,
7159 workspace: WeakEntity<Workspace>,
7160 transaction: ProjectTransaction,
7161 title: String,
7162 cx: &mut AsyncWindowContext,
7163 ) -> Result<()> {
7164 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7165 cx.update(|_, cx| {
7166 entries.sort_unstable_by_key(|(buffer, _)| {
7167 buffer.read(cx).file().map(|f| f.path().clone())
7168 });
7169 })?;
7170 if entries.is_empty() {
7171 return Ok(());
7172 }
7173
7174 // If the project transaction's edits are all contained within this editor, then
7175 // avoid opening a new editor to display them.
7176
7177 if let [(buffer, transaction)] = &*entries {
7178 let excerpt = editor.update(cx, |editor, cx| {
7179 editor
7180 .buffer()
7181 .read(cx)
7182 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7183 })?;
7184 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7185 && excerpted_buffer == *buffer
7186 {
7187 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7188 let excerpt_range = excerpt_range.to_offset(buffer);
7189 buffer
7190 .edited_ranges_for_transaction::<usize>(transaction)
7191 .all(|range| {
7192 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7193 })
7194 });
7195
7196 if all_edits_within_excerpt {
7197 return Ok(());
7198 }
7199 }
7200 }
7201
7202 let mut ranges_to_highlight = Vec::new();
7203 let excerpt_buffer = cx.new(|cx| {
7204 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7205 for (buffer_handle, transaction) in &entries {
7206 let edited_ranges = buffer_handle
7207 .read(cx)
7208 .edited_ranges_for_transaction::<Point>(transaction)
7209 .collect::<Vec<_>>();
7210 let (ranges, _) = multibuffer.set_excerpts_for_path(
7211 PathKey::for_buffer(buffer_handle, cx),
7212 buffer_handle.clone(),
7213 edited_ranges,
7214 multibuffer_context_lines(cx),
7215 cx,
7216 );
7217
7218 ranges_to_highlight.extend(ranges);
7219 }
7220 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7221 multibuffer
7222 });
7223
7224 workspace.update_in(cx, |workspace, window, cx| {
7225 let project = workspace.project().clone();
7226 let editor =
7227 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7228 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7229 editor.update(cx, |editor, cx| {
7230 editor.highlight_background(
7231 HighlightKey::Editor,
7232 &ranges_to_highlight,
7233 |_, theme| theme.colors().editor_highlighted_line_background,
7234 cx,
7235 );
7236 });
7237 })?;
7238
7239 Ok(())
7240 }
7241
7242 pub fn clear_code_action_providers(&mut self) {
7243 self.code_action_providers.clear();
7244 self.available_code_actions.take();
7245 }
7246
7247 pub fn add_code_action_provider(
7248 &mut self,
7249 provider: Rc<dyn CodeActionProvider>,
7250 window: &mut Window,
7251 cx: &mut Context<Self>,
7252 ) {
7253 if self
7254 .code_action_providers
7255 .iter()
7256 .any(|existing_provider| existing_provider.id() == provider.id())
7257 {
7258 return;
7259 }
7260
7261 self.code_action_providers.push(provider);
7262 self.refresh_code_actions(window, cx);
7263 }
7264
7265 pub fn remove_code_action_provider(
7266 &mut self,
7267 id: Arc<str>,
7268 window: &mut Window,
7269 cx: &mut Context<Self>,
7270 ) {
7271 self.code_action_providers
7272 .retain(|provider| provider.id() != id);
7273 self.refresh_code_actions(window, cx);
7274 }
7275
7276 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7277 !self.code_action_providers.is_empty()
7278 && EditorSettings::get_global(cx).toolbar.code_actions
7279 }
7280
7281 pub fn has_available_code_actions(&self) -> bool {
7282 self.available_code_actions
7283 .as_ref()
7284 .is_some_and(|(_, actions)| !actions.is_empty())
7285 }
7286
7287 fn render_inline_code_actions(
7288 &self,
7289 icon_size: ui::IconSize,
7290 display_row: DisplayRow,
7291 is_active: bool,
7292 cx: &mut Context<Self>,
7293 ) -> AnyElement {
7294 let show_tooltip = !self.context_menu_visible();
7295 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7296 .icon_size(icon_size)
7297 .shape(ui::IconButtonShape::Square)
7298 .icon_color(ui::Color::Hidden)
7299 .toggle_state(is_active)
7300 .when(show_tooltip, |this| {
7301 this.tooltip({
7302 let focus_handle = self.focus_handle.clone();
7303 move |_window, cx| {
7304 Tooltip::for_action_in(
7305 "Toggle Code Actions",
7306 &ToggleCodeActions {
7307 deployed_from: None,
7308 quick_launch: false,
7309 },
7310 &focus_handle,
7311 cx,
7312 )
7313 }
7314 })
7315 })
7316 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7317 window.focus(&editor.focus_handle(cx), cx);
7318 editor.toggle_code_actions(
7319 &crate::actions::ToggleCodeActions {
7320 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7321 display_row,
7322 )),
7323 quick_launch: false,
7324 },
7325 window,
7326 cx,
7327 );
7328 }))
7329 .into_any_element()
7330 }
7331
7332 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7333 &self.context_menu
7334 }
7335
7336 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7337 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7338 cx.background_executor()
7339 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7340 .await;
7341
7342 let (start_buffer, start, _, end, newest_selection) = this
7343 .update(cx, |this, cx| {
7344 let newest_selection = this.selections.newest_anchor().clone();
7345 if newest_selection.head().diff_base_anchor.is_some() {
7346 return None;
7347 }
7348 let display_snapshot = this.display_snapshot(cx);
7349 let newest_selection_adjusted =
7350 this.selections.newest_adjusted(&display_snapshot);
7351 let buffer = this.buffer.read(cx);
7352
7353 let (start_buffer, start) =
7354 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7355 let (end_buffer, end) =
7356 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7357
7358 Some((start_buffer, start, end_buffer, end, newest_selection))
7359 })?
7360 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7361 .context(
7362 "Expected selection to lie in a single buffer when refreshing code actions",
7363 )?;
7364 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7365 let providers = this.code_action_providers.clone();
7366 let tasks = this
7367 .code_action_providers
7368 .iter()
7369 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7370 .collect::<Vec<_>>();
7371 (providers, tasks)
7372 })?;
7373
7374 let mut actions = Vec::new();
7375 for (provider, provider_actions) in
7376 providers.into_iter().zip(future::join_all(tasks).await)
7377 {
7378 if let Some(provider_actions) = provider_actions.log_err() {
7379 actions.extend(provider_actions.into_iter().map(|action| {
7380 AvailableCodeAction {
7381 excerpt_id: newest_selection.start.excerpt_id,
7382 action,
7383 provider: provider.clone(),
7384 }
7385 }));
7386 }
7387 }
7388
7389 this.update(cx, |this, cx| {
7390 this.available_code_actions = if actions.is_empty() {
7391 None
7392 } else {
7393 Some((
7394 Location {
7395 buffer: start_buffer,
7396 range: start..end,
7397 },
7398 actions.into(),
7399 ))
7400 };
7401 cx.notify();
7402 })
7403 }));
7404 }
7405
7406 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7407 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7408 self.show_git_blame_inline = false;
7409
7410 self.show_git_blame_inline_delay_task =
7411 Some(cx.spawn_in(window, async move |this, cx| {
7412 cx.background_executor().timer(delay).await;
7413
7414 this.update(cx, |this, cx| {
7415 this.show_git_blame_inline = true;
7416 cx.notify();
7417 })
7418 .log_err();
7419 }));
7420 }
7421 }
7422
7423 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7424 let snapshot = self.snapshot(window, cx);
7425 let cursor = self
7426 .selections
7427 .newest::<Point>(&snapshot.display_snapshot)
7428 .head();
7429 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7430 else {
7431 return;
7432 };
7433
7434 if self.blame.is_none() {
7435 self.start_git_blame(true, window, cx);
7436 }
7437 let Some(blame) = self.blame.as_ref() else {
7438 return;
7439 };
7440
7441 let row_info = RowInfo {
7442 buffer_id: Some(buffer.remote_id()),
7443 buffer_row: Some(point.row),
7444 ..Default::default()
7445 };
7446 let Some((buffer, blame_entry)) = blame
7447 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7448 .flatten()
7449 else {
7450 return;
7451 };
7452
7453 let anchor = self.selections.newest_anchor().head();
7454 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7455 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7456 self.show_blame_popover(
7457 buffer,
7458 &blame_entry,
7459 position + last_bounds.origin,
7460 true,
7461 cx,
7462 );
7463 };
7464 }
7465
7466 fn show_blame_popover(
7467 &mut self,
7468 buffer: BufferId,
7469 blame_entry: &BlameEntry,
7470 position: gpui::Point<Pixels>,
7471 ignore_timeout: bool,
7472 cx: &mut Context<Self>,
7473 ) {
7474 if let Some(state) = &mut self.inline_blame_popover {
7475 state.hide_task.take();
7476 } else {
7477 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7478 let blame_entry = blame_entry.clone();
7479 let show_task = cx.spawn(async move |editor, cx| {
7480 if !ignore_timeout {
7481 cx.background_executor()
7482 .timer(std::time::Duration::from_millis(blame_popover_delay))
7483 .await;
7484 }
7485 editor
7486 .update(cx, |editor, cx| {
7487 editor.inline_blame_popover_show_task.take();
7488 let Some(blame) = editor.blame.as_ref() else {
7489 return;
7490 };
7491 let blame = blame.read(cx);
7492 let details = blame.details_for_entry(buffer, &blame_entry);
7493 let markdown = cx.new(|cx| {
7494 Markdown::new(
7495 details
7496 .as_ref()
7497 .map(|message| message.message.clone())
7498 .unwrap_or_default(),
7499 None,
7500 None,
7501 cx,
7502 )
7503 });
7504 editor.inline_blame_popover = Some(InlineBlamePopover {
7505 position,
7506 hide_task: None,
7507 popover_bounds: None,
7508 popover_state: InlineBlamePopoverState {
7509 scroll_handle: ScrollHandle::new(),
7510 commit_message: details,
7511 markdown,
7512 },
7513 keyboard_grace: ignore_timeout,
7514 });
7515 cx.notify();
7516 })
7517 .ok();
7518 });
7519 self.inline_blame_popover_show_task = Some(show_task);
7520 }
7521 }
7522
7523 pub fn has_mouse_context_menu(&self) -> bool {
7524 self.mouse_context_menu.is_some()
7525 }
7526
7527 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7528 self.inline_blame_popover_show_task.take();
7529 if let Some(state) = &mut self.inline_blame_popover {
7530 let hide_task = cx.spawn(async move |editor, cx| {
7531 if !ignore_timeout {
7532 cx.background_executor()
7533 .timer(std::time::Duration::from_millis(100))
7534 .await;
7535 }
7536 editor
7537 .update(cx, |editor, cx| {
7538 editor.inline_blame_popover.take();
7539 cx.notify();
7540 })
7541 .ok();
7542 });
7543 state.hide_task = Some(hide_task);
7544 true
7545 } else {
7546 false
7547 }
7548 }
7549
7550 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7551 if self.pending_rename.is_some() {
7552 return None;
7553 }
7554
7555 let provider = self.semantics_provider.clone()?;
7556 let buffer = self.buffer.read(cx);
7557 let newest_selection = self.selections.newest_anchor().clone();
7558 let cursor_position = newest_selection.head();
7559 let (cursor_buffer, cursor_buffer_position) =
7560 buffer.text_anchor_for_position(cursor_position, cx)?;
7561 let (tail_buffer, tail_buffer_position) =
7562 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7563 if cursor_buffer != tail_buffer {
7564 return None;
7565 }
7566
7567 let snapshot = cursor_buffer.read(cx).snapshot();
7568 let word_ranges = cx.background_spawn(async move {
7569 // this might look odd to put on the background thread, but
7570 // `surrounding_word` can be quite expensive as it calls into
7571 // tree-sitter language scopes
7572 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7573 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7574 (start_word_range, end_word_range)
7575 });
7576
7577 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7578 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7579 let (start_word_range, end_word_range) = word_ranges.await;
7580 if start_word_range != end_word_range {
7581 this.update(cx, |this, cx| {
7582 this.document_highlights_task.take();
7583 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7584 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7585 })
7586 .ok();
7587 return;
7588 }
7589 cx.background_executor()
7590 .timer(Duration::from_millis(debounce))
7591 .await;
7592
7593 let highlights = if let Some(highlights) = cx.update(|cx| {
7594 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7595 }) {
7596 highlights.await.log_err()
7597 } else {
7598 None
7599 };
7600
7601 if let Some(highlights) = highlights {
7602 this.update(cx, |this, cx| {
7603 if this.pending_rename.is_some() {
7604 return;
7605 }
7606
7607 let buffer = this.buffer.read(cx);
7608 if buffer
7609 .text_anchor_for_position(cursor_position, cx)
7610 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7611 {
7612 return;
7613 }
7614
7615 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7616 let mut write_ranges = Vec::new();
7617 let mut read_ranges = Vec::new();
7618 for highlight in highlights {
7619 let buffer_id = cursor_buffer.read(cx).remote_id();
7620 for (excerpt_id, _, excerpt_range) in
7621 buffer.excerpts_for_buffer(buffer_id, cx)
7622 {
7623 let start = highlight
7624 .range
7625 .start
7626 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7627 let end = highlight
7628 .range
7629 .end
7630 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7631 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7632 continue;
7633 }
7634
7635 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7636 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7637 write_ranges.push(range);
7638 } else {
7639 read_ranges.push(range);
7640 }
7641 }
7642 }
7643
7644 this.highlight_background(
7645 HighlightKey::DocumentHighlightRead,
7646 &read_ranges,
7647 |_, theme| theme.colors().editor_document_highlight_read_background,
7648 cx,
7649 );
7650 this.highlight_background(
7651 HighlightKey::DocumentHighlightWrite,
7652 &write_ranges,
7653 |_, theme| theme.colors().editor_document_highlight_write_background,
7654 cx,
7655 );
7656 cx.notify();
7657 })
7658 .log_err();
7659 }
7660 }));
7661 None
7662 }
7663
7664 fn prepare_highlight_query_from_selection(
7665 &mut self,
7666 snapshot: &DisplaySnapshot,
7667 cx: &mut Context<Editor>,
7668 ) -> Option<(String, Range<Anchor>)> {
7669 if matches!(self.mode, EditorMode::SingleLine) {
7670 return None;
7671 }
7672 if !EditorSettings::get_global(cx).selection_highlight {
7673 return None;
7674 }
7675 if self.selections.count() != 1 || self.selections.line_mode() {
7676 return None;
7677 }
7678 let selection = self.selections.newest::<Point>(&snapshot);
7679 // If the selection spans multiple rows OR it is empty
7680 if selection.start.row != selection.end.row
7681 || selection.start.column == selection.end.column
7682 {
7683 return None;
7684 }
7685 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7686 let query = snapshot
7687 .buffer_snapshot()
7688 .text_for_range(selection_anchor_range.clone())
7689 .collect::<String>();
7690 if query.trim().is_empty() {
7691 return None;
7692 }
7693 Some((query, selection_anchor_range))
7694 }
7695
7696 #[ztracing::instrument(skip_all)]
7697 fn update_selection_occurrence_highlights(
7698 &mut self,
7699 multi_buffer_snapshot: MultiBufferSnapshot,
7700 query_text: String,
7701 query_range: Range<Anchor>,
7702 multi_buffer_range_to_query: Range<Point>,
7703 use_debounce: bool,
7704 window: &mut Window,
7705 cx: &mut Context<Editor>,
7706 ) -> Task<()> {
7707 cx.spawn_in(window, async move |editor, cx| {
7708 if use_debounce {
7709 cx.background_executor()
7710 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7711 .await;
7712 }
7713 let match_task = cx.background_spawn(async move {
7714 let buffer_ranges = multi_buffer_snapshot
7715 .range_to_buffer_ranges(
7716 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7717 )
7718 .into_iter()
7719 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7720 let mut match_ranges = Vec::new();
7721 let Ok(regex) = project::search::SearchQuery::text(
7722 query_text,
7723 false,
7724 false,
7725 false,
7726 Default::default(),
7727 Default::default(),
7728 false,
7729 None,
7730 ) else {
7731 return Vec::default();
7732 };
7733 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7734 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7735 match_ranges.extend(
7736 regex
7737 .search(
7738 buffer_snapshot,
7739 Some(search_range.start.0..search_range.end.0),
7740 )
7741 .await
7742 .into_iter()
7743 .filter_map(|match_range| {
7744 let match_start = buffer_snapshot
7745 .anchor_after(search_range.start + match_range.start);
7746 let match_end = buffer_snapshot
7747 .anchor_before(search_range.start + match_range.end);
7748 let match_anchor_range =
7749 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7750 (match_anchor_range != query_range).then_some(match_anchor_range)
7751 }),
7752 );
7753 }
7754 match_ranges
7755 });
7756 let match_ranges = match_task.await;
7757 editor
7758 .update_in(cx, |editor, _, cx| {
7759 if use_debounce {
7760 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7761 editor.debounced_selection_highlight_complete = true;
7762 } else if editor.debounced_selection_highlight_complete {
7763 return;
7764 }
7765 if !match_ranges.is_empty() {
7766 editor.highlight_background(
7767 HighlightKey::SelectedTextHighlight,
7768 &match_ranges,
7769 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7770 cx,
7771 )
7772 }
7773 })
7774 .log_err();
7775 })
7776 }
7777
7778 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7779 struct NewlineFold;
7780 let type_id = std::any::TypeId::of::<NewlineFold>();
7781 if !self.mode.is_single_line() {
7782 return;
7783 }
7784 let snapshot = self.snapshot(window, cx);
7785 if snapshot.buffer_snapshot().max_point().row == 0 {
7786 return;
7787 }
7788 let task = cx.background_spawn(async move {
7789 let new_newlines = snapshot
7790 .buffer_chars_at(MultiBufferOffset(0))
7791 .filter_map(|(c, i)| {
7792 if c == '\n' {
7793 Some(
7794 snapshot.buffer_snapshot().anchor_after(i)
7795 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7796 )
7797 } else {
7798 None
7799 }
7800 })
7801 .collect::<Vec<_>>();
7802 let existing_newlines = snapshot
7803 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7804 .filter_map(|fold| {
7805 if fold.placeholder.type_tag == Some(type_id) {
7806 Some(fold.range.start..fold.range.end)
7807 } else {
7808 None
7809 }
7810 })
7811 .collect::<Vec<_>>();
7812
7813 (new_newlines, existing_newlines)
7814 });
7815 self.folding_newlines = cx.spawn(async move |this, cx| {
7816 let (new_newlines, existing_newlines) = task.await;
7817 if new_newlines == existing_newlines {
7818 return;
7819 }
7820 let placeholder = FoldPlaceholder {
7821 render: Arc::new(move |_, _, cx| {
7822 div()
7823 .bg(cx.theme().status().hint_background)
7824 .border_b_1()
7825 .size_full()
7826 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7827 .border_color(cx.theme().status().hint)
7828 .child("\\n")
7829 .into_any()
7830 }),
7831 constrain_width: false,
7832 merge_adjacent: false,
7833 type_tag: Some(type_id),
7834 collapsed_text: None,
7835 };
7836 let creases = new_newlines
7837 .into_iter()
7838 .map(|range| Crease::simple(range, placeholder.clone()))
7839 .collect();
7840 this.update(cx, |this, cx| {
7841 this.display_map.update(cx, |display_map, cx| {
7842 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7843 display_map.fold(creases, cx);
7844 });
7845 })
7846 .ok();
7847 });
7848 }
7849
7850 #[ztracing::instrument(skip_all)]
7851 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7852 if !self.lsp_data_enabled() {
7853 return;
7854 }
7855 let cursor = self.selections.newest_anchor().head();
7856 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7857
7858 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7859 self.outline_symbols_at_cursor =
7860 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7861 cx.emit(EditorEvent::OutlineSymbolsChanged);
7862 cx.notify();
7863 } else {
7864 let syntax = cx.theme().syntax().clone();
7865 let background_task = cx.background_spawn(async move {
7866 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7867 });
7868 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7869 cx.spawn(async move |this, cx| {
7870 let symbols = background_task.await;
7871 this.update(cx, |this, cx| {
7872 this.outline_symbols_at_cursor = symbols;
7873 cx.emit(EditorEvent::OutlineSymbolsChanged);
7874 cx.notify();
7875 })
7876 .ok();
7877 });
7878 }
7879 }
7880
7881 #[ztracing::instrument(skip_all)]
7882 fn refresh_selected_text_highlights(
7883 &mut self,
7884 snapshot: &DisplaySnapshot,
7885 on_buffer_edit: bool,
7886 window: &mut Window,
7887 cx: &mut Context<Editor>,
7888 ) {
7889 let Some((query_text, query_range)) =
7890 self.prepare_highlight_query_from_selection(snapshot, cx)
7891 else {
7892 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7893 self.quick_selection_highlight_task.take();
7894 self.debounced_selection_highlight_task.take();
7895 self.debounced_selection_highlight_complete = false;
7896 return;
7897 };
7898 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7899 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7900 let query_changed = self
7901 .quick_selection_highlight_task
7902 .as_ref()
7903 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7904 if query_changed {
7905 self.debounced_selection_highlight_complete = false;
7906 }
7907 if on_buffer_edit || query_changed {
7908 self.quick_selection_highlight_task = Some((
7909 query_range.clone(),
7910 self.update_selection_occurrence_highlights(
7911 snapshot.buffer.clone(),
7912 query_text.clone(),
7913 query_range.clone(),
7914 self.multi_buffer_visible_range(&display_snapshot, cx),
7915 false,
7916 window,
7917 cx,
7918 ),
7919 ));
7920 }
7921 if on_buffer_edit
7922 || self
7923 .debounced_selection_highlight_task
7924 .as_ref()
7925 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7926 {
7927 let multi_buffer_start = multi_buffer_snapshot
7928 .anchor_before(MultiBufferOffset(0))
7929 .to_point(&multi_buffer_snapshot);
7930 let multi_buffer_end = multi_buffer_snapshot
7931 .anchor_after(multi_buffer_snapshot.len())
7932 .to_point(&multi_buffer_snapshot);
7933 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7934 self.debounced_selection_highlight_task = Some((
7935 query_range.clone(),
7936 self.update_selection_occurrence_highlights(
7937 snapshot.buffer.clone(),
7938 query_text,
7939 query_range,
7940 multi_buffer_full_range,
7941 true,
7942 window,
7943 cx,
7944 ),
7945 ));
7946 }
7947 }
7948
7949 pub fn multi_buffer_visible_range(
7950 &self,
7951 display_snapshot: &DisplaySnapshot,
7952 cx: &App,
7953 ) -> Range<Point> {
7954 let visible_start = self
7955 .scroll_manager
7956 .native_anchor(display_snapshot, cx)
7957 .anchor
7958 .to_point(display_snapshot.buffer_snapshot())
7959 .to_display_point(display_snapshot);
7960
7961 let mut target_end = visible_start;
7962 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
7963
7964 visible_start.to_point(display_snapshot)
7965 ..display_snapshot
7966 .clip_point(target_end, Bias::Right)
7967 .to_point(display_snapshot)
7968 }
7969
7970 pub fn refresh_edit_prediction(
7971 &mut self,
7972 debounce: bool,
7973 user_requested: bool,
7974 window: &mut Window,
7975 cx: &mut Context<Self>,
7976 ) -> Option<()> {
7977 if self.leader_id.is_some() {
7978 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7979 return None;
7980 }
7981
7982 let cursor = self.selections.newest_anchor().head();
7983 let (buffer, cursor_buffer_position) =
7984 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7985
7986 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7987 return None;
7988 }
7989
7990 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7991 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7992 return None;
7993 }
7994
7995 self.update_visible_edit_prediction(window, cx);
7996
7997 if !user_requested
7998 && (!self.should_show_edit_predictions()
7999 || !self.is_focused(window)
8000 || buffer.read(cx).is_empty())
8001 {
8002 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8003 return None;
8004 }
8005
8006 self.edit_prediction_provider()?
8007 .refresh(buffer, cursor_buffer_position, debounce, cx);
8008 Some(())
8009 }
8010
8011 fn show_edit_predictions_in_menu(&self) -> bool {
8012 match self.edit_prediction_settings {
8013 EditPredictionSettings::Disabled => false,
8014 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
8015 }
8016 }
8017
8018 pub fn edit_predictions_enabled(&self) -> bool {
8019 match self.edit_prediction_settings {
8020 EditPredictionSettings::Disabled => false,
8021 EditPredictionSettings::Enabled { .. } => true,
8022 }
8023 }
8024
8025 fn edit_prediction_requires_modifier(&self) -> bool {
8026 match self.edit_prediction_settings {
8027 EditPredictionSettings::Disabled => false,
8028 EditPredictionSettings::Enabled {
8029 preview_requires_modifier,
8030 ..
8031 } => preview_requires_modifier,
8032 }
8033 }
8034
8035 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
8036 if self.edit_prediction_provider.is_none() {
8037 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8038 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8039 return;
8040 }
8041
8042 let selection = self.selections.newest_anchor();
8043 let cursor = selection.head();
8044
8045 if let Some((buffer, cursor_buffer_position)) =
8046 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8047 {
8048 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8049 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8050 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8051 return;
8052 }
8053 self.edit_prediction_settings =
8054 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8055 }
8056 }
8057
8058 fn edit_prediction_settings_at_position(
8059 &self,
8060 buffer: &Entity<Buffer>,
8061 buffer_position: language::Anchor,
8062 cx: &App,
8063 ) -> EditPredictionSettings {
8064 if !self.mode.is_full()
8065 || !self.show_edit_predictions_override.unwrap_or(true)
8066 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
8067 {
8068 return EditPredictionSettings::Disabled;
8069 }
8070
8071 if !LanguageSettings::for_buffer(&buffer.read(cx), cx).show_edit_predictions {
8072 return EditPredictionSettings::Disabled;
8073 };
8074
8075 let by_provider = matches!(
8076 self.menu_edit_predictions_policy,
8077 MenuEditPredictionsPolicy::ByProvider
8078 );
8079
8080 let show_in_menu = by_provider
8081 && self
8082 .edit_prediction_provider
8083 .as_ref()
8084 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
8085
8086 let file = buffer.read(cx).file();
8087 let preview_requires_modifier =
8088 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
8089
8090 EditPredictionSettings::Enabled {
8091 show_in_menu,
8092 preview_requires_modifier,
8093 }
8094 }
8095
8096 fn should_show_edit_predictions(&self) -> bool {
8097 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
8098 }
8099
8100 pub fn edit_prediction_preview_is_active(&self) -> bool {
8101 matches!(
8102 self.edit_prediction_preview,
8103 EditPredictionPreview::Active { .. }
8104 )
8105 }
8106
8107 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
8108 let cursor = self.selections.newest_anchor().head();
8109 if let Some((buffer, cursor_position)) =
8110 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8111 {
8112 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
8113 } else {
8114 false
8115 }
8116 }
8117
8118 pub fn supports_minimap(&self, cx: &App) -> bool {
8119 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
8120 }
8121
8122 fn edit_predictions_enabled_in_buffer(
8123 &self,
8124 buffer: &Entity<Buffer>,
8125 buffer_position: language::Anchor,
8126 cx: &App,
8127 ) -> bool {
8128 maybe!({
8129 if self.read_only(cx) || self.leader_id.is_some() {
8130 return Some(false);
8131 }
8132 let provider = self.edit_prediction_provider()?;
8133 if !provider.is_enabled(buffer, buffer_position, cx) {
8134 return Some(false);
8135 }
8136 let buffer = buffer.read(cx);
8137 let Some(file) = buffer.file() else {
8138 return Some(true);
8139 };
8140 let settings = all_language_settings(Some(file), cx);
8141 Some(settings.edit_predictions_enabled_for_file(file, cx))
8142 })
8143 .unwrap_or(false)
8144 }
8145
8146 pub fn show_edit_prediction(
8147 &mut self,
8148 _: &ShowEditPrediction,
8149 window: &mut Window,
8150 cx: &mut Context<Self>,
8151 ) {
8152 if !self.has_active_edit_prediction() {
8153 self.refresh_edit_prediction(false, true, window, cx);
8154 return;
8155 }
8156
8157 self.update_visible_edit_prediction(window, cx);
8158 }
8159
8160 pub fn display_cursor_names(
8161 &mut self,
8162 _: &DisplayCursorNames,
8163 window: &mut Window,
8164 cx: &mut Context<Self>,
8165 ) {
8166 self.show_cursor_names(window, cx);
8167 }
8168
8169 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8170 self.show_cursor_names = true;
8171 cx.notify();
8172 cx.spawn_in(window, async move |this, cx| {
8173 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8174 this.update(cx, |this, cx| {
8175 this.show_cursor_names = false;
8176 cx.notify()
8177 })
8178 .ok()
8179 })
8180 .detach();
8181 }
8182
8183 pub fn accept_partial_edit_prediction(
8184 &mut self,
8185 granularity: EditPredictionGranularity,
8186 window: &mut Window,
8187 cx: &mut Context<Self>,
8188 ) {
8189 if self.show_edit_predictions_in_menu() {
8190 self.hide_context_menu(window, cx);
8191 }
8192
8193 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8194 return;
8195 };
8196
8197 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8198 return;
8199 }
8200
8201 match &active_edit_prediction.completion {
8202 EditPrediction::MoveWithin { target, .. } => {
8203 let target = *target;
8204
8205 if matches!(granularity, EditPredictionGranularity::Full) {
8206 if let Some(position_map) = &self.last_position_map {
8207 let target_row = target.to_display_point(&position_map.snapshot).row();
8208 let is_visible = position_map.visible_row_range.contains(&target_row);
8209
8210 if is_visible || !self.edit_prediction_requires_modifier() {
8211 self.unfold_ranges(&[target..target], true, false, cx);
8212 self.change_selections(
8213 SelectionEffects::scroll(Autoscroll::newest()),
8214 window,
8215 cx,
8216 |selections| {
8217 selections.select_anchor_ranges([target..target]);
8218 },
8219 );
8220 self.clear_row_highlights::<EditPredictionPreview>();
8221 self.edit_prediction_preview
8222 .set_previous_scroll_position(None);
8223 } else {
8224 // Highlight and request scroll
8225 self.edit_prediction_preview
8226 .set_previous_scroll_position(Some(
8227 position_map.snapshot.scroll_anchor,
8228 ));
8229 self.highlight_rows::<EditPredictionPreview>(
8230 target..target,
8231 cx.theme().colors().editor_highlighted_line_background,
8232 RowHighlightOptions {
8233 autoscroll: true,
8234 ..Default::default()
8235 },
8236 cx,
8237 );
8238 self.request_autoscroll(Autoscroll::fit(), cx);
8239 }
8240 }
8241 } else {
8242 self.change_selections(
8243 SelectionEffects::scroll(Autoscroll::newest()),
8244 window,
8245 cx,
8246 |selections| {
8247 selections.select_anchor_ranges([target..target]);
8248 },
8249 );
8250 }
8251 }
8252 EditPrediction::MoveOutside { snapshot, target } => {
8253 if let Some(workspace) = self.workspace() {
8254 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8255 .detach_and_log_err(cx);
8256 }
8257 }
8258 EditPrediction::Edit {
8259 edits,
8260 cursor_position,
8261 ..
8262 } => {
8263 self.report_edit_prediction_event(
8264 active_edit_prediction.completion_id.clone(),
8265 true,
8266 cx,
8267 );
8268
8269 match granularity {
8270 EditPredictionGranularity::Full => {
8271 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8272
8273 // Compute fallback cursor position BEFORE applying the edit,
8274 // so the anchor tracks through the edit correctly
8275 let fallback_cursor_target = {
8276 let snapshot = self.buffer.read(cx).snapshot(cx);
8277 edits.last().unwrap().0.end.bias_right(&snapshot)
8278 };
8279
8280 self.buffer.update(cx, |buffer, cx| {
8281 buffer.edit(edits.iter().cloned(), None, cx)
8282 });
8283
8284 if let Some(provider) = self.edit_prediction_provider() {
8285 provider.accept(cx);
8286 }
8287
8288 // Resolve cursor position after the edit is applied
8289 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8290 // The anchor tracks through the edit, then we add the offset
8291 let snapshot = self.buffer.read(cx).snapshot(cx);
8292 let base_offset = anchor.to_offset(&snapshot).0;
8293 let target_offset =
8294 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8295 snapshot.anchor_after(target_offset)
8296 } else {
8297 fallback_cursor_target
8298 };
8299
8300 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8301 s.select_anchor_ranges([cursor_target..cursor_target]);
8302 });
8303
8304 let selections = self.selections.disjoint_anchors_arc();
8305 if let Some(transaction_id_now) =
8306 self.buffer.read(cx).last_transaction_id(cx)
8307 {
8308 if transaction_id_prev != Some(transaction_id_now) {
8309 self.selection_history
8310 .insert_transaction(transaction_id_now, selections);
8311 }
8312 }
8313
8314 self.update_visible_edit_prediction(window, cx);
8315 if self.active_edit_prediction.is_none() {
8316 self.refresh_edit_prediction(true, true, window, cx);
8317 }
8318 cx.notify();
8319 }
8320 _ => {
8321 let snapshot = self.buffer.read(cx).snapshot(cx);
8322 let cursor_offset = self
8323 .selections
8324 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8325 .head();
8326
8327 let insertion = edits.iter().find_map(|(range, text)| {
8328 let range = range.to_offset(&snapshot);
8329 if range.is_empty() && range.start == cursor_offset {
8330 Some(text)
8331 } else {
8332 None
8333 }
8334 });
8335
8336 if let Some(text) = insertion {
8337 let text_to_insert = match granularity {
8338 EditPredictionGranularity::Word => {
8339 let mut partial = text
8340 .chars()
8341 .by_ref()
8342 .take_while(|c| c.is_alphabetic())
8343 .collect::<String>();
8344 if partial.is_empty() {
8345 partial = text
8346 .chars()
8347 .by_ref()
8348 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8349 .collect::<String>();
8350 }
8351 partial
8352 }
8353 EditPredictionGranularity::Line => {
8354 if let Some(line) = text.split_inclusive('\n').next() {
8355 line.to_string()
8356 } else {
8357 text.to_string()
8358 }
8359 }
8360 EditPredictionGranularity::Full => unreachable!(),
8361 };
8362
8363 cx.emit(EditorEvent::InputHandled {
8364 utf16_range_to_replace: None,
8365 text: text_to_insert.clone().into(),
8366 });
8367
8368 self.replace_selections(&text_to_insert, None, window, cx, false);
8369 self.refresh_edit_prediction(true, true, window, cx);
8370 cx.notify();
8371 } else {
8372 self.accept_partial_edit_prediction(
8373 EditPredictionGranularity::Full,
8374 window,
8375 cx,
8376 );
8377 }
8378 }
8379 }
8380 }
8381 }
8382 }
8383
8384 pub fn accept_next_word_edit_prediction(
8385 &mut self,
8386 _: &AcceptNextWordEditPrediction,
8387 window: &mut Window,
8388 cx: &mut Context<Self>,
8389 ) {
8390 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8391 }
8392
8393 pub fn accept_next_line_edit_prediction(
8394 &mut self,
8395 _: &AcceptNextLineEditPrediction,
8396 window: &mut Window,
8397 cx: &mut Context<Self>,
8398 ) {
8399 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8400 }
8401
8402 pub fn accept_edit_prediction(
8403 &mut self,
8404 _: &AcceptEditPrediction,
8405 window: &mut Window,
8406 cx: &mut Context<Self>,
8407 ) {
8408 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8409 }
8410
8411 fn discard_edit_prediction(
8412 &mut self,
8413 reason: EditPredictionDiscardReason,
8414 cx: &mut Context<Self>,
8415 ) -> bool {
8416 if reason == EditPredictionDiscardReason::Rejected {
8417 let completion_id = self
8418 .active_edit_prediction
8419 .as_ref()
8420 .and_then(|active_completion| active_completion.completion_id.clone());
8421
8422 self.report_edit_prediction_event(completion_id, false, cx);
8423 }
8424
8425 if let Some(provider) = self.edit_prediction_provider() {
8426 provider.discard(reason, cx);
8427 }
8428
8429 self.take_active_edit_prediction(reason == EditPredictionDiscardReason::Ignored, cx)
8430 }
8431
8432 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8433 let Some(provider) = self.edit_prediction_provider() else {
8434 return;
8435 };
8436
8437 let Some((_, buffer, _)) = self
8438 .buffer
8439 .read(cx)
8440 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8441 else {
8442 return;
8443 };
8444
8445 let extension = buffer
8446 .read(cx)
8447 .file()
8448 .and_then(|file| Some(file.path().extension()?.to_string()));
8449
8450 let event_type = match accepted {
8451 true => "Edit Prediction Accepted",
8452 false => "Edit Prediction Discarded",
8453 };
8454 telemetry::event!(
8455 event_type,
8456 provider = provider.name(),
8457 prediction_id = id,
8458 suggestion_accepted = accepted,
8459 file_extension = extension,
8460 );
8461 }
8462
8463 fn open_editor_at_anchor(
8464 snapshot: &language::BufferSnapshot,
8465 target: language::Anchor,
8466 workspace: &Entity<Workspace>,
8467 window: &mut Window,
8468 cx: &mut App,
8469 ) -> Task<Result<()>> {
8470 workspace.update(cx, |workspace, cx| {
8471 let path = snapshot.file().map(|file| file.full_path(cx));
8472 let Some(path) =
8473 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8474 else {
8475 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8476 };
8477 let target = text::ToPoint::to_point(&target, snapshot);
8478 let item = workspace.open_path(path, None, true, window, cx);
8479 window.spawn(cx, async move |cx| {
8480 let Some(editor) = item.await?.downcast::<Editor>() else {
8481 return Ok(());
8482 };
8483 editor
8484 .update_in(cx, |editor, window, cx| {
8485 editor.go_to_singleton_buffer_point(target, window, cx);
8486 })
8487 .ok();
8488 anyhow::Ok(())
8489 })
8490 })
8491 }
8492
8493 pub fn has_active_edit_prediction(&self) -> bool {
8494 self.active_edit_prediction.is_some()
8495 }
8496
8497 fn take_active_edit_prediction(
8498 &mut self,
8499 preserve_stale_in_menu: bool,
8500 cx: &mut Context<Self>,
8501 ) -> bool {
8502 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8503 if !preserve_stale_in_menu {
8504 self.stale_edit_prediction_in_menu = None;
8505 }
8506 return false;
8507 };
8508
8509 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8510 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8511 self.stale_edit_prediction_in_menu =
8512 preserve_stale_in_menu.then_some(active_edit_prediction);
8513 true
8514 }
8515
8516 /// Returns true when we're displaying the edit prediction popover below the cursor
8517 /// like we are not previewing and the LSP autocomplete menu is visible
8518 /// or we are in `when_holding_modifier` mode.
8519 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8520 if self.edit_prediction_preview_is_active()
8521 || !self.show_edit_predictions_in_menu()
8522 || !self.edit_predictions_enabled()
8523 {
8524 return false;
8525 }
8526
8527 if self.has_visible_completions_menu() {
8528 return true;
8529 }
8530
8531 has_completion && self.edit_prediction_requires_modifier()
8532 }
8533
8534 fn handle_modifiers_changed(
8535 &mut self,
8536 modifiers: Modifiers,
8537 position_map: &PositionMap,
8538 window: &mut Window,
8539 cx: &mut Context<Self>,
8540 ) {
8541 self.update_edit_prediction_settings(cx);
8542
8543 // Ensure that the edit prediction preview is updated, even when not
8544 // enabled, if there's an active edit prediction preview.
8545 if self.show_edit_predictions_in_menu()
8546 || self.edit_prediction_requires_modifier()
8547 || matches!(
8548 self.edit_prediction_preview,
8549 EditPredictionPreview::Active { .. }
8550 )
8551 {
8552 self.update_edit_prediction_preview(&modifiers, window, cx);
8553 }
8554
8555 self.update_selection_mode(&modifiers, position_map, window, cx);
8556
8557 let mouse_position = window.mouse_position();
8558 if !position_map.text_hitbox.is_hovered(window) {
8559 return;
8560 }
8561
8562 self.update_hovered_link(
8563 position_map.point_for_position(mouse_position),
8564 Some(mouse_position),
8565 &position_map.snapshot,
8566 modifiers,
8567 window,
8568 cx,
8569 )
8570 }
8571
8572 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8573 match EditorSettings::get_global(cx).multi_cursor_modifier {
8574 MultiCursorModifier::Alt => modifiers.secondary(),
8575 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8576 }
8577 }
8578
8579 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8580 match EditorSettings::get_global(cx).multi_cursor_modifier {
8581 MultiCursorModifier::Alt => modifiers.alt,
8582 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8583 }
8584 }
8585
8586 fn columnar_selection_mode(
8587 modifiers: &Modifiers,
8588 cx: &mut Context<Self>,
8589 ) -> Option<ColumnarMode> {
8590 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8591 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8592 Some(ColumnarMode::FromMouse)
8593 } else if Self::is_alt_pressed(modifiers, cx) {
8594 Some(ColumnarMode::FromSelection)
8595 } else {
8596 None
8597 }
8598 } else {
8599 None
8600 }
8601 }
8602
8603 fn update_selection_mode(
8604 &mut self,
8605 modifiers: &Modifiers,
8606 position_map: &PositionMap,
8607 window: &mut Window,
8608 cx: &mut Context<Self>,
8609 ) {
8610 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8611 return;
8612 };
8613 if self.selections.pending_anchor().is_none() {
8614 return;
8615 }
8616
8617 let mouse_position = window.mouse_position();
8618 let point_for_position = position_map.point_for_position(mouse_position);
8619 let position = point_for_position.previous_valid;
8620
8621 self.select(
8622 SelectPhase::BeginColumnar {
8623 position,
8624 reset: false,
8625 mode,
8626 goal_column: point_for_position.exact_unclipped.column(),
8627 },
8628 window,
8629 cx,
8630 );
8631 }
8632
8633 fn update_edit_prediction_preview(
8634 &mut self,
8635 modifiers: &Modifiers,
8636 window: &mut Window,
8637 cx: &mut Context<Self>,
8638 ) {
8639 let modifiers_held = self.edit_prediction_preview_modifiers_held(modifiers, window, cx);
8640
8641 if modifiers_held {
8642 if matches!(
8643 self.edit_prediction_preview,
8644 EditPredictionPreview::Inactive { .. }
8645 ) {
8646 self.edit_prediction_preview = EditPredictionPreview::Active {
8647 previous_scroll_position: None,
8648 since: Instant::now(),
8649 };
8650
8651 self.update_visible_edit_prediction(window, cx);
8652 cx.notify();
8653 }
8654 } else if let EditPredictionPreview::Active {
8655 previous_scroll_position,
8656 since,
8657 } = self.edit_prediction_preview
8658 {
8659 if let (Some(previous_scroll_position), Some(position_map)) =
8660 (previous_scroll_position, self.last_position_map.as_ref())
8661 {
8662 self.set_scroll_position(
8663 previous_scroll_position
8664 .scroll_position(&position_map.snapshot.display_snapshot),
8665 window,
8666 cx,
8667 );
8668 }
8669
8670 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8671 released_too_fast: since.elapsed() < Duration::from_millis(200),
8672 };
8673 self.clear_row_highlights::<EditPredictionPreview>();
8674 self.update_visible_edit_prediction(window, cx);
8675 cx.notify();
8676 }
8677 }
8678
8679 fn update_visible_edit_prediction(
8680 &mut self,
8681 _window: &mut Window,
8682 cx: &mut Context<Self>,
8683 ) -> Option<()> {
8684 if self.ime_transaction.is_some() {
8685 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8686 return None;
8687 }
8688
8689 let selection = self.selections.newest_anchor();
8690 let cursor = selection.head();
8691 let multibuffer = self.buffer.read(cx).snapshot(cx);
8692
8693 // Check project-level disable_ai setting for the current buffer
8694 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8695 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8696 return None;
8697 }
8698 }
8699 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8700 let excerpt_id = cursor.excerpt_id;
8701
8702 let show_in_menu = self.show_edit_predictions_in_menu();
8703 let completions_menu_has_precedence = !show_in_menu
8704 && (self.context_menu.borrow().is_some()
8705 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8706
8707 if completions_menu_has_precedence
8708 || !offset_selection.is_empty()
8709 || self
8710 .active_edit_prediction
8711 .as_ref()
8712 .is_some_and(|completion| {
8713 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8714 return false;
8715 };
8716 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8717 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8718 !invalidation_range.contains(&offset_selection.head())
8719 })
8720 {
8721 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8722 return None;
8723 }
8724
8725 self.take_active_edit_prediction(true, cx);
8726 let Some(provider) = self.edit_prediction_provider() else {
8727 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8728 return None;
8729 };
8730
8731 let (buffer, cursor_buffer_position) =
8732 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8733
8734 self.edit_prediction_settings =
8735 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8736
8737 self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor);
8738
8739 if self.in_leading_whitespace {
8740 let cursor_point = cursor.to_point(&multibuffer);
8741 let mut suggested_indent = None;
8742 multibuffer.suggested_indents_callback(
8743 cursor_point.row..cursor_point.row + 1,
8744 &mut |_, indent| {
8745 suggested_indent = Some(indent);
8746 ControlFlow::Break(())
8747 },
8748 cx,
8749 );
8750
8751 if let Some(indent) = suggested_indent
8752 && indent.len == cursor_point.column
8753 {
8754 self.in_leading_whitespace = false;
8755 }
8756 }
8757
8758 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8759
8760 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8761 {
8762 edit_prediction_types::EditPrediction::Local {
8763 id,
8764 edits,
8765 cursor_position,
8766 edit_preview,
8767 } => (id, edits, cursor_position, edit_preview),
8768 edit_prediction_types::EditPrediction::Jump {
8769 id,
8770 snapshot,
8771 target,
8772 } => {
8773 if let Some(provider) = &self.edit_prediction_provider {
8774 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8775 }
8776 self.stale_edit_prediction_in_menu = None;
8777 self.active_edit_prediction = Some(EditPredictionState {
8778 inlay_ids: vec![],
8779 completion: EditPrediction::MoveOutside { snapshot, target },
8780 completion_id: id,
8781 invalidation_range: None,
8782 });
8783 cx.notify();
8784 return Some(());
8785 }
8786 };
8787
8788 let edits = edits
8789 .into_iter()
8790 .flat_map(|(range, new_text)| {
8791 Some((
8792 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8793 new_text,
8794 ))
8795 })
8796 .collect::<Vec<_>>();
8797 if edits.is_empty() {
8798 return None;
8799 }
8800
8801 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8802 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8803 Some((anchor, predicted.offset))
8804 });
8805
8806 let first_edit_start = edits.first().unwrap().0.start;
8807 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8808 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8809
8810 let last_edit_end = edits.last().unwrap().0.end;
8811 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8812 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8813
8814 let cursor_row = cursor.to_point(&multibuffer).row;
8815
8816 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8817
8818 let mut inlay_ids = Vec::new();
8819 let invalidation_row_range;
8820 let move_invalidation_row_range = if cursor_row < edit_start_row {
8821 Some(cursor_row..edit_end_row)
8822 } else if cursor_row > edit_end_row {
8823 Some(edit_start_row..cursor_row)
8824 } else {
8825 None
8826 };
8827 let supports_jump = self
8828 .edit_prediction_provider
8829 .as_ref()
8830 .map(|provider| provider.provider.supports_jump_to_edit())
8831 .unwrap_or(true);
8832
8833 let is_move = supports_jump
8834 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8835 let completion = if is_move {
8836 if let Some(provider) = &self.edit_prediction_provider {
8837 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8838 }
8839 invalidation_row_range =
8840 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8841 let target = first_edit_start;
8842 EditPrediction::MoveWithin { target, snapshot }
8843 } else {
8844 let show_completions_in_menu = self.has_visible_completions_menu();
8845 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8846 && !self.edit_predictions_hidden_for_vim_mode;
8847
8848 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8849 if provider.show_tab_accept_marker() {
8850 EditDisplayMode::TabAccept
8851 } else {
8852 EditDisplayMode::Inline
8853 }
8854 } else {
8855 EditDisplayMode::DiffPopover
8856 };
8857
8858 let report_shown = match display_mode {
8859 EditDisplayMode::DiffPopover | EditDisplayMode::Inline => {
8860 show_completions_in_buffer || show_completions_in_menu
8861 }
8862 EditDisplayMode::TabAccept => {
8863 show_completions_in_menu || self.edit_prediction_preview_is_active()
8864 }
8865 };
8866
8867 if report_shown && let Some(provider) = &self.edit_prediction_provider {
8868 let suggestion_display_type = match display_mode {
8869 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8870 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8871 SuggestionDisplayType::GhostText
8872 }
8873 };
8874 provider.provider.did_show(suggestion_display_type, cx);
8875 }
8876
8877 if show_completions_in_buffer {
8878 if edits
8879 .iter()
8880 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8881 {
8882 let mut inlays = Vec::new();
8883 for (range, new_text) in &edits {
8884 let inlay = Inlay::edit_prediction(
8885 post_inc(&mut self.next_inlay_id),
8886 range.start,
8887 new_text.as_ref(),
8888 );
8889 inlay_ids.push(inlay.id);
8890 inlays.push(inlay);
8891 }
8892
8893 self.splice_inlays(&[], inlays, cx);
8894 } else {
8895 let background_color = cx.theme().status().deleted_background;
8896 self.highlight_text(
8897 HighlightKey::EditPredictionHighlight,
8898 edits.iter().map(|(range, _)| range.clone()).collect(),
8899 HighlightStyle {
8900 background_color: Some(background_color),
8901 ..Default::default()
8902 },
8903 cx,
8904 );
8905 }
8906 }
8907
8908 invalidation_row_range = edit_start_row..edit_end_row;
8909
8910 EditPrediction::Edit {
8911 edits,
8912 cursor_position,
8913 edit_preview,
8914 display_mode,
8915 snapshot,
8916 }
8917 };
8918
8919 let invalidation_range = multibuffer
8920 .anchor_before(Point::new(invalidation_row_range.start, 0))
8921 ..multibuffer.anchor_after(Point::new(
8922 invalidation_row_range.end,
8923 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8924 ));
8925
8926 self.stale_edit_prediction_in_menu = None;
8927 self.active_edit_prediction = Some(EditPredictionState {
8928 inlay_ids,
8929 completion,
8930 completion_id,
8931 invalidation_range: Some(invalidation_range),
8932 });
8933
8934 cx.notify();
8935
8936 Some(())
8937 }
8938
8939 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8940 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8941 }
8942
8943 /// Get all display points of breakpoints that will be rendered within editor
8944 ///
8945 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8946 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8947 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8948 fn active_breakpoints(
8949 &self,
8950 range: Range<DisplayRow>,
8951 window: &mut Window,
8952 cx: &mut Context<Self>,
8953 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8954 let mut breakpoint_display_points = HashMap::default();
8955
8956 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8957 return breakpoint_display_points;
8958 };
8959
8960 let snapshot = self.snapshot(window, cx);
8961
8962 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8963 let Some(project) = self.project() else {
8964 return breakpoint_display_points;
8965 };
8966
8967 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8968 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8969
8970 for (buffer_snapshot, range, excerpt_id) in
8971 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8972 {
8973 let Some(buffer) = project
8974 .read(cx)
8975 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8976 else {
8977 continue;
8978 };
8979 let breakpoints = breakpoint_store.read(cx).breakpoints(
8980 &buffer,
8981 Some(
8982 buffer_snapshot.anchor_before(range.start)
8983 ..buffer_snapshot.anchor_after(range.end),
8984 ),
8985 buffer_snapshot,
8986 cx,
8987 );
8988 for (breakpoint, state) in breakpoints {
8989 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8990 let position = multi_buffer_anchor
8991 .to_point(&multi_buffer_snapshot)
8992 .to_display_point(&snapshot);
8993
8994 breakpoint_display_points.insert(
8995 position.row(),
8996 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8997 );
8998 }
8999 }
9000
9001 breakpoint_display_points
9002 }
9003
9004 fn breakpoint_context_menu(
9005 &self,
9006 anchor: Anchor,
9007 window: &mut Window,
9008 cx: &mut Context<Self>,
9009 ) -> Entity<ui::ContextMenu> {
9010 let weak_editor = cx.weak_entity();
9011 let focus_handle = self.focus_handle(cx);
9012
9013 let row = self
9014 .buffer
9015 .read(cx)
9016 .snapshot(cx)
9017 .summary_for_anchor::<Point>(&anchor)
9018 .row;
9019
9020 let breakpoint = self
9021 .breakpoint_at_row(row, window, cx)
9022 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
9023
9024 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
9025 "Edit Log Breakpoint"
9026 } else {
9027 "Set Log Breakpoint"
9028 };
9029
9030 let condition_breakpoint_msg = if breakpoint
9031 .as_ref()
9032 .is_some_and(|bp| bp.1.condition.is_some())
9033 {
9034 "Edit Condition Breakpoint"
9035 } else {
9036 "Set Condition Breakpoint"
9037 };
9038
9039 let hit_condition_breakpoint_msg = if breakpoint
9040 .as_ref()
9041 .is_some_and(|bp| bp.1.hit_condition.is_some())
9042 {
9043 "Edit Hit Condition Breakpoint"
9044 } else {
9045 "Set Hit Condition Breakpoint"
9046 };
9047
9048 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
9049 "Unset Breakpoint"
9050 } else {
9051 "Set Breakpoint"
9052 };
9053
9054 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
9055
9056 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
9057 BreakpointState::Enabled => Some("Disable"),
9058 BreakpointState::Disabled => Some("Enable"),
9059 });
9060
9061 let (anchor, breakpoint) =
9062 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
9063
9064 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
9065 menu.on_blur_subscription(Subscription::new(|| {}))
9066 .context(focus_handle)
9067 .when(run_to_cursor, |this| {
9068 let weak_editor = weak_editor.clone();
9069 this.entry("Run to Cursor", None, move |window, cx| {
9070 weak_editor
9071 .update(cx, |editor, cx| {
9072 editor.change_selections(
9073 SelectionEffects::no_scroll(),
9074 window,
9075 cx,
9076 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
9077 );
9078 })
9079 .ok();
9080
9081 window.dispatch_action(Box::new(RunToCursor), cx);
9082 })
9083 .separator()
9084 })
9085 .when_some(toggle_state_msg, |this, msg| {
9086 this.entry(msg, None, {
9087 let weak_editor = weak_editor.clone();
9088 let breakpoint = breakpoint.clone();
9089 move |_window, cx| {
9090 weak_editor
9091 .update(cx, |this, cx| {
9092 this.edit_breakpoint_at_anchor(
9093 anchor,
9094 breakpoint.as_ref().clone(),
9095 BreakpointEditAction::InvertState,
9096 cx,
9097 );
9098 })
9099 .log_err();
9100 }
9101 })
9102 })
9103 .entry(set_breakpoint_msg, None, {
9104 let weak_editor = weak_editor.clone();
9105 let breakpoint = breakpoint.clone();
9106 move |_window, cx| {
9107 weak_editor
9108 .update(cx, |this, cx| {
9109 this.edit_breakpoint_at_anchor(
9110 anchor,
9111 breakpoint.as_ref().clone(),
9112 BreakpointEditAction::Toggle,
9113 cx,
9114 );
9115 })
9116 .log_err();
9117 }
9118 })
9119 .entry(log_breakpoint_msg, None, {
9120 let breakpoint = breakpoint.clone();
9121 let weak_editor = weak_editor.clone();
9122 move |window, cx| {
9123 weak_editor
9124 .update(cx, |this, cx| {
9125 this.add_edit_breakpoint_block(
9126 anchor,
9127 breakpoint.as_ref(),
9128 BreakpointPromptEditAction::Log,
9129 window,
9130 cx,
9131 );
9132 })
9133 .log_err();
9134 }
9135 })
9136 .entry(condition_breakpoint_msg, None, {
9137 let breakpoint = breakpoint.clone();
9138 let weak_editor = weak_editor.clone();
9139 move |window, cx| {
9140 weak_editor
9141 .update(cx, |this, cx| {
9142 this.add_edit_breakpoint_block(
9143 anchor,
9144 breakpoint.as_ref(),
9145 BreakpointPromptEditAction::Condition,
9146 window,
9147 cx,
9148 );
9149 })
9150 .log_err();
9151 }
9152 })
9153 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
9154 weak_editor
9155 .update(cx, |this, cx| {
9156 this.add_edit_breakpoint_block(
9157 anchor,
9158 breakpoint.as_ref(),
9159 BreakpointPromptEditAction::HitCondition,
9160 window,
9161 cx,
9162 );
9163 })
9164 .log_err();
9165 })
9166 })
9167 }
9168
9169 fn render_breakpoint(
9170 &self,
9171 position: Anchor,
9172 row: DisplayRow,
9173 breakpoint: &Breakpoint,
9174 state: Option<BreakpointSessionState>,
9175 cx: &mut Context<Self>,
9176 ) -> IconButton {
9177 let is_rejected = state.is_some_and(|s| !s.verified);
9178 // Is it a breakpoint that shows up when hovering over gutter?
9179 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9180 (false, false),
9181 |PhantomBreakpointIndicator {
9182 is_active,
9183 display_row,
9184 collides_with_existing_breakpoint,
9185 }| {
9186 (
9187 is_active && display_row == row,
9188 collides_with_existing_breakpoint,
9189 )
9190 },
9191 );
9192
9193 let (color, icon) = {
9194 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9195 (false, false) => ui::IconName::DebugBreakpoint,
9196 (true, false) => ui::IconName::DebugLogBreakpoint,
9197 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9198 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9199 };
9200
9201 let theme_colors = cx.theme().colors();
9202
9203 let color = if is_phantom {
9204 if collides_with_existing {
9205 Color::Custom(
9206 theme_colors
9207 .debugger_accent
9208 .blend(theme_colors.text.opacity(0.6)),
9209 )
9210 } else {
9211 Color::Hint
9212 }
9213 } else if is_rejected {
9214 Color::Disabled
9215 } else {
9216 Color::Debugger
9217 };
9218
9219 (color, icon)
9220 };
9221
9222 let breakpoint = Arc::from(breakpoint.clone());
9223
9224 let alt_as_text = gpui::Keystroke {
9225 modifiers: Modifiers::secondary_key(),
9226 ..Default::default()
9227 };
9228 let primary_action_text = if breakpoint.is_disabled() {
9229 "Enable breakpoint"
9230 } else if is_phantom && !collides_with_existing {
9231 "Set breakpoint"
9232 } else {
9233 "Unset breakpoint"
9234 };
9235 let focus_handle = self.focus_handle.clone();
9236
9237 let meta = if is_rejected {
9238 SharedString::from("No executable code is associated with this line.")
9239 } else if collides_with_existing && !breakpoint.is_disabled() {
9240 SharedString::from(format!(
9241 "{alt_as_text}-click to disable,\nright-click for more options."
9242 ))
9243 } else {
9244 SharedString::from("Right-click for more options.")
9245 };
9246 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9247 .icon_size(IconSize::XSmall)
9248 .size(ui::ButtonSize::None)
9249 .when(is_rejected, |this| {
9250 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9251 })
9252 .icon_color(color)
9253 .style(ButtonStyle::Transparent)
9254 .on_click(cx.listener({
9255 move |editor, event: &ClickEvent, window, cx| {
9256 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9257 BreakpointEditAction::InvertState
9258 } else {
9259 BreakpointEditAction::Toggle
9260 };
9261
9262 window.focus(&editor.focus_handle(cx), cx);
9263 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9264 editor.edit_breakpoint_at_anchor(
9265 position,
9266 breakpoint.as_ref().clone(),
9267 edit_action,
9268 cx,
9269 );
9270 }
9271 }))
9272 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9273 editor.set_breakpoint_context_menu(
9274 row,
9275 Some(position),
9276 event.position(),
9277 window,
9278 cx,
9279 );
9280 }))
9281 .tooltip(move |_window, cx| {
9282 Tooltip::with_meta_in(
9283 primary_action_text,
9284 Some(&ToggleBreakpoint),
9285 meta.clone(),
9286 &focus_handle,
9287 cx,
9288 )
9289 })
9290 }
9291
9292 fn build_tasks_context(
9293 project: &Entity<Project>,
9294 buffer: &Entity<Buffer>,
9295 buffer_row: u32,
9296 tasks: &Arc<RunnableTasks>,
9297 cx: &mut Context<Self>,
9298 ) -> Task<Option<task::TaskContext>> {
9299 let position = Point::new(buffer_row, tasks.column);
9300 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9301 let location = Location {
9302 buffer: buffer.clone(),
9303 range: range_start..range_start,
9304 };
9305 // Fill in the environmental variables from the tree-sitter captures
9306 let mut captured_task_variables = TaskVariables::default();
9307 for (capture_name, value) in tasks.extra_variables.clone() {
9308 captured_task_variables.insert(
9309 task::VariableName::Custom(capture_name.into()),
9310 value.clone(),
9311 );
9312 }
9313 project.update(cx, |project, cx| {
9314 project.task_store().update(cx, |task_store, cx| {
9315 task_store.task_context_for_location(captured_task_variables, location, cx)
9316 })
9317 })
9318 }
9319
9320 pub fn context_menu_visible(&self) -> bool {
9321 !self.edit_prediction_preview_is_active()
9322 && self
9323 .context_menu
9324 .borrow()
9325 .as_ref()
9326 .is_some_and(|menu| menu.visible())
9327 }
9328
9329 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9330 self.context_menu
9331 .borrow()
9332 .as_ref()
9333 .map(|menu| menu.origin())
9334 }
9335
9336 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9337 self.context_menu_options = Some(options);
9338 }
9339
9340 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9341 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9342
9343 fn render_edit_prediction_popover(
9344 &mut self,
9345 text_bounds: &Bounds<Pixels>,
9346 content_origin: gpui::Point<Pixels>,
9347 right_margin: Pixels,
9348 editor_snapshot: &EditorSnapshot,
9349 visible_row_range: Range<DisplayRow>,
9350 scroll_top: ScrollOffset,
9351 scroll_bottom: ScrollOffset,
9352 line_layouts: &[LineWithInvisibles],
9353 line_height: Pixels,
9354 scroll_position: gpui::Point<ScrollOffset>,
9355 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9356 newest_selection_head: Option<DisplayPoint>,
9357 editor_width: Pixels,
9358 style: &EditorStyle,
9359 window: &mut Window,
9360 cx: &mut App,
9361 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9362 if self.mode().is_minimap() {
9363 return None;
9364 }
9365 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9366
9367 if self.edit_prediction_visible_in_cursor_popover(true) {
9368 return None;
9369 }
9370
9371 match &active_edit_prediction.completion {
9372 EditPrediction::MoveWithin { target, .. } => {
9373 let target_display_point = target.to_display_point(editor_snapshot);
9374
9375 if self.edit_prediction_requires_modifier() {
9376 if !self.edit_prediction_preview_is_active() {
9377 return None;
9378 }
9379
9380 self.render_edit_prediction_modifier_jump_popover(
9381 text_bounds,
9382 content_origin,
9383 visible_row_range,
9384 line_layouts,
9385 line_height,
9386 scroll_pixel_position,
9387 newest_selection_head,
9388 target_display_point,
9389 window,
9390 cx,
9391 )
9392 } else {
9393 self.render_edit_prediction_eager_jump_popover(
9394 text_bounds,
9395 content_origin,
9396 editor_snapshot,
9397 visible_row_range,
9398 scroll_top,
9399 scroll_bottom,
9400 line_height,
9401 scroll_pixel_position,
9402 target_display_point,
9403 editor_width,
9404 window,
9405 cx,
9406 )
9407 }
9408 }
9409 EditPrediction::Edit {
9410 display_mode: EditDisplayMode::Inline,
9411 ..
9412 } => None,
9413 EditPrediction::Edit {
9414 display_mode: EditDisplayMode::TabAccept,
9415 edits,
9416 ..
9417 } => {
9418 let range = &edits.first()?.0;
9419 let target_display_point = range.end.to_display_point(editor_snapshot);
9420
9421 self.render_edit_prediction_end_of_line_popover(
9422 "Accept",
9423 editor_snapshot,
9424 visible_row_range,
9425 target_display_point,
9426 line_height,
9427 scroll_pixel_position,
9428 content_origin,
9429 editor_width,
9430 window,
9431 cx,
9432 )
9433 }
9434 EditPrediction::Edit {
9435 edits,
9436 edit_preview,
9437 display_mode: EditDisplayMode::DiffPopover,
9438 snapshot,
9439 ..
9440 } => self.render_edit_prediction_diff_popover(
9441 text_bounds,
9442 content_origin,
9443 right_margin,
9444 editor_snapshot,
9445 visible_row_range,
9446 line_layouts,
9447 line_height,
9448 scroll_position,
9449 scroll_pixel_position,
9450 newest_selection_head,
9451 editor_width,
9452 style,
9453 edits,
9454 edit_preview,
9455 snapshot,
9456 window,
9457 cx,
9458 ),
9459 EditPrediction::MoveOutside { snapshot, .. } => {
9460 let mut element = self
9461 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9462 .into_any();
9463
9464 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9465 let origin_x = text_bounds.size.width - size.width - px(30.);
9466 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9467 element.prepaint_at(origin, window, cx);
9468
9469 Some((element, origin))
9470 }
9471 }
9472 }
9473
9474 fn render_edit_prediction_modifier_jump_popover(
9475 &mut self,
9476 text_bounds: &Bounds<Pixels>,
9477 content_origin: gpui::Point<Pixels>,
9478 visible_row_range: Range<DisplayRow>,
9479 line_layouts: &[LineWithInvisibles],
9480 line_height: Pixels,
9481 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9482 newest_selection_head: Option<DisplayPoint>,
9483 target_display_point: DisplayPoint,
9484 window: &mut Window,
9485 cx: &mut App,
9486 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9487 let scrolled_content_origin =
9488 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9489
9490 const SCROLL_PADDING_Y: Pixels = px(12.);
9491
9492 if target_display_point.row() < visible_row_range.start {
9493 return self.render_edit_prediction_scroll_popover(
9494 &|_| SCROLL_PADDING_Y,
9495 IconName::ArrowUp,
9496 visible_row_range,
9497 line_layouts,
9498 newest_selection_head,
9499 scrolled_content_origin,
9500 window,
9501 cx,
9502 );
9503 } else if target_display_point.row() >= visible_row_range.end {
9504 return self.render_edit_prediction_scroll_popover(
9505 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9506 IconName::ArrowDown,
9507 visible_row_range,
9508 line_layouts,
9509 newest_selection_head,
9510 scrolled_content_origin,
9511 window,
9512 cx,
9513 );
9514 }
9515
9516 const POLE_WIDTH: Pixels = px(2.);
9517
9518 let line_layout =
9519 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9520 let target_column = target_display_point.column() as usize;
9521
9522 let target_x = line_layout.x_for_index(target_column);
9523 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9524 - scroll_pixel_position.y;
9525
9526 let flag_on_right = target_x < text_bounds.size.width / 2.;
9527
9528 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9529 border_color.l += 0.001;
9530
9531 let mut element = v_flex()
9532 .items_end()
9533 .when(flag_on_right, |el| el.items_start())
9534 .child(if flag_on_right {
9535 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9536 .rounded_bl(px(0.))
9537 .rounded_tl(px(0.))
9538 .border_l_2()
9539 .border_color(border_color)
9540 } else {
9541 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9542 .rounded_br(px(0.))
9543 .rounded_tr(px(0.))
9544 .border_r_2()
9545 .border_color(border_color)
9546 })
9547 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9548 .into_any();
9549
9550 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9551
9552 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9553 - point(
9554 if flag_on_right {
9555 POLE_WIDTH
9556 } else {
9557 size.width - POLE_WIDTH
9558 },
9559 size.height - line_height,
9560 );
9561
9562 origin.x = origin.x.max(content_origin.x);
9563
9564 element.prepaint_at(origin, window, cx);
9565
9566 Some((element, origin))
9567 }
9568
9569 fn render_edit_prediction_scroll_popover(
9570 &mut self,
9571 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9572 scroll_icon: IconName,
9573 visible_row_range: Range<DisplayRow>,
9574 line_layouts: &[LineWithInvisibles],
9575 newest_selection_head: Option<DisplayPoint>,
9576 scrolled_content_origin: gpui::Point<Pixels>,
9577 window: &mut Window,
9578 cx: &mut App,
9579 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9580 let mut element = self
9581 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9582 .into_any();
9583
9584 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9585
9586 let cursor = newest_selection_head?;
9587 let cursor_row_layout =
9588 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9589 let cursor_column = cursor.column() as usize;
9590
9591 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9592
9593 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9594
9595 element.prepaint_at(origin, window, cx);
9596 Some((element, origin))
9597 }
9598
9599 fn render_edit_prediction_eager_jump_popover(
9600 &mut self,
9601 text_bounds: &Bounds<Pixels>,
9602 content_origin: gpui::Point<Pixels>,
9603 editor_snapshot: &EditorSnapshot,
9604 visible_row_range: Range<DisplayRow>,
9605 scroll_top: ScrollOffset,
9606 scroll_bottom: ScrollOffset,
9607 line_height: Pixels,
9608 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9609 target_display_point: DisplayPoint,
9610 editor_width: Pixels,
9611 window: &mut Window,
9612 cx: &mut App,
9613 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9614 if target_display_point.row().as_f64() < scroll_top {
9615 let mut element = self
9616 .render_edit_prediction_line_popover(
9617 "Jump to Edit",
9618 Some(IconName::ArrowUp),
9619 window,
9620 cx,
9621 )
9622 .into_any();
9623
9624 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9625 let offset = point(
9626 (text_bounds.size.width - size.width) / 2.,
9627 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9628 );
9629
9630 let origin = text_bounds.origin + offset;
9631 element.prepaint_at(origin, window, cx);
9632 Some((element, origin))
9633 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9634 let mut element = self
9635 .render_edit_prediction_line_popover(
9636 "Jump to Edit",
9637 Some(IconName::ArrowDown),
9638 window,
9639 cx,
9640 )
9641 .into_any();
9642
9643 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9644 let offset = point(
9645 (text_bounds.size.width - size.width) / 2.,
9646 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9647 );
9648
9649 let origin = text_bounds.origin + offset;
9650 element.prepaint_at(origin, window, cx);
9651 Some((element, origin))
9652 } else {
9653 self.render_edit_prediction_end_of_line_popover(
9654 "Jump to Edit",
9655 editor_snapshot,
9656 visible_row_range,
9657 target_display_point,
9658 line_height,
9659 scroll_pixel_position,
9660 content_origin,
9661 editor_width,
9662 window,
9663 cx,
9664 )
9665 }
9666 }
9667
9668 fn render_edit_prediction_end_of_line_popover(
9669 self: &mut Editor,
9670 label: &'static str,
9671 editor_snapshot: &EditorSnapshot,
9672 visible_row_range: Range<DisplayRow>,
9673 target_display_point: DisplayPoint,
9674 line_height: Pixels,
9675 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9676 content_origin: gpui::Point<Pixels>,
9677 editor_width: Pixels,
9678 window: &mut Window,
9679 cx: &mut App,
9680 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9681 let target_line_end = DisplayPoint::new(
9682 target_display_point.row(),
9683 editor_snapshot.line_len(target_display_point.row()),
9684 );
9685
9686 let mut element = self
9687 .render_edit_prediction_line_popover(label, None, window, cx)
9688 .into_any();
9689
9690 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9691
9692 let line_origin =
9693 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9694
9695 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9696 let mut origin = start_point
9697 + line_origin
9698 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9699 origin.x = origin.x.max(content_origin.x);
9700
9701 let max_x = content_origin.x + editor_width - size.width;
9702
9703 if origin.x > max_x {
9704 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9705
9706 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9707 origin.y += offset;
9708 IconName::ArrowUp
9709 } else {
9710 origin.y -= offset;
9711 IconName::ArrowDown
9712 };
9713
9714 element = self
9715 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9716 .into_any();
9717
9718 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9719
9720 origin.x = content_origin.x + editor_width - size.width - px(2.);
9721 }
9722
9723 element.prepaint_at(origin, window, cx);
9724 Some((element, origin))
9725 }
9726
9727 fn render_edit_prediction_diff_popover(
9728 self: &Editor,
9729 text_bounds: &Bounds<Pixels>,
9730 content_origin: gpui::Point<Pixels>,
9731 right_margin: Pixels,
9732 editor_snapshot: &EditorSnapshot,
9733 visible_row_range: Range<DisplayRow>,
9734 line_layouts: &[LineWithInvisibles],
9735 line_height: Pixels,
9736 scroll_position: gpui::Point<ScrollOffset>,
9737 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9738 newest_selection_head: Option<DisplayPoint>,
9739 editor_width: Pixels,
9740 style: &EditorStyle,
9741 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9742 edit_preview: &Option<language::EditPreview>,
9743 snapshot: &language::BufferSnapshot,
9744 window: &mut Window,
9745 cx: &mut App,
9746 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9747 let edit_start = edits
9748 .first()
9749 .unwrap()
9750 .0
9751 .start
9752 .to_display_point(editor_snapshot);
9753 let edit_end = edits
9754 .last()
9755 .unwrap()
9756 .0
9757 .end
9758 .to_display_point(editor_snapshot);
9759
9760 let is_visible = visible_row_range.contains(&edit_start.row())
9761 || visible_row_range.contains(&edit_end.row());
9762 if !is_visible {
9763 return None;
9764 }
9765
9766 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9767 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9768 } else {
9769 // Fallback for providers without edit_preview
9770 crate::edit_prediction_fallback_text(edits, cx)
9771 };
9772
9773 let styled_text = highlighted_edits.to_styled_text(&style.text);
9774 let line_count = highlighted_edits.text.lines().count();
9775
9776 const BORDER_WIDTH: Pixels = px(1.);
9777
9778 let keybind = self.render_edit_prediction_keybind(window, cx);
9779 let has_keybind = keybind.is_some();
9780
9781 let mut element = h_flex()
9782 .items_start()
9783 .child(
9784 h_flex()
9785 .bg(cx.theme().colors().editor_background)
9786 .border(BORDER_WIDTH)
9787 .shadow_xs()
9788 .border_color(cx.theme().colors().border)
9789 .rounded_l_lg()
9790 .when(line_count > 1, |el| el.rounded_br_lg())
9791 .pr_1()
9792 .child(styled_text),
9793 )
9794 .child(
9795 h_flex()
9796 .h(line_height + BORDER_WIDTH * 2.)
9797 .px_1p5()
9798 .gap_1()
9799 // Workaround: For some reason, there's a gap if we don't do this
9800 .ml(-BORDER_WIDTH)
9801 .shadow(vec![gpui::BoxShadow {
9802 color: gpui::black().opacity(0.05),
9803 offset: point(px(1.), px(1.)),
9804 blur_radius: px(2.),
9805 spread_radius: px(0.),
9806 }])
9807 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9808 .border(BORDER_WIDTH)
9809 .border_color(cx.theme().colors().border)
9810 .rounded_r_lg()
9811 .id("edit_prediction_diff_popover_keybind")
9812 .when(!has_keybind, |el| {
9813 let status_colors = cx.theme().status();
9814
9815 el.bg(status_colors.error_background)
9816 .border_color(status_colors.error.opacity(0.6))
9817 .child(Icon::new(IconName::Info).color(Color::Error))
9818 .cursor_default()
9819 .hoverable_tooltip(move |_window, cx| {
9820 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9821 })
9822 })
9823 .children(keybind),
9824 )
9825 .into_any();
9826
9827 let longest_row =
9828 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9829 let longest_line_width = if visible_row_range.contains(&longest_row) {
9830 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9831 } else {
9832 layout_line(
9833 longest_row,
9834 editor_snapshot,
9835 style,
9836 editor_width,
9837 |_| false,
9838 window,
9839 cx,
9840 )
9841 .width
9842 };
9843
9844 let viewport_bounds =
9845 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9846 right: -right_margin,
9847 ..Default::default()
9848 });
9849
9850 let x_after_longest = Pixels::from(
9851 ScrollPixelOffset::from(
9852 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9853 ) - scroll_pixel_position.x,
9854 );
9855
9856 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9857
9858 // Fully visible if it can be displayed within the window (allow overlapping other
9859 // panes). However, this is only allowed if the popover starts within text_bounds.
9860 let can_position_to_the_right = x_after_longest < text_bounds.right()
9861 && x_after_longest + element_bounds.width < viewport_bounds.right();
9862
9863 let mut origin = if can_position_to_the_right {
9864 point(
9865 x_after_longest,
9866 text_bounds.origin.y
9867 + Pixels::from(
9868 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9869 - scroll_pixel_position.y,
9870 ),
9871 )
9872 } else {
9873 let cursor_row = newest_selection_head.map(|head| head.row());
9874 let above_edit = edit_start
9875 .row()
9876 .0
9877 .checked_sub(line_count as u32)
9878 .map(DisplayRow);
9879 let below_edit = Some(edit_end.row() + 1);
9880 let above_cursor =
9881 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9882 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9883
9884 // Place the edit popover adjacent to the edit if there is a location
9885 // available that is onscreen and does not obscure the cursor. Otherwise,
9886 // place it adjacent to the cursor.
9887 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9888 .into_iter()
9889 .flatten()
9890 .find(|&start_row| {
9891 let end_row = start_row + line_count as u32;
9892 visible_row_range.contains(&start_row)
9893 && visible_row_range.contains(&end_row)
9894 && cursor_row
9895 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9896 })?;
9897
9898 content_origin
9899 + point(
9900 Pixels::from(-scroll_pixel_position.x),
9901 Pixels::from(
9902 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9903 ),
9904 )
9905 };
9906
9907 origin.x -= BORDER_WIDTH;
9908
9909 window.with_content_mask(
9910 Some(gpui::ContentMask {
9911 bounds: *text_bounds,
9912 }),
9913 |window| {
9914 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9915 },
9916 );
9917
9918 // Do not return an element, since it will already be drawn due to defer_draw.
9919 None
9920 }
9921
9922 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9923 px(30.)
9924 }
9925
9926 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9927 if self.read_only(cx) {
9928 cx.theme().players().read_only()
9929 } else {
9930 self.style.as_ref().unwrap().local_player
9931 }
9932 }
9933
9934 fn render_edit_prediction_inline_keystroke(
9935 &self,
9936 keystroke: &gpui::KeybindingKeystroke,
9937 modifiers_color: Color,
9938 cx: &App,
9939 ) -> AnyElement {
9940 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9941
9942 h_flex()
9943 .px_0p5()
9944 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9945 .font(
9946 theme_settings::ThemeSettings::get_global(cx)
9947 .buffer_font
9948 .clone(),
9949 )
9950 .text_size(TextSize::XSmall.rems(cx))
9951 .child(h_flex().children(ui::render_modifiers(
9952 keystroke.modifiers(),
9953 PlatformStyle::platform(),
9954 Some(modifiers_color),
9955 Some(IconSize::XSmall.rems().into()),
9956 true,
9957 )))
9958 .when(is_platform_style_mac, |parent| {
9959 parent.child(keystroke.key().to_string())
9960 })
9961 .when(!is_platform_style_mac, |parent| {
9962 parent.child(
9963 Key::new(ui::utils::capitalize(keystroke.key()), Some(Color::Default))
9964 .size(Some(IconSize::XSmall.rems().into())),
9965 )
9966 })
9967 .into_any()
9968 }
9969
9970 fn render_edit_prediction_popover_keystroke(
9971 &self,
9972 keystroke: &gpui::KeybindingKeystroke,
9973 color: Color,
9974 cx: &App,
9975 ) -> AnyElement {
9976 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9977
9978 if keystroke.modifiers().modified() {
9979 h_flex()
9980 .font(
9981 theme_settings::ThemeSettings::get_global(cx)
9982 .buffer_font
9983 .clone(),
9984 )
9985 .when(is_platform_style_mac, |parent| parent.gap_1())
9986 .child(h_flex().children(ui::render_modifiers(
9987 keystroke.modifiers(),
9988 PlatformStyle::platform(),
9989 Some(color),
9990 None,
9991 false,
9992 )))
9993 .into_any()
9994 } else {
9995 Key::new(ui::utils::capitalize(keystroke.key()), Some(color))
9996 .size(Some(IconSize::XSmall.rems().into()))
9997 .into_any_element()
9998 }
9999 }
10000
10001 fn render_edit_prediction_keybind(
10002 &self,
10003 window: &mut Window,
10004 cx: &mut App,
10005 ) -> Option<AnyElement> {
10006 let keybind_display =
10007 self.edit_prediction_keybind_display(EditPredictionKeybindSurface::Inline, window, cx);
10008 let keystroke = keybind_display.displayed_keystroke.as_ref()?;
10009
10010 let modifiers_color = if *keystroke.modifiers() == window.modifiers() {
10011 Color::Accent
10012 } else {
10013 Color::Muted
10014 };
10015
10016 Some(self.render_edit_prediction_inline_keystroke(keystroke, modifiers_color, cx))
10017 }
10018
10019 fn render_edit_prediction_line_popover(
10020 &self,
10021 label: impl Into<SharedString>,
10022 icon: Option<IconName>,
10023 window: &mut Window,
10024 cx: &mut App,
10025 ) -> Stateful<Div> {
10026 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
10027
10028 let keybind = self.render_edit_prediction_keybind(window, cx);
10029 let has_keybind = keybind.is_some();
10030 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10031
10032 h_flex()
10033 .id("ep-line-popover")
10034 .py_0p5()
10035 .pl_1()
10036 .pr(padding_right)
10037 .gap_1()
10038 .rounded_md()
10039 .border_1()
10040 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10041 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10042 .shadow_xs()
10043 .when(!has_keybind, |el| {
10044 let status_colors = cx.theme().status();
10045
10046 el.bg(status_colors.error_background)
10047 .border_color(status_colors.error.opacity(0.6))
10048 .pl_2()
10049 .child(Icon::new(icons.error).color(Color::Error))
10050 .cursor_default()
10051 .hoverable_tooltip(move |_window, cx| {
10052 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10053 })
10054 })
10055 .children(keybind)
10056 .child(
10057 Label::new(label)
10058 .size(LabelSize::Small)
10059 .when(!has_keybind, |el| {
10060 el.color(cx.theme().status().error.into()).strikethrough()
10061 }),
10062 )
10063 .when(!has_keybind, |el| {
10064 el.child(
10065 h_flex().ml_1().child(
10066 Icon::new(IconName::Info)
10067 .size(IconSize::Small)
10068 .color(cx.theme().status().error.into()),
10069 ),
10070 )
10071 })
10072 .when_some(icon, |element, icon| {
10073 element.child(
10074 div()
10075 .mt(px(1.5))
10076 .child(Icon::new(icon).size(IconSize::Small)),
10077 )
10078 })
10079 }
10080
10081 fn render_edit_prediction_jump_outside_popover(
10082 &self,
10083 snapshot: &BufferSnapshot,
10084 window: &mut Window,
10085 cx: &mut App,
10086 ) -> Stateful<Div> {
10087 let keybind = self.render_edit_prediction_keybind(window, cx);
10088 let has_keybind = keybind.is_some();
10089 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10090
10091 let file_name = snapshot
10092 .file()
10093 .map(|file| SharedString::new(file.file_name(cx)))
10094 .unwrap_or(SharedString::new_static("untitled"));
10095
10096 h_flex()
10097 .id("ep-jump-outside-popover")
10098 .py_1()
10099 .px_2()
10100 .gap_1()
10101 .rounded_md()
10102 .border_1()
10103 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10104 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10105 .shadow_xs()
10106 .when(!has_keybind, |el| {
10107 let status_colors = cx.theme().status();
10108
10109 el.bg(status_colors.error_background)
10110 .border_color(status_colors.error.opacity(0.6))
10111 .pl_2()
10112 .child(Icon::new(icons.error).color(Color::Error))
10113 .cursor_default()
10114 .hoverable_tooltip(move |_window, cx| {
10115 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10116 })
10117 })
10118 .children(keybind)
10119 .child(
10120 Label::new(file_name)
10121 .size(LabelSize::Small)
10122 .buffer_font(cx)
10123 .when(!has_keybind, |el| {
10124 el.color(cx.theme().status().error.into()).strikethrough()
10125 }),
10126 )
10127 .when(!has_keybind, |el| {
10128 el.child(
10129 h_flex().ml_1().child(
10130 Icon::new(IconName::Info)
10131 .size(IconSize::Small)
10132 .color(cx.theme().status().error.into()),
10133 ),
10134 )
10135 })
10136 .child(
10137 div()
10138 .mt(px(1.5))
10139 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10140 )
10141 }
10142
10143 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10144 let accent_color = cx.theme().colors().text_accent;
10145 let editor_bg_color = cx.theme().colors().editor_background;
10146 editor_bg_color.blend(accent_color.opacity(0.1))
10147 }
10148
10149 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10150 let accent_color = cx.theme().colors().text_accent;
10151 let editor_bg_color = cx.theme().colors().editor_background;
10152 editor_bg_color.blend(accent_color.opacity(0.6))
10153 }
10154 fn get_prediction_provider_icons(
10155 provider: &Option<RegisteredEditPredictionDelegate>,
10156 cx: &App,
10157 ) -> edit_prediction_types::EditPredictionIconSet {
10158 match provider {
10159 Some(provider) => provider.provider.icons(cx),
10160 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10161 }
10162 }
10163
10164 fn render_edit_prediction_cursor_popover(
10165 &self,
10166 min_width: Pixels,
10167 max_width: Pixels,
10168 cursor_point: Point,
10169 style: &EditorStyle,
10170 window: &mut Window,
10171 cx: &mut Context<Editor>,
10172 ) -> Option<AnyElement> {
10173 let provider = self.edit_prediction_provider.as_ref()?;
10174 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10175
10176 let is_refreshing = provider.provider.is_refreshing(cx);
10177
10178 fn pending_completion_container(icon: IconName) -> Div {
10179 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10180 }
10181
10182 let completion = match &self.active_edit_prediction {
10183 Some(prediction) => {
10184 if !self.has_visible_completions_menu() {
10185 const RADIUS: Pixels = px(6.);
10186 const BORDER_WIDTH: Pixels = px(1.);
10187 let keybind_display = self.edit_prediction_keybind_display(
10188 EditPredictionKeybindSurface::CursorPopoverCompact,
10189 window,
10190 cx,
10191 );
10192
10193 return Some(
10194 h_flex()
10195 .elevation_2(cx)
10196 .border(BORDER_WIDTH)
10197 .border_color(cx.theme().colors().border)
10198 .when(keybind_display.missing_accept_keystroke, |el| {
10199 el.border_color(cx.theme().status().error)
10200 })
10201 .rounded(RADIUS)
10202 .rounded_tl(px(0.))
10203 .overflow_hidden()
10204 .child(div().px_1p5().child(match &prediction.completion {
10205 EditPrediction::MoveWithin { target, snapshot } => {
10206 use text::ToPoint as _;
10207 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10208 {
10209 Icon::new(icons.down)
10210 } else {
10211 Icon::new(icons.up)
10212 }
10213 }
10214 EditPrediction::MoveOutside { .. } => {
10215 // TODO [zeta2] custom icon for external jump?
10216 Icon::new(icons.base)
10217 }
10218 EditPrediction::Edit { .. } => Icon::new(icons.base),
10219 }))
10220 .child(
10221 h_flex()
10222 .gap_1()
10223 .py_1()
10224 .px_2()
10225 .rounded_r(RADIUS - BORDER_WIDTH)
10226 .border_l_1()
10227 .border_color(cx.theme().colors().border)
10228 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10229 .when(keybind_display.show_hold_label, |el| {
10230 el.child(
10231 Label::new("Hold")
10232 .size(LabelSize::Small)
10233 .when(
10234 keybind_display.missing_accept_keystroke,
10235 |el| el.strikethrough(),
10236 )
10237 .line_height_style(LineHeightStyle::UiLabel),
10238 )
10239 })
10240 .id("edit_prediction_cursor_popover_keybind")
10241 .when(keybind_display.missing_accept_keystroke, |el| {
10242 let status_colors = cx.theme().status();
10243
10244 el.bg(status_colors.error_background)
10245 .border_color(status_colors.error.opacity(0.6))
10246 .child(Icon::new(IconName::Info).color(Color::Error))
10247 .cursor_default()
10248 .hoverable_tooltip(move |_window, cx| {
10249 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10250 .into()
10251 })
10252 })
10253 .when_some(
10254 keybind_display.displayed_keystroke.as_ref(),
10255 |el, compact_keystroke| {
10256 el.child(self.render_edit_prediction_popover_keystroke(
10257 compact_keystroke,
10258 Color::Default,
10259 cx,
10260 ))
10261 },
10262 ),
10263 )
10264 .into_any(),
10265 );
10266 }
10267
10268 self.render_edit_prediction_cursor_popover_preview(
10269 prediction,
10270 cursor_point,
10271 style,
10272 cx,
10273 )?
10274 }
10275
10276 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10277 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10278 stale_completion,
10279 cursor_point,
10280 style,
10281 cx,
10282 )?,
10283
10284 None => pending_completion_container(icons.base)
10285 .child(Label::new("...").size(LabelSize::Small)),
10286 },
10287
10288 None => pending_completion_container(icons.base)
10289 .child(Label::new("...").size(LabelSize::Small)),
10290 };
10291
10292 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10293 completion
10294 .with_animation(
10295 "loading-completion",
10296 Animation::new(Duration::from_secs(2))
10297 .repeat()
10298 .with_easing(pulsating_between(0.4, 0.8)),
10299 |label, delta| label.opacity(delta),
10300 )
10301 .into_any_element()
10302 } else {
10303 completion.into_any_element()
10304 };
10305
10306 let has_completion = self.active_edit_prediction.is_some();
10307 let keybind_display = self.edit_prediction_keybind_display(
10308 EditPredictionKeybindSurface::CursorPopoverExpanded,
10309 window,
10310 cx,
10311 );
10312
10313 Some(
10314 h_flex()
10315 .min_w(min_width)
10316 .max_w(max_width)
10317 .flex_1()
10318 .elevation_2(cx)
10319 .border_color(cx.theme().colors().border)
10320 .child(
10321 div()
10322 .flex_1()
10323 .py_1()
10324 .px_2()
10325 .overflow_hidden()
10326 .child(completion),
10327 )
10328 .when_some(
10329 keybind_display.displayed_keystroke.as_ref(),
10330 |el, keystroke| {
10331 let key_color = if !has_completion {
10332 Color::Muted
10333 } else {
10334 Color::Default
10335 };
10336
10337 if keybind_display.action == EditPredictionKeybindAction::Preview {
10338 el.child(
10339 h_flex()
10340 .h_full()
10341 .border_l_1()
10342 .rounded_r_lg()
10343 .border_color(cx.theme().colors().border)
10344 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10345 .gap_1()
10346 .py_1()
10347 .px_2()
10348 .child(self.render_edit_prediction_popover_keystroke(
10349 keystroke, key_color, cx,
10350 ))
10351 .child(Label::new("Preview").into_any_element())
10352 .opacity(if has_completion { 1.0 } else { 0.4 }),
10353 )
10354 } else {
10355 el.child(
10356 h_flex()
10357 .h_full()
10358 .border_l_1()
10359 .rounded_r_lg()
10360 .border_color(cx.theme().colors().border)
10361 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10362 .gap_1()
10363 .py_1()
10364 .px_2()
10365 .child(self.render_edit_prediction_popover_keystroke(
10366 keystroke, key_color, cx,
10367 ))
10368 .opacity(if has_completion { 1.0 } else { 0.4 }),
10369 )
10370 }
10371 },
10372 )
10373 .into_any(),
10374 )
10375 }
10376
10377 fn render_edit_prediction_cursor_popover_preview(
10378 &self,
10379 completion: &EditPredictionState,
10380 cursor_point: Point,
10381 style: &EditorStyle,
10382 cx: &mut Context<Editor>,
10383 ) -> Option<Div> {
10384 use text::ToPoint as _;
10385
10386 fn render_relative_row_jump(
10387 prefix: impl Into<String>,
10388 current_row: u32,
10389 target_row: u32,
10390 ) -> Div {
10391 let (row_diff, arrow) = if target_row < current_row {
10392 (current_row - target_row, IconName::ArrowUp)
10393 } else {
10394 (target_row - current_row, IconName::ArrowDown)
10395 };
10396
10397 h_flex()
10398 .child(
10399 Label::new(format!("{}{}", prefix.into(), row_diff))
10400 .color(Color::Muted)
10401 .size(LabelSize::Small),
10402 )
10403 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10404 }
10405
10406 let supports_jump = self
10407 .edit_prediction_provider
10408 .as_ref()
10409 .map(|provider| provider.provider.supports_jump_to_edit())
10410 .unwrap_or(true);
10411
10412 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10413
10414 match &completion.completion {
10415 EditPrediction::MoveWithin {
10416 target, snapshot, ..
10417 } => {
10418 if !supports_jump {
10419 return None;
10420 }
10421
10422 Some(
10423 h_flex()
10424 .px_2()
10425 .gap_2()
10426 .flex_1()
10427 .child(
10428 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10429 Icon::new(icons.down)
10430 } else {
10431 Icon::new(icons.up)
10432 },
10433 )
10434 .child(Label::new("Jump to Edit")),
10435 )
10436 }
10437 EditPrediction::MoveOutside { snapshot, .. } => {
10438 let file_name = snapshot
10439 .file()
10440 .map(|file| file.file_name(cx))
10441 .unwrap_or("untitled");
10442 Some(
10443 h_flex()
10444 .px_2()
10445 .gap_2()
10446 .flex_1()
10447 .child(Icon::new(icons.base))
10448 .child(Label::new(format!("Jump to {file_name}"))),
10449 )
10450 }
10451 EditPrediction::Edit {
10452 edits,
10453 edit_preview,
10454 snapshot,
10455 ..
10456 } => {
10457 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10458
10459 let (highlighted_edits, has_more_lines) =
10460 if let Some(edit_preview) = edit_preview.as_ref() {
10461 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10462 .first_line_preview()
10463 } else {
10464 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10465 };
10466
10467 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10468 .with_default_highlights(&style.text, highlighted_edits.highlights);
10469
10470 let preview = h_flex()
10471 .gap_1()
10472 .min_w_16()
10473 .child(styled_text)
10474 .when(has_more_lines, |parent| parent.child("…"));
10475
10476 let left = if supports_jump && first_edit_row != cursor_point.row {
10477 render_relative_row_jump("", cursor_point.row, first_edit_row)
10478 .into_any_element()
10479 } else {
10480 Icon::new(icons.base).into_any_element()
10481 };
10482
10483 Some(
10484 h_flex()
10485 .h_full()
10486 .flex_1()
10487 .gap_2()
10488 .pr_1()
10489 .overflow_x_hidden()
10490 .font(
10491 theme_settings::ThemeSettings::get_global(cx)
10492 .buffer_font
10493 .clone(),
10494 )
10495 .child(left)
10496 .child(preview),
10497 )
10498 }
10499 }
10500 }
10501
10502 pub fn render_context_menu(
10503 &mut self,
10504 max_height_in_lines: u32,
10505 window: &mut Window,
10506 cx: &mut Context<Editor>,
10507 ) -> Option<AnyElement> {
10508 let menu = self.context_menu.borrow();
10509 let menu = menu.as_ref()?;
10510 if !menu.visible() {
10511 return None;
10512 };
10513 self.style
10514 .as_ref()
10515 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10516 }
10517
10518 fn render_context_menu_aside(
10519 &mut self,
10520 max_size: Size<Pixels>,
10521 window: &mut Window,
10522 cx: &mut Context<Editor>,
10523 ) -> Option<AnyElement> {
10524 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10525 if menu.visible() {
10526 menu.render_aside(max_size, window, cx)
10527 } else {
10528 None
10529 }
10530 })
10531 }
10532
10533 fn hide_context_menu(
10534 &mut self,
10535 window: &mut Window,
10536 cx: &mut Context<Self>,
10537 ) -> Option<CodeContextMenu> {
10538 cx.notify();
10539 self.completion_tasks.clear();
10540 let context_menu = self.context_menu.borrow_mut().take();
10541 self.stale_edit_prediction_in_menu.take();
10542 self.update_visible_edit_prediction(window, cx);
10543 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10544 && let Some(completion_provider) = &self.completion_provider
10545 {
10546 completion_provider.selection_changed(None, window, cx);
10547 }
10548 context_menu
10549 }
10550
10551 fn show_snippet_choices(
10552 &mut self,
10553 choices: &Vec<String>,
10554 selection: Range<Anchor>,
10555 cx: &mut Context<Self>,
10556 ) {
10557 let Some((_, buffer, _)) = self
10558 .buffer()
10559 .read(cx)
10560 .excerpt_containing(selection.start, cx)
10561 else {
10562 return;
10563 };
10564 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10565 else {
10566 return;
10567 };
10568 if buffer != end_buffer {
10569 log::error!("expected anchor range to have matching buffer IDs");
10570 return;
10571 }
10572
10573 let id = post_inc(&mut self.next_completion_id);
10574 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10575 let mut context_menu = self.context_menu.borrow_mut();
10576 let old_menu = context_menu.take();
10577 *context_menu = Some(CodeContextMenu::Completions(
10578 CompletionsMenu::new_snippet_choices(
10579 id,
10580 true,
10581 choices,
10582 selection,
10583 buffer,
10584 old_menu.map(|menu| menu.primary_scroll_handle()),
10585 snippet_sort_order,
10586 ),
10587 ));
10588 }
10589
10590 pub fn insert_snippet(
10591 &mut self,
10592 insertion_ranges: &[Range<MultiBufferOffset>],
10593 snippet: Snippet,
10594 window: &mut Window,
10595 cx: &mut Context<Self>,
10596 ) -> Result<()> {
10597 struct Tabstop<T> {
10598 is_end_tabstop: bool,
10599 ranges: Vec<Range<T>>,
10600 choices: Option<Vec<String>>,
10601 }
10602
10603 let tabstops = self.buffer.update(cx, |buffer, cx| {
10604 let snippet_text: Arc<str> = snippet.text.clone().into();
10605 let edits = insertion_ranges
10606 .iter()
10607 .cloned()
10608 .map(|range| (range, snippet_text.clone()));
10609 let autoindent_mode = AutoindentMode::Block {
10610 original_indent_columns: Vec::new(),
10611 };
10612 buffer.edit(edits, Some(autoindent_mode), cx);
10613
10614 let snapshot = &*buffer.read(cx);
10615 let snippet = &snippet;
10616 snippet
10617 .tabstops
10618 .iter()
10619 .map(|tabstop| {
10620 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10621 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10622 });
10623 let mut tabstop_ranges = tabstop
10624 .ranges
10625 .iter()
10626 .flat_map(|tabstop_range| {
10627 let mut delta = 0_isize;
10628 insertion_ranges.iter().map(move |insertion_range| {
10629 let insertion_start = insertion_range.start + delta;
10630 delta += snippet.text.len() as isize
10631 - (insertion_range.end - insertion_range.start) as isize;
10632
10633 let start =
10634 (insertion_start + tabstop_range.start).min(snapshot.len());
10635 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10636 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10637 })
10638 })
10639 .collect::<Vec<_>>();
10640 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10641
10642 Tabstop {
10643 is_end_tabstop,
10644 ranges: tabstop_ranges,
10645 choices: tabstop.choices.clone(),
10646 }
10647 })
10648 .collect::<Vec<_>>()
10649 });
10650 if let Some(tabstop) = tabstops.first() {
10651 self.change_selections(Default::default(), window, cx, |s| {
10652 // Reverse order so that the first range is the newest created selection.
10653 // Completions will use it and autoscroll will prioritize it.
10654 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10655 });
10656
10657 if let Some(choices) = &tabstop.choices
10658 && let Some(selection) = tabstop.ranges.first()
10659 {
10660 self.show_snippet_choices(choices, selection.clone(), cx)
10661 }
10662
10663 // If we're already at the last tabstop and it's at the end of the snippet,
10664 // we're done, we don't need to keep the state around.
10665 if !tabstop.is_end_tabstop {
10666 let choices = tabstops
10667 .iter()
10668 .map(|tabstop| tabstop.choices.clone())
10669 .collect();
10670
10671 let ranges = tabstops
10672 .into_iter()
10673 .map(|tabstop| tabstop.ranges)
10674 .collect::<Vec<_>>();
10675
10676 self.snippet_stack.push(SnippetState {
10677 active_index: 0,
10678 ranges,
10679 choices,
10680 });
10681 }
10682
10683 // Check whether the just-entered snippet ends with an auto-closable bracket.
10684 if self.autoclose_regions.is_empty() {
10685 let snapshot = self.buffer.read(cx).snapshot(cx);
10686 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10687 let selection_head = selection.head();
10688 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10689 continue;
10690 };
10691
10692 let mut bracket_pair = None;
10693 let max_lookup_length = scope
10694 .brackets()
10695 .map(|(pair, _)| {
10696 pair.start
10697 .as_str()
10698 .chars()
10699 .count()
10700 .max(pair.end.as_str().chars().count())
10701 })
10702 .max();
10703 if let Some(max_lookup_length) = max_lookup_length {
10704 let next_text = snapshot
10705 .chars_at(selection_head)
10706 .take(max_lookup_length)
10707 .collect::<String>();
10708 let prev_text = snapshot
10709 .reversed_chars_at(selection_head)
10710 .take(max_lookup_length)
10711 .collect::<String>();
10712
10713 for (pair, enabled) in scope.brackets() {
10714 if enabled
10715 && pair.close
10716 && prev_text.starts_with(pair.start.as_str())
10717 && next_text.starts_with(pair.end.as_str())
10718 {
10719 bracket_pair = Some(pair.clone());
10720 break;
10721 }
10722 }
10723 }
10724
10725 if let Some(pair) = bracket_pair {
10726 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10727 let autoclose_enabled =
10728 self.use_autoclose && snapshot_settings.use_autoclose;
10729 if autoclose_enabled {
10730 let start = snapshot.anchor_after(selection_head);
10731 let end = snapshot.anchor_after(selection_head);
10732 self.autoclose_regions.push(AutocloseRegion {
10733 selection_id: selection.id,
10734 range: start..end,
10735 pair,
10736 });
10737 }
10738 }
10739 }
10740 }
10741 }
10742 Ok(())
10743 }
10744
10745 pub fn move_to_next_snippet_tabstop(
10746 &mut self,
10747 window: &mut Window,
10748 cx: &mut Context<Self>,
10749 ) -> bool {
10750 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10751 }
10752
10753 pub fn move_to_prev_snippet_tabstop(
10754 &mut self,
10755 window: &mut Window,
10756 cx: &mut Context<Self>,
10757 ) -> bool {
10758 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10759 }
10760
10761 pub fn move_to_snippet_tabstop(
10762 &mut self,
10763 bias: Bias,
10764 window: &mut Window,
10765 cx: &mut Context<Self>,
10766 ) -> bool {
10767 if let Some(mut snippet) = self.snippet_stack.pop() {
10768 match bias {
10769 Bias::Left => {
10770 if snippet.active_index > 0 {
10771 snippet.active_index -= 1;
10772 } else {
10773 self.snippet_stack.push(snippet);
10774 return false;
10775 }
10776 }
10777 Bias::Right => {
10778 if snippet.active_index + 1 < snippet.ranges.len() {
10779 snippet.active_index += 1;
10780 } else {
10781 self.snippet_stack.push(snippet);
10782 return false;
10783 }
10784 }
10785 }
10786 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10787 self.change_selections(Default::default(), window, cx, |s| {
10788 // Reverse order so that the first range is the newest created selection.
10789 // Completions will use it and autoscroll will prioritize it.
10790 s.select_ranges(current_ranges.iter().rev().cloned())
10791 });
10792
10793 if let Some(choices) = &snippet.choices[snippet.active_index]
10794 && let Some(selection) = current_ranges.first()
10795 {
10796 self.show_snippet_choices(choices, selection.clone(), cx);
10797 }
10798
10799 // If snippet state is not at the last tabstop, push it back on the stack
10800 if snippet.active_index + 1 < snippet.ranges.len() {
10801 self.snippet_stack.push(snippet);
10802 }
10803 return true;
10804 }
10805 }
10806
10807 false
10808 }
10809
10810 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10811 self.transact(window, cx, |this, window, cx| {
10812 this.select_all(&SelectAll, window, cx);
10813 this.insert("", window, cx);
10814 });
10815 }
10816
10817 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10818 if self.read_only(cx) {
10819 return;
10820 }
10821 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10822 self.transact(window, cx, |this, window, cx| {
10823 this.select_autoclose_pair(window, cx);
10824
10825 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10826
10827 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10828 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10829 for selection in &mut selections {
10830 if selection.is_empty() {
10831 let old_head = selection.head();
10832 let mut new_head =
10833 movement::left(&display_map, old_head.to_display_point(&display_map))
10834 .to_point(&display_map);
10835 if let Some((buffer, line_buffer_range)) = display_map
10836 .buffer_snapshot()
10837 .buffer_line_for_row(MultiBufferRow(old_head.row))
10838 {
10839 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10840 let indent_len = match indent_size.kind {
10841 IndentKind::Space => {
10842 buffer.settings_at(line_buffer_range.start, cx).tab_size
10843 }
10844 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10845 };
10846 if old_head.column <= indent_size.len && old_head.column > 0 {
10847 let indent_len = indent_len.get();
10848 new_head = cmp::min(
10849 new_head,
10850 MultiBufferPoint::new(
10851 old_head.row,
10852 ((old_head.column - 1) / indent_len) * indent_len,
10853 ),
10854 );
10855 }
10856 }
10857
10858 selection.set_head(new_head, SelectionGoal::None);
10859 }
10860 }
10861
10862 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10863 this.insert("", window, cx);
10864 linked_edits.apply_with_left_expansion(cx);
10865 this.refresh_edit_prediction(true, false, window, cx);
10866 refresh_linked_ranges(this, window, cx);
10867 });
10868 }
10869
10870 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10871 if self.read_only(cx) {
10872 return;
10873 }
10874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10875 self.transact(window, cx, |this, window, cx| {
10876 this.change_selections(Default::default(), window, cx, |s| {
10877 s.move_with(&mut |map, selection| {
10878 if selection.is_empty() {
10879 let cursor = movement::right(map, selection.head());
10880 selection.end = cursor;
10881 selection.reversed = true;
10882 selection.goal = SelectionGoal::None;
10883 }
10884 })
10885 });
10886 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10887 this.insert("", window, cx);
10888 linked_edits.apply(cx);
10889 this.refresh_edit_prediction(true, false, window, cx);
10890 refresh_linked_ranges(this, window, cx);
10891 });
10892 }
10893
10894 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10895 if self.mode.is_single_line() {
10896 cx.propagate();
10897 return;
10898 }
10899
10900 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10901 if self.move_to_prev_snippet_tabstop(window, cx) {
10902 return;
10903 }
10904 self.outdent(&Outdent, window, cx);
10905 }
10906
10907 pub fn next_snippet_tabstop(
10908 &mut self,
10909 _: &NextSnippetTabstop,
10910 window: &mut Window,
10911 cx: &mut Context<Self>,
10912 ) {
10913 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10914 cx.propagate();
10915 return;
10916 }
10917
10918 if self.move_to_next_snippet_tabstop(window, cx) {
10919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10920 return;
10921 }
10922 cx.propagate();
10923 }
10924
10925 pub fn previous_snippet_tabstop(
10926 &mut self,
10927 _: &PreviousSnippetTabstop,
10928 window: &mut Window,
10929 cx: &mut Context<Self>,
10930 ) {
10931 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10932 cx.propagate();
10933 return;
10934 }
10935
10936 if self.move_to_prev_snippet_tabstop(window, cx) {
10937 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10938 return;
10939 }
10940 cx.propagate();
10941 }
10942
10943 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10944 if self.mode.is_single_line() {
10945 cx.propagate();
10946 return;
10947 }
10948
10949 if self.move_to_next_snippet_tabstop(window, cx) {
10950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10951 return;
10952 }
10953 if self.read_only(cx) {
10954 return;
10955 }
10956 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10957 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10958 let buffer = self.buffer.read(cx);
10959 let snapshot = buffer.snapshot(cx);
10960 let rows_iter = selections.iter().map(|s| s.head().row);
10961 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10962
10963 let has_some_cursor_in_whitespace = selections
10964 .iter()
10965 .filter(|selection| selection.is_empty())
10966 .any(|selection| {
10967 let cursor = selection.head();
10968 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10969 cursor.column < current_indent.len
10970 });
10971
10972 let mut edits = Vec::new();
10973 let mut prev_edited_row = 0;
10974 let mut row_delta = 0;
10975 for selection in &mut selections {
10976 if selection.start.row != prev_edited_row {
10977 row_delta = 0;
10978 }
10979 prev_edited_row = selection.end.row;
10980
10981 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10982 if selection.is_empty() {
10983 let cursor = selection.head();
10984 let settings = buffer.language_settings_at(cursor, cx);
10985 if settings.indent_list_on_tab {
10986 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10987 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10988 row_delta = Self::indent_selection(
10989 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10990 );
10991 continue;
10992 }
10993 }
10994 }
10995 }
10996
10997 // If the selection is non-empty, then increase the indentation of the selected lines.
10998 if !selection.is_empty() {
10999 row_delta =
11000 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11001 continue;
11002 }
11003
11004 let cursor = selection.head();
11005 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
11006 if let Some(suggested_indent) =
11007 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
11008 {
11009 // Don't do anything if already at suggested indent
11010 // and there is any other cursor which is not
11011 if has_some_cursor_in_whitespace
11012 && cursor.column == current_indent.len
11013 && current_indent.len == suggested_indent.len
11014 {
11015 continue;
11016 }
11017
11018 // Adjust line and move cursor to suggested indent
11019 // if cursor is not at suggested indent
11020 if cursor.column < suggested_indent.len
11021 && cursor.column <= current_indent.len
11022 && current_indent.len <= suggested_indent.len
11023 {
11024 selection.start = Point::new(cursor.row, suggested_indent.len);
11025 selection.end = selection.start;
11026 if row_delta == 0 {
11027 edits.extend(Buffer::edit_for_indent_size_adjustment(
11028 cursor.row,
11029 current_indent,
11030 suggested_indent,
11031 ));
11032 row_delta = suggested_indent.len - current_indent.len;
11033 }
11034 continue;
11035 }
11036
11037 // If current indent is more than suggested indent
11038 // only move cursor to current indent and skip indent
11039 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
11040 selection.start = Point::new(cursor.row, current_indent.len);
11041 selection.end = selection.start;
11042 continue;
11043 }
11044 }
11045
11046 // Otherwise, insert a hard or soft tab.
11047 let settings = buffer.language_settings_at(cursor, cx);
11048 let tab_size = if settings.hard_tabs {
11049 IndentSize::tab()
11050 } else {
11051 let tab_size = settings.tab_size.get();
11052 let indent_remainder = snapshot
11053 .text_for_range(Point::new(cursor.row, 0)..cursor)
11054 .flat_map(str::chars)
11055 .fold(row_delta % tab_size, |counter: u32, c| {
11056 if c == '\t' {
11057 0
11058 } else {
11059 (counter + 1) % tab_size
11060 }
11061 });
11062
11063 let chars_to_next_tab_stop = tab_size - indent_remainder;
11064 IndentSize::spaces(chars_to_next_tab_stop)
11065 };
11066 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
11067 selection.end = selection.start;
11068 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11069 row_delta += tab_size.len;
11070 }
11071
11072 self.transact(window, cx, |this, window, cx| {
11073 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11074 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11075 this.refresh_edit_prediction(true, false, window, cx);
11076 });
11077 }
11078
11079 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11080 if self.read_only(cx) {
11081 return;
11082 }
11083 if self.mode.is_single_line() {
11084 cx.propagate();
11085 return;
11086 }
11087
11088 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11089 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11090 let mut prev_edited_row = 0;
11091 let mut row_delta = 0;
11092 let mut edits = Vec::new();
11093 let buffer = self.buffer.read(cx);
11094 let snapshot = buffer.snapshot(cx);
11095 for selection in &mut selections {
11096 if selection.start.row != prev_edited_row {
11097 row_delta = 0;
11098 }
11099 prev_edited_row = selection.end.row;
11100
11101 row_delta =
11102 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11103 }
11104
11105 self.transact(window, cx, |this, window, cx| {
11106 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11107 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11108 });
11109 }
11110
11111 fn indent_selection(
11112 buffer: &MultiBuffer,
11113 snapshot: &MultiBufferSnapshot,
11114 selection: &mut Selection<Point>,
11115 edits: &mut Vec<(Range<Point>, String)>,
11116 delta_for_start_row: u32,
11117 cx: &App,
11118 ) -> u32 {
11119 let settings = buffer.language_settings_at(selection.start, cx);
11120 let tab_size = settings.tab_size.get();
11121 let indent_kind = if settings.hard_tabs {
11122 IndentKind::Tab
11123 } else {
11124 IndentKind::Space
11125 };
11126 let mut start_row = selection.start.row;
11127 let mut end_row = selection.end.row + 1;
11128
11129 // If a selection ends at the beginning of a line, don't indent
11130 // that last line.
11131 if selection.end.column == 0 && selection.end.row > selection.start.row {
11132 end_row -= 1;
11133 }
11134
11135 // Avoid re-indenting a row that has already been indented by a
11136 // previous selection, but still update this selection's column
11137 // to reflect that indentation.
11138 if delta_for_start_row > 0 {
11139 start_row += 1;
11140 selection.start.column += delta_for_start_row;
11141 if selection.end.row == selection.start.row {
11142 selection.end.column += delta_for_start_row;
11143 }
11144 }
11145
11146 let mut delta_for_end_row = 0;
11147 let has_multiple_rows = start_row + 1 != end_row;
11148 for row in start_row..end_row {
11149 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11150 let indent_delta = match (current_indent.kind, indent_kind) {
11151 (IndentKind::Space, IndentKind::Space) => {
11152 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11153 IndentSize::spaces(columns_to_next_tab_stop)
11154 }
11155 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11156 (_, IndentKind::Tab) => IndentSize::tab(),
11157 };
11158
11159 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11160 0
11161 } else {
11162 selection.start.column
11163 };
11164 let row_start = Point::new(row, start);
11165 edits.push((
11166 row_start..row_start,
11167 indent_delta.chars().collect::<String>(),
11168 ));
11169
11170 // Update this selection's endpoints to reflect the indentation.
11171 if row == selection.start.row {
11172 selection.start.column += indent_delta.len;
11173 }
11174 if row == selection.end.row {
11175 selection.end.column += indent_delta.len;
11176 delta_for_end_row = indent_delta.len;
11177 }
11178 }
11179
11180 if selection.start.row == selection.end.row {
11181 delta_for_start_row + delta_for_end_row
11182 } else {
11183 delta_for_end_row
11184 }
11185 }
11186
11187 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11188 if self.read_only(cx) {
11189 return;
11190 }
11191 if self.mode.is_single_line() {
11192 cx.propagate();
11193 return;
11194 }
11195
11196 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11197 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11198 let selections = self.selections.all::<Point>(&display_map);
11199 let mut deletion_ranges = Vec::new();
11200 let mut last_outdent = None;
11201 {
11202 let buffer = self.buffer.read(cx);
11203 let snapshot = buffer.snapshot(cx);
11204 for selection in &selections {
11205 let settings = buffer.language_settings_at(selection.start, cx);
11206 let tab_size = settings.tab_size.get();
11207 let mut rows = selection.spanned_rows(false, &display_map);
11208
11209 // Avoid re-outdenting a row that has already been outdented by a
11210 // previous selection.
11211 if let Some(last_row) = last_outdent
11212 && last_row == rows.start
11213 {
11214 rows.start = rows.start.next_row();
11215 }
11216 let has_multiple_rows = rows.len() > 1;
11217 for row in rows.iter_rows() {
11218 let indent_size = snapshot.indent_size_for_line(row);
11219 if indent_size.len > 0 {
11220 let deletion_len = match indent_size.kind {
11221 IndentKind::Space => {
11222 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11223 if columns_to_prev_tab_stop == 0 {
11224 tab_size
11225 } else {
11226 columns_to_prev_tab_stop
11227 }
11228 }
11229 IndentKind::Tab => 1,
11230 };
11231 let start = if has_multiple_rows
11232 || deletion_len > selection.start.column
11233 || indent_size.len < selection.start.column
11234 {
11235 0
11236 } else {
11237 selection.start.column - deletion_len
11238 };
11239 deletion_ranges.push(
11240 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11241 );
11242 last_outdent = Some(row);
11243 }
11244 }
11245 }
11246 }
11247
11248 self.transact(window, cx, |this, window, cx| {
11249 this.buffer.update(cx, |buffer, cx| {
11250 let empty_str: Arc<str> = Arc::default();
11251 buffer.edit(
11252 deletion_ranges
11253 .into_iter()
11254 .map(|range| (range, empty_str.clone())),
11255 None,
11256 cx,
11257 );
11258 });
11259 let selections = this
11260 .selections
11261 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11262 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11263 });
11264 }
11265
11266 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11267 if self.read_only(cx) {
11268 return;
11269 }
11270 if self.mode.is_single_line() {
11271 cx.propagate();
11272 return;
11273 }
11274
11275 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11276 let selections = self
11277 .selections
11278 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11279 .into_iter()
11280 .map(|s| s.range());
11281
11282 self.transact(window, cx, |this, window, cx| {
11283 this.buffer.update(cx, |buffer, cx| {
11284 buffer.autoindent_ranges(selections, cx);
11285 });
11286 let selections = this
11287 .selections
11288 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11289 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11290 });
11291 }
11292
11293 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11294 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11295 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11296 let selections = self.selections.all::<Point>(&display_map);
11297
11298 let mut new_cursors = Vec::new();
11299 let mut edit_ranges = Vec::new();
11300 let mut selections = selections.iter().peekable();
11301 while let Some(selection) = selections.next() {
11302 let mut rows = selection.spanned_rows(false, &display_map);
11303
11304 // Accumulate contiguous regions of rows that we want to delete.
11305 while let Some(next_selection) = selections.peek() {
11306 let next_rows = next_selection.spanned_rows(false, &display_map);
11307 if next_rows.start <= rows.end {
11308 rows.end = next_rows.end;
11309 selections.next().unwrap();
11310 } else {
11311 break;
11312 }
11313 }
11314
11315 let buffer = display_map.buffer_snapshot();
11316 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11317 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11318 // If there's a line after the range, delete the \n from the end of the row range
11319 (
11320 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11321 rows.end,
11322 )
11323 } else {
11324 // If there isn't a line after the range, delete the \n from the line before the
11325 // start of the row range
11326 edit_start = edit_start.saturating_sub_usize(1);
11327 (buffer.len(), rows.start.previous_row())
11328 };
11329
11330 let text_layout_details = self.text_layout_details(window, cx);
11331 let x = display_map.x_for_display_point(
11332 selection.head().to_display_point(&display_map),
11333 &text_layout_details,
11334 );
11335 let row = Point::new(target_row.0, 0)
11336 .to_display_point(&display_map)
11337 .row();
11338 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11339
11340 new_cursors.push((
11341 selection.id,
11342 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11343 SelectionGoal::None,
11344 ));
11345 edit_ranges.push(edit_start..edit_end);
11346 }
11347
11348 self.transact(window, cx, |this, window, cx| {
11349 let buffer = this.buffer.update(cx, |buffer, cx| {
11350 let empty_str: Arc<str> = Arc::default();
11351 buffer.edit(
11352 edit_ranges
11353 .into_iter()
11354 .map(|range| (range, empty_str.clone())),
11355 None,
11356 cx,
11357 );
11358 buffer.snapshot(cx)
11359 });
11360 let new_selections = new_cursors
11361 .into_iter()
11362 .map(|(id, cursor, goal)| {
11363 let cursor = cursor.to_point(&buffer);
11364 Selection {
11365 id,
11366 start: cursor,
11367 end: cursor,
11368 reversed: false,
11369 goal,
11370 }
11371 })
11372 .collect();
11373
11374 this.change_selections(Default::default(), window, cx, |s| {
11375 s.select(new_selections);
11376 });
11377 });
11378 }
11379
11380 pub fn join_lines_impl(
11381 &mut self,
11382 insert_whitespace: bool,
11383 window: &mut Window,
11384 cx: &mut Context<Self>,
11385 ) {
11386 if self.read_only(cx) {
11387 return;
11388 }
11389 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11390 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11391 let start = MultiBufferRow(selection.start.row);
11392 // Treat single line selections as if they include the next line. Otherwise this action
11393 // would do nothing for single line selections individual cursors.
11394 let end = if selection.start.row == selection.end.row {
11395 MultiBufferRow(selection.start.row + 1)
11396 } else if selection.end.column == 0 {
11397 // If the selection ends at the start of a line, it's logically at the end of the
11398 // previous line (plus its newline).
11399 // Don't include the end line unless there's only one line selected.
11400 if selection.start.row + 1 == selection.end.row {
11401 MultiBufferRow(selection.end.row)
11402 } else {
11403 MultiBufferRow(selection.end.row - 1)
11404 }
11405 } else {
11406 MultiBufferRow(selection.end.row)
11407 };
11408
11409 if let Some(last_row_range) = row_ranges.last_mut()
11410 && start <= last_row_range.end
11411 {
11412 last_row_range.end = end;
11413 continue;
11414 }
11415 row_ranges.push(start..end);
11416 }
11417
11418 let snapshot = self.buffer.read(cx).snapshot(cx);
11419 let mut cursor_positions = Vec::new();
11420 for row_range in &row_ranges {
11421 let anchor = snapshot.anchor_before(Point::new(
11422 row_range.end.previous_row().0,
11423 snapshot.line_len(row_range.end.previous_row()),
11424 ));
11425 cursor_positions.push(anchor..anchor);
11426 }
11427
11428 self.transact(window, cx, |this, window, cx| {
11429 for row_range in row_ranges.into_iter().rev() {
11430 for row in row_range.iter_rows().rev() {
11431 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11432 let next_line_row = row.next_row();
11433 let indent = snapshot.indent_size_for_line(next_line_row);
11434 let mut join_start_column = indent.len;
11435
11436 if let Some(language_scope) =
11437 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11438 {
11439 let line_end =
11440 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11441 let line_text_after_indent = snapshot
11442 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11443 .collect::<String>();
11444
11445 if !line_text_after_indent.is_empty() {
11446 let block_prefix = language_scope
11447 .block_comment()
11448 .map(|c| c.prefix.as_ref())
11449 .filter(|p| !p.is_empty());
11450 let doc_prefix = language_scope
11451 .documentation_comment()
11452 .map(|c| c.prefix.as_ref())
11453 .filter(|p| !p.is_empty());
11454 let all_prefixes = language_scope
11455 .line_comment_prefixes()
11456 .iter()
11457 .map(|p| p.as_ref())
11458 .chain(block_prefix)
11459 .chain(doc_prefix)
11460 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11461
11462 let mut longest_prefix_len = None;
11463 for prefix in all_prefixes {
11464 let trimmed = prefix.trim_end();
11465 if line_text_after_indent.starts_with(trimmed) {
11466 let candidate_len =
11467 if line_text_after_indent.starts_with(prefix) {
11468 prefix.len()
11469 } else {
11470 trimmed.len()
11471 };
11472 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11473 longest_prefix_len = Some(candidate_len);
11474 }
11475 }
11476 }
11477
11478 if let Some(prefix_len) = longest_prefix_len {
11479 join_start_column =
11480 join_start_column.saturating_add(prefix_len as u32);
11481 }
11482 }
11483 }
11484
11485 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11486
11487 let replace = if snapshot.line_len(next_line_row) > join_start_column
11488 && insert_whitespace
11489 {
11490 " "
11491 } else {
11492 ""
11493 };
11494
11495 this.buffer.update(cx, |buffer, cx| {
11496 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11497 });
11498 }
11499 }
11500
11501 this.change_selections(Default::default(), window, cx, |s| {
11502 s.select_anchor_ranges(cursor_positions)
11503 });
11504 });
11505 }
11506
11507 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11508 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11509 self.join_lines_impl(true, window, cx);
11510 }
11511
11512 pub fn sort_lines_case_sensitive(
11513 &mut self,
11514 _: &SortLinesCaseSensitive,
11515 window: &mut Window,
11516 cx: &mut Context<Self>,
11517 ) {
11518 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11519 }
11520
11521 pub fn sort_lines_by_length(
11522 &mut self,
11523 _: &SortLinesByLength,
11524 window: &mut Window,
11525 cx: &mut Context<Self>,
11526 ) {
11527 self.manipulate_immutable_lines(window, cx, |lines| {
11528 lines.sort_by_key(|&line| line.chars().count())
11529 })
11530 }
11531
11532 pub fn sort_lines_case_insensitive(
11533 &mut self,
11534 _: &SortLinesCaseInsensitive,
11535 window: &mut Window,
11536 cx: &mut Context<Self>,
11537 ) {
11538 self.manipulate_immutable_lines(window, cx, |lines| {
11539 lines.sort_by_key(|line| line.to_lowercase())
11540 })
11541 }
11542
11543 pub fn unique_lines_case_insensitive(
11544 &mut self,
11545 _: &UniqueLinesCaseInsensitive,
11546 window: &mut Window,
11547 cx: &mut Context<Self>,
11548 ) {
11549 self.manipulate_immutable_lines(window, cx, |lines| {
11550 let mut seen = HashSet::default();
11551 lines.retain(|line| seen.insert(line.to_lowercase()));
11552 })
11553 }
11554
11555 pub fn unique_lines_case_sensitive(
11556 &mut self,
11557 _: &UniqueLinesCaseSensitive,
11558 window: &mut Window,
11559 cx: &mut Context<Self>,
11560 ) {
11561 self.manipulate_immutable_lines(window, cx, |lines| {
11562 let mut seen = HashSet::default();
11563 lines.retain(|line| seen.insert(*line));
11564 })
11565 }
11566
11567 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11568 let snapshot = self.buffer.read(cx).snapshot(cx);
11569 for selection in self.selections.disjoint_anchors_arc().iter() {
11570 if snapshot
11571 .language_at(selection.start)
11572 .and_then(|lang| lang.config().wrap_characters.as_ref())
11573 .is_some()
11574 {
11575 return true;
11576 }
11577 }
11578 false
11579 }
11580
11581 fn wrap_selections_in_tag(
11582 &mut self,
11583 _: &WrapSelectionsInTag,
11584 window: &mut Window,
11585 cx: &mut Context<Self>,
11586 ) {
11587 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11588
11589 let snapshot = self.buffer.read(cx).snapshot(cx);
11590
11591 let mut edits = Vec::new();
11592 let mut boundaries = Vec::new();
11593
11594 for selection in self
11595 .selections
11596 .all_adjusted(&self.display_snapshot(cx))
11597 .iter()
11598 {
11599 let Some(wrap_config) = snapshot
11600 .language_at(selection.start)
11601 .and_then(|lang| lang.config().wrap_characters.clone())
11602 else {
11603 continue;
11604 };
11605
11606 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11607 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11608
11609 let start_before = snapshot.anchor_before(selection.start);
11610 let end_after = snapshot.anchor_after(selection.end);
11611
11612 edits.push((start_before..start_before, open_tag));
11613 edits.push((end_after..end_after, close_tag));
11614
11615 boundaries.push((
11616 start_before,
11617 end_after,
11618 wrap_config.start_prefix.len(),
11619 wrap_config.end_suffix.len(),
11620 ));
11621 }
11622
11623 if edits.is_empty() {
11624 return;
11625 }
11626
11627 self.transact(window, cx, |this, window, cx| {
11628 let buffer = this.buffer.update(cx, |buffer, cx| {
11629 buffer.edit(edits, None, cx);
11630 buffer.snapshot(cx)
11631 });
11632
11633 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11634 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11635 boundaries.into_iter()
11636 {
11637 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11638 let close_offset = end_after
11639 .to_offset(&buffer)
11640 .saturating_sub_usize(end_suffix_len);
11641 new_selections.push(open_offset..open_offset);
11642 new_selections.push(close_offset..close_offset);
11643 }
11644
11645 this.change_selections(Default::default(), window, cx, |s| {
11646 s.select_ranges(new_selections);
11647 });
11648
11649 this.request_autoscroll(Autoscroll::fit(), cx);
11650 });
11651 }
11652
11653 pub fn toggle_read_only(
11654 &mut self,
11655 _: &workspace::ToggleReadOnlyFile,
11656 _: &mut Window,
11657 cx: &mut Context<Self>,
11658 ) {
11659 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11660 buffer.update(cx, |buffer, cx| {
11661 buffer.set_capability(
11662 match buffer.capability() {
11663 Capability::ReadWrite => Capability::Read,
11664 Capability::Read => Capability::ReadWrite,
11665 Capability::ReadOnly => Capability::ReadOnly,
11666 },
11667 cx,
11668 );
11669 })
11670 }
11671 }
11672
11673 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11674 let Some(project) = self.project.clone() else {
11675 return;
11676 };
11677 let task = self.reload(project, window, cx);
11678 self.detach_and_notify_err(task, window, cx);
11679 }
11680
11681 pub fn restore_file(
11682 &mut self,
11683 _: &::git::RestoreFile,
11684 window: &mut Window,
11685 cx: &mut Context<Self>,
11686 ) {
11687 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11688 let mut buffer_ids = HashSet::default();
11689 let snapshot = self.buffer().read(cx).snapshot(cx);
11690 for selection in self
11691 .selections
11692 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11693 {
11694 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11695 }
11696
11697 let buffer = self.buffer().read(cx);
11698 let ranges = buffer_ids
11699 .into_iter()
11700 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11701 .collect::<Vec<_>>();
11702
11703 self.restore_hunks_in_ranges(ranges, window, cx);
11704 }
11705
11706 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11707 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11708 let selections = self
11709 .selections
11710 .all(&self.display_snapshot(cx))
11711 .into_iter()
11712 .map(|s| s.range())
11713 .collect();
11714 self.restore_hunks_in_ranges(selections, window, cx);
11715 }
11716
11717 /// Restores the diff hunks in the editor's selections and moves the cursor
11718 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11719 /// not all diff hunks are expanded.
11720 pub fn restore_and_next(
11721 &mut self,
11722 _: &::git::RestoreAndNext,
11723 window: &mut Window,
11724 cx: &mut Context<Self>,
11725 ) {
11726 let selections = self
11727 .selections
11728 .all(&self.display_snapshot(cx))
11729 .into_iter()
11730 .map(|selection| selection.range())
11731 .collect();
11732
11733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11734 self.restore_hunks_in_ranges(selections, window, cx);
11735
11736 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11737 let wrap_around = !all_diff_hunks_expanded;
11738 let snapshot = self.snapshot(window, cx);
11739 let position = self
11740 .selections
11741 .newest::<Point>(&snapshot.display_snapshot)
11742 .head();
11743
11744 self.go_to_hunk_before_or_after_position(
11745 &snapshot,
11746 position,
11747 Direction::Next,
11748 wrap_around,
11749 window,
11750 cx,
11751 );
11752 }
11753
11754 pub fn restore_hunks_in_ranges(
11755 &mut self,
11756 ranges: Vec<Range<Point>>,
11757 window: &mut Window,
11758 cx: &mut Context<Editor>,
11759 ) {
11760 if self.delegate_stage_and_restore {
11761 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11762 if !hunks.is_empty() {
11763 cx.emit(EditorEvent::RestoreRequested { hunks });
11764 }
11765 return;
11766 }
11767 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11768 self.transact(window, cx, |editor, window, cx| {
11769 editor.restore_diff_hunks(hunks, cx);
11770 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11771 selections.refresh()
11772 });
11773 });
11774 }
11775
11776 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11777 let mut revert_changes = HashMap::default();
11778 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11779 for (buffer_id, hunks) in &chunk_by {
11780 let hunks = hunks.collect::<Vec<_>>();
11781 for hunk in &hunks {
11782 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11783 }
11784 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11785 }
11786 if !revert_changes.is_empty() {
11787 self.buffer().update(cx, |multi_buffer, cx| {
11788 for (buffer_id, changes) in revert_changes {
11789 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11790 buffer.update(cx, |buffer, cx| {
11791 buffer.edit(
11792 changes
11793 .into_iter()
11794 .map(|(range, text)| (range, text.to_string())),
11795 None,
11796 cx,
11797 );
11798 });
11799 }
11800 }
11801 });
11802 }
11803 }
11804
11805 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11806 if let Some(status) = self
11807 .addons
11808 .iter()
11809 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11810 {
11811 return Some(status);
11812 }
11813 self.project
11814 .as_ref()?
11815 .read(cx)
11816 .status_for_buffer_id(buffer_id, cx)
11817 }
11818
11819 pub fn open_active_item_in_terminal(
11820 &mut self,
11821 _: &OpenInTerminal,
11822 window: &mut Window,
11823 cx: &mut Context<Self>,
11824 ) {
11825 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11826 let project_path = buffer.read(cx).project_path(cx)?;
11827 let project = self.project()?.read(cx);
11828 let entry = project.entry_for_path(&project_path, cx)?;
11829 let parent = match &entry.canonical_path {
11830 Some(canonical_path) => canonical_path.to_path_buf(),
11831 None => project.absolute_path(&project_path, cx)?,
11832 }
11833 .parent()?
11834 .to_path_buf();
11835 Some(parent)
11836 }) {
11837 window.dispatch_action(
11838 OpenTerminal {
11839 working_directory,
11840 local: false,
11841 }
11842 .boxed_clone(),
11843 cx,
11844 );
11845 }
11846 }
11847
11848 fn set_breakpoint_context_menu(
11849 &mut self,
11850 display_row: DisplayRow,
11851 position: Option<Anchor>,
11852 clicked_point: gpui::Point<Pixels>,
11853 window: &mut Window,
11854 cx: &mut Context<Self>,
11855 ) {
11856 let source = self
11857 .buffer
11858 .read(cx)
11859 .snapshot(cx)
11860 .anchor_before(Point::new(display_row.0, 0u32));
11861
11862 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11863
11864 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11865 self,
11866 source,
11867 clicked_point,
11868 context_menu,
11869 window,
11870 cx,
11871 );
11872 }
11873
11874 fn add_edit_breakpoint_block(
11875 &mut self,
11876 anchor: Anchor,
11877 breakpoint: &Breakpoint,
11878 edit_action: BreakpointPromptEditAction,
11879 window: &mut Window,
11880 cx: &mut Context<Self>,
11881 ) {
11882 let weak_editor = cx.weak_entity();
11883 let bp_prompt = cx.new(|cx| {
11884 BreakpointPromptEditor::new(
11885 weak_editor,
11886 anchor,
11887 breakpoint.clone(),
11888 edit_action,
11889 window,
11890 cx,
11891 )
11892 });
11893
11894 let height = bp_prompt.update(cx, |this, cx| {
11895 this.prompt
11896 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11897 });
11898 let cloned_prompt = bp_prompt.clone();
11899 let blocks = vec![BlockProperties {
11900 style: BlockStyle::Sticky,
11901 placement: BlockPlacement::Above(anchor),
11902 height: Some(height),
11903 render: Arc::new(move |cx| {
11904 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11905 cloned_prompt.clone().into_any_element()
11906 }),
11907 priority: 0,
11908 }];
11909
11910 let focus_handle = bp_prompt.focus_handle(cx);
11911 window.focus(&focus_handle, cx);
11912
11913 let block_ids = self.insert_blocks(blocks, None, cx);
11914 bp_prompt.update(cx, |prompt, _| {
11915 prompt.add_block_ids(block_ids);
11916 });
11917 }
11918
11919 pub(crate) fn breakpoint_at_row(
11920 &self,
11921 row: u32,
11922 window: &mut Window,
11923 cx: &mut Context<Self>,
11924 ) -> Option<(Anchor, Breakpoint)> {
11925 let snapshot = self.snapshot(window, cx);
11926 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11927
11928 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11929 }
11930
11931 pub(crate) fn breakpoint_at_anchor(
11932 &self,
11933 breakpoint_position: Anchor,
11934 snapshot: &EditorSnapshot,
11935 cx: &mut Context<Self>,
11936 ) -> Option<(Anchor, Breakpoint)> {
11937 let buffer = self
11938 .buffer
11939 .read(cx)
11940 .buffer_for_anchor(breakpoint_position, cx)?;
11941
11942 let enclosing_excerpt = breakpoint_position.excerpt_id;
11943 let buffer_snapshot = buffer.read(cx).snapshot();
11944
11945 let row = buffer_snapshot
11946 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11947 .row;
11948
11949 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11950 let anchor_end = snapshot
11951 .buffer_snapshot()
11952 .anchor_after(Point::new(row, line_len));
11953
11954 self.breakpoint_store
11955 .as_ref()?
11956 .read_with(cx, |breakpoint_store, cx| {
11957 breakpoint_store
11958 .breakpoints(
11959 &buffer,
11960 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11961 &buffer_snapshot,
11962 cx,
11963 )
11964 .next()
11965 .and_then(|(bp, _)| {
11966 let breakpoint_row = buffer_snapshot
11967 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11968 .row;
11969
11970 if breakpoint_row == row {
11971 snapshot
11972 .buffer_snapshot()
11973 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11974 .map(|position| (position, bp.bp.clone()))
11975 } else {
11976 None
11977 }
11978 })
11979 })
11980 }
11981
11982 pub fn edit_log_breakpoint(
11983 &mut self,
11984 _: &EditLogBreakpoint,
11985 window: &mut Window,
11986 cx: &mut Context<Self>,
11987 ) {
11988 if self.breakpoint_store.is_none() {
11989 return;
11990 }
11991
11992 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11993 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11994 message: None,
11995 state: BreakpointState::Enabled,
11996 condition: None,
11997 hit_condition: None,
11998 });
11999
12000 self.add_edit_breakpoint_block(
12001 anchor,
12002 &breakpoint,
12003 BreakpointPromptEditAction::Log,
12004 window,
12005 cx,
12006 );
12007 }
12008 }
12009
12010 fn breakpoints_at_cursors(
12011 &self,
12012 window: &mut Window,
12013 cx: &mut Context<Self>,
12014 ) -> Vec<(Anchor, Option<Breakpoint>)> {
12015 let snapshot = self.snapshot(window, cx);
12016 let cursors = self
12017 .selections
12018 .disjoint_anchors_arc()
12019 .iter()
12020 .map(|selection| {
12021 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
12022
12023 let breakpoint_position = self
12024 .breakpoint_at_row(cursor_position.row, window, cx)
12025 .map(|bp| bp.0)
12026 .unwrap_or_else(|| {
12027 snapshot
12028 .display_snapshot
12029 .buffer_snapshot()
12030 .anchor_after(Point::new(cursor_position.row, 0))
12031 });
12032
12033 let breakpoint = self
12034 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
12035 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
12036
12037 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
12038 })
12039 // 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.
12040 .collect::<HashMap<Anchor, _>>();
12041
12042 cursors.into_iter().collect()
12043 }
12044
12045 pub fn enable_breakpoint(
12046 &mut self,
12047 _: &crate::actions::EnableBreakpoint,
12048 window: &mut Window,
12049 cx: &mut Context<Self>,
12050 ) {
12051 if self.breakpoint_store.is_none() {
12052 return;
12053 }
12054
12055 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12056 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
12057 continue;
12058 };
12059 self.edit_breakpoint_at_anchor(
12060 anchor,
12061 breakpoint,
12062 BreakpointEditAction::InvertState,
12063 cx,
12064 );
12065 }
12066 }
12067
12068 pub fn align_selections(
12069 &mut self,
12070 _: &crate::actions::AlignSelections,
12071 window: &mut Window,
12072 cx: &mut Context<Self>,
12073 ) {
12074 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12075
12076 let display_snapshot = self.display_snapshot(cx);
12077
12078 struct CursorData {
12079 anchor: Anchor,
12080 point: Point,
12081 }
12082 let cursor_data: Vec<CursorData> = self
12083 .selections
12084 .disjoint_anchors()
12085 .iter()
12086 .map(|selection| {
12087 let anchor = if selection.reversed {
12088 selection.head()
12089 } else {
12090 selection.tail()
12091 };
12092 CursorData {
12093 anchor: anchor,
12094 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
12095 }
12096 })
12097 .collect();
12098
12099 let rows_anchors_count: Vec<usize> = cursor_data
12100 .iter()
12101 .map(|cursor| cursor.point.row)
12102 .chunk_by(|&row| row)
12103 .into_iter()
12104 .map(|(_, group)| group.count())
12105 .collect();
12106 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
12107 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
12108 let mut edits = Vec::new();
12109
12110 for column_idx in 0..max_columns {
12111 let mut cursor_index = 0;
12112
12113 // Calculate target_column => position that the selections will go
12114 let mut target_column = 0;
12115 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12116 // Skip rows that don't have this column
12117 if column_idx >= *cursor_count {
12118 cursor_index += cursor_count;
12119 continue;
12120 }
12121
12122 let point = &cursor_data[cursor_index + column_idx].point;
12123 let adjusted_column = point.column + rows_column_offset[row_idx];
12124 if adjusted_column > target_column {
12125 target_column = adjusted_column;
12126 }
12127 cursor_index += cursor_count;
12128 }
12129
12130 // Collect edits for this column
12131 cursor_index = 0;
12132 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12133 // Skip rows that don't have this column
12134 if column_idx >= *cursor_count {
12135 cursor_index += *cursor_count;
12136 continue;
12137 }
12138
12139 let point = &cursor_data[cursor_index + column_idx].point;
12140 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
12141 if spaces_needed > 0 {
12142 let anchor = cursor_data[cursor_index + column_idx]
12143 .anchor
12144 .bias_left(&display_snapshot);
12145 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
12146 }
12147 rows_column_offset[row_idx] += spaces_needed;
12148
12149 cursor_index += *cursor_count;
12150 }
12151 }
12152
12153 if !edits.is_empty() {
12154 self.transact(window, cx, |editor, _window, cx| {
12155 editor.edit(edits, cx);
12156 });
12157 }
12158 }
12159
12160 pub fn disable_breakpoint(
12161 &mut self,
12162 _: &crate::actions::DisableBreakpoint,
12163 window: &mut Window,
12164 cx: &mut Context<Self>,
12165 ) {
12166 if self.breakpoint_store.is_none() {
12167 return;
12168 }
12169
12170 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12171 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12172 continue;
12173 };
12174 self.edit_breakpoint_at_anchor(
12175 anchor,
12176 breakpoint,
12177 BreakpointEditAction::InvertState,
12178 cx,
12179 );
12180 }
12181 }
12182
12183 pub fn toggle_breakpoint(
12184 &mut self,
12185 _: &crate::actions::ToggleBreakpoint,
12186 window: &mut Window,
12187 cx: &mut Context<Self>,
12188 ) {
12189 if self.breakpoint_store.is_none() {
12190 return;
12191 }
12192
12193 let snapshot = self.snapshot(window, cx);
12194 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12195 if self.gutter_breakpoint_indicator.0.is_some() {
12196 let display_row = anchor
12197 .to_point(snapshot.buffer_snapshot())
12198 .to_display_point(&snapshot.display_snapshot)
12199 .row();
12200 self.update_breakpoint_collision_on_toggle(
12201 display_row,
12202 &BreakpointEditAction::Toggle,
12203 );
12204 }
12205
12206 if let Some(breakpoint) = breakpoint {
12207 self.edit_breakpoint_at_anchor(
12208 anchor,
12209 breakpoint,
12210 BreakpointEditAction::Toggle,
12211 cx,
12212 );
12213 } else {
12214 self.edit_breakpoint_at_anchor(
12215 anchor,
12216 Breakpoint::new_standard(),
12217 BreakpointEditAction::Toggle,
12218 cx,
12219 );
12220 }
12221 }
12222 }
12223
12224 fn update_breakpoint_collision_on_toggle(
12225 &mut self,
12226 display_row: DisplayRow,
12227 edit_action: &BreakpointEditAction,
12228 ) {
12229 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12230 if breakpoint_indicator.display_row == display_row
12231 && matches!(edit_action, BreakpointEditAction::Toggle)
12232 {
12233 breakpoint_indicator.collides_with_existing_breakpoint =
12234 !breakpoint_indicator.collides_with_existing_breakpoint;
12235 }
12236 }
12237 }
12238
12239 pub fn edit_breakpoint_at_anchor(
12240 &mut self,
12241 breakpoint_position: Anchor,
12242 breakpoint: Breakpoint,
12243 edit_action: BreakpointEditAction,
12244 cx: &mut Context<Self>,
12245 ) {
12246 let Some(breakpoint_store) = &self.breakpoint_store else {
12247 return;
12248 };
12249
12250 let Some(buffer) = self
12251 .buffer
12252 .read(cx)
12253 .buffer_for_anchor(breakpoint_position, cx)
12254 else {
12255 return;
12256 };
12257
12258 breakpoint_store.update(cx, |breakpoint_store, cx| {
12259 breakpoint_store.toggle_breakpoint(
12260 buffer,
12261 BreakpointWithPosition {
12262 position: breakpoint_position.text_anchor,
12263 bp: breakpoint,
12264 },
12265 edit_action,
12266 cx,
12267 );
12268 });
12269
12270 cx.notify();
12271 }
12272
12273 #[cfg(any(test, feature = "test-support"))]
12274 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12275 self.breakpoint_store.clone()
12276 }
12277
12278 pub fn prepare_restore_change(
12279 &self,
12280 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12281 hunk: &MultiBufferDiffHunk,
12282 cx: &mut App,
12283 ) -> Option<()> {
12284 if hunk.is_created_file() {
12285 return None;
12286 }
12287 let multi_buffer = self.buffer.read(cx);
12288 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
12289 let diff_snapshot = multi_buffer_snapshot.diff_for_buffer_id(hunk.buffer_id)?;
12290 let original_text = diff_snapshot
12291 .base_text()
12292 .as_rope()
12293 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12294 let buffer = multi_buffer.buffer(hunk.buffer_id)?;
12295 let buffer = buffer.read(cx);
12296 let buffer_snapshot = buffer.snapshot();
12297 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12298 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12299 probe
12300 .0
12301 .start
12302 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12303 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12304 }) {
12305 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12306 Some(())
12307 } else {
12308 None
12309 }
12310 }
12311
12312 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12313 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12314 }
12315
12316 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12317 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12318 }
12319
12320 pub fn rotate_selections_forward(
12321 &mut self,
12322 _: &RotateSelectionsForward,
12323 window: &mut Window,
12324 cx: &mut Context<Self>,
12325 ) {
12326 self.rotate_selections(window, cx, false)
12327 }
12328
12329 pub fn rotate_selections_backward(
12330 &mut self,
12331 _: &RotateSelectionsBackward,
12332 window: &mut Window,
12333 cx: &mut Context<Self>,
12334 ) {
12335 self.rotate_selections(window, cx, true)
12336 }
12337
12338 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12339 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12340 let display_snapshot = self.display_snapshot(cx);
12341 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12342
12343 if selections.len() < 2 {
12344 return;
12345 }
12346
12347 let (edits, new_selections) = {
12348 let buffer = self.buffer.read(cx).read(cx);
12349 let has_selections = selections.iter().any(|s| !s.is_empty());
12350 if has_selections {
12351 let mut selected_texts: Vec<String> = selections
12352 .iter()
12353 .map(|selection| {
12354 buffer
12355 .text_for_range(selection.start..selection.end)
12356 .collect()
12357 })
12358 .collect();
12359
12360 if reverse {
12361 selected_texts.rotate_left(1);
12362 } else {
12363 selected_texts.rotate_right(1);
12364 }
12365
12366 let mut offset_delta: i64 = 0;
12367 let mut new_selections = Vec::new();
12368 let edits: Vec<_> = selections
12369 .iter()
12370 .zip(selected_texts.iter())
12371 .map(|(selection, new_text)| {
12372 let old_len = (selection.end.0 - selection.start.0) as i64;
12373 let new_len = new_text.len() as i64;
12374 let adjusted_start =
12375 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12376 let adjusted_end =
12377 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12378
12379 new_selections.push(Selection {
12380 id: selection.id,
12381 start: adjusted_start,
12382 end: adjusted_end,
12383 reversed: selection.reversed,
12384 goal: selection.goal,
12385 });
12386
12387 offset_delta += new_len - old_len;
12388 (selection.start..selection.end, new_text.clone())
12389 })
12390 .collect();
12391 (edits, new_selections)
12392 } else {
12393 let mut all_rows: Vec<u32> = selections
12394 .iter()
12395 .map(|selection| buffer.offset_to_point(selection.start).row)
12396 .collect();
12397 all_rows.sort_unstable();
12398 all_rows.dedup();
12399
12400 if all_rows.len() < 2 {
12401 return;
12402 }
12403
12404 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12405 .iter()
12406 .map(|&row| {
12407 let start = Point::new(row, 0);
12408 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12409 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12410 })
12411 .collect();
12412
12413 let mut line_texts: Vec<String> = line_ranges
12414 .iter()
12415 .map(|range| buffer.text_for_range(range.clone()).collect())
12416 .collect();
12417
12418 if reverse {
12419 line_texts.rotate_left(1);
12420 } else {
12421 line_texts.rotate_right(1);
12422 }
12423
12424 let edits = line_ranges
12425 .iter()
12426 .zip(line_texts.iter())
12427 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12428 .collect();
12429
12430 let num_rows = all_rows.len();
12431 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12432 .iter()
12433 .enumerate()
12434 .map(|(i, &row)| (row, i))
12435 .collect();
12436
12437 // Compute new line start offsets after rotation (handles CRLF)
12438 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12439 let first_line_start = line_ranges[0].start.0;
12440 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12441 for text in line_texts.iter().take(num_rows - 1) {
12442 let prev_start = *new_line_starts.last().unwrap();
12443 new_line_starts.push(prev_start + text.len() + newline_len);
12444 }
12445
12446 let new_selections = selections
12447 .iter()
12448 .map(|selection| {
12449 let point = buffer.offset_to_point(selection.start);
12450 let old_index = row_to_index[&point.row];
12451 let new_index = if reverse {
12452 (old_index + num_rows - 1) % num_rows
12453 } else {
12454 (old_index + 1) % num_rows
12455 };
12456 let new_offset =
12457 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12458 Selection {
12459 id: selection.id,
12460 start: new_offset,
12461 end: new_offset,
12462 reversed: selection.reversed,
12463 goal: selection.goal,
12464 }
12465 })
12466 .collect();
12467
12468 (edits, new_selections)
12469 }
12470 };
12471
12472 self.transact(window, cx, |this, window, cx| {
12473 this.buffer.update(cx, |buffer, cx| {
12474 buffer.edit(edits, None, cx);
12475 });
12476 this.change_selections(Default::default(), window, cx, |s| {
12477 s.select(new_selections);
12478 });
12479 });
12480 }
12481
12482 fn manipulate_lines<M>(
12483 &mut self,
12484 window: &mut Window,
12485 cx: &mut Context<Self>,
12486 mut manipulate: M,
12487 ) where
12488 M: FnMut(&str) -> LineManipulationResult,
12489 {
12490 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12491
12492 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12493 let buffer = self.buffer.read(cx).snapshot(cx);
12494
12495 let mut edits = Vec::new();
12496
12497 let selections = self.selections.all::<Point>(&display_map);
12498 let mut selections = selections.iter().peekable();
12499 let mut contiguous_row_selections = Vec::new();
12500 let mut new_selections = Vec::new();
12501 let mut added_lines = 0;
12502 let mut removed_lines = 0;
12503
12504 while let Some(selection) = selections.next() {
12505 let (start_row, end_row) = consume_contiguous_rows(
12506 &mut contiguous_row_selections,
12507 selection,
12508 &display_map,
12509 &mut selections,
12510 );
12511
12512 let start_point = Point::new(start_row.0, 0);
12513 let end_point = Point::new(
12514 end_row.previous_row().0,
12515 buffer.line_len(end_row.previous_row()),
12516 );
12517 let text = buffer
12518 .text_for_range(start_point..end_point)
12519 .collect::<String>();
12520
12521 let LineManipulationResult {
12522 new_text,
12523 line_count_before,
12524 line_count_after,
12525 } = manipulate(&text);
12526
12527 edits.push((start_point..end_point, new_text));
12528
12529 // Selections must change based on added and removed line count
12530 let start_row =
12531 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12532 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12533 new_selections.push(Selection {
12534 id: selection.id,
12535 start: start_row,
12536 end: end_row,
12537 goal: SelectionGoal::None,
12538 reversed: selection.reversed,
12539 });
12540
12541 if line_count_after > line_count_before {
12542 added_lines += line_count_after - line_count_before;
12543 } else if line_count_before > line_count_after {
12544 removed_lines += line_count_before - line_count_after;
12545 }
12546 }
12547
12548 self.transact(window, cx, |this, window, cx| {
12549 let buffer = this.buffer.update(cx, |buffer, cx| {
12550 buffer.edit(edits, None, cx);
12551 buffer.snapshot(cx)
12552 });
12553
12554 // Recalculate offsets on newly edited buffer
12555 let new_selections = new_selections
12556 .iter()
12557 .map(|s| {
12558 let start_point = Point::new(s.start.0, 0);
12559 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12560 Selection {
12561 id: s.id,
12562 start: buffer.point_to_offset(start_point),
12563 end: buffer.point_to_offset(end_point),
12564 goal: s.goal,
12565 reversed: s.reversed,
12566 }
12567 })
12568 .collect();
12569
12570 this.change_selections(Default::default(), window, cx, |s| {
12571 s.select(new_selections);
12572 });
12573
12574 this.request_autoscroll(Autoscroll::fit(), cx);
12575 });
12576 }
12577
12578 fn manipulate_immutable_lines<Fn>(
12579 &mut self,
12580 window: &mut Window,
12581 cx: &mut Context<Self>,
12582 mut callback: Fn,
12583 ) where
12584 Fn: FnMut(&mut Vec<&str>),
12585 {
12586 self.manipulate_lines(window, cx, |text| {
12587 let mut lines: Vec<&str> = text.split('\n').collect();
12588 let line_count_before = lines.len();
12589
12590 callback(&mut lines);
12591
12592 LineManipulationResult {
12593 new_text: lines.join("\n"),
12594 line_count_before,
12595 line_count_after: lines.len(),
12596 }
12597 });
12598 }
12599
12600 fn manipulate_mutable_lines<Fn>(
12601 &mut self,
12602 window: &mut Window,
12603 cx: &mut Context<Self>,
12604 mut callback: Fn,
12605 ) where
12606 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12607 {
12608 self.manipulate_lines(window, cx, |text| {
12609 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12610 let line_count_before = lines.len();
12611
12612 callback(&mut lines);
12613
12614 LineManipulationResult {
12615 new_text: lines.join("\n"),
12616 line_count_before,
12617 line_count_after: lines.len(),
12618 }
12619 });
12620 }
12621
12622 pub fn convert_indentation_to_spaces(
12623 &mut self,
12624 _: &ConvertIndentationToSpaces,
12625 window: &mut Window,
12626 cx: &mut Context<Self>,
12627 ) {
12628 let settings = self.buffer.read(cx).language_settings(cx);
12629 let tab_size = settings.tab_size.get() as usize;
12630
12631 self.manipulate_mutable_lines(window, cx, |lines| {
12632 // Allocates a reasonably sized scratch buffer once for the whole loop
12633 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12634 // Avoids recomputing spaces that could be inserted many times
12635 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12636 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12637 .collect();
12638
12639 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12640 let mut chars = line.as_ref().chars();
12641 let mut col = 0;
12642 let mut changed = false;
12643
12644 for ch in chars.by_ref() {
12645 match ch {
12646 ' ' => {
12647 reindented_line.push(' ');
12648 col += 1;
12649 }
12650 '\t' => {
12651 // \t are converted to spaces depending on the current column
12652 let spaces_len = tab_size - (col % tab_size);
12653 reindented_line.extend(&space_cache[spaces_len - 1]);
12654 col += spaces_len;
12655 changed = true;
12656 }
12657 _ => {
12658 // If we dont append before break, the character is consumed
12659 reindented_line.push(ch);
12660 break;
12661 }
12662 }
12663 }
12664
12665 if !changed {
12666 reindented_line.clear();
12667 continue;
12668 }
12669 // Append the rest of the line and replace old reference with new one
12670 reindented_line.extend(chars);
12671 *line = Cow::Owned(reindented_line.clone());
12672 reindented_line.clear();
12673 }
12674 });
12675 }
12676
12677 pub fn convert_indentation_to_tabs(
12678 &mut self,
12679 _: &ConvertIndentationToTabs,
12680 window: &mut Window,
12681 cx: &mut Context<Self>,
12682 ) {
12683 let settings = self.buffer.read(cx).language_settings(cx);
12684 let tab_size = settings.tab_size.get() as usize;
12685
12686 self.manipulate_mutable_lines(window, cx, |lines| {
12687 // Allocates a reasonably sized buffer once for the whole loop
12688 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12689 // Avoids recomputing spaces that could be inserted many times
12690 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12691 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12692 .collect();
12693
12694 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12695 let mut chars = line.chars();
12696 let mut spaces_count = 0;
12697 let mut first_non_indent_char = None;
12698 let mut changed = false;
12699
12700 for ch in chars.by_ref() {
12701 match ch {
12702 ' ' => {
12703 // Keep track of spaces. Append \t when we reach tab_size
12704 spaces_count += 1;
12705 changed = true;
12706 if spaces_count == tab_size {
12707 reindented_line.push('\t');
12708 spaces_count = 0;
12709 }
12710 }
12711 '\t' => {
12712 reindented_line.push('\t');
12713 spaces_count = 0;
12714 }
12715 _ => {
12716 // Dont append it yet, we might have remaining spaces
12717 first_non_indent_char = Some(ch);
12718 break;
12719 }
12720 }
12721 }
12722
12723 if !changed {
12724 reindented_line.clear();
12725 continue;
12726 }
12727 // Remaining spaces that didn't make a full tab stop
12728 if spaces_count > 0 {
12729 reindented_line.extend(&space_cache[spaces_count - 1]);
12730 }
12731 // If we consume an extra character that was not indentation, add it back
12732 if let Some(extra_char) = first_non_indent_char {
12733 reindented_line.push(extra_char);
12734 }
12735 // Append the rest of the line and replace old reference with new one
12736 reindented_line.extend(chars);
12737 *line = Cow::Owned(reindented_line.clone());
12738 reindented_line.clear();
12739 }
12740 });
12741 }
12742
12743 pub fn convert_to_upper_case(
12744 &mut self,
12745 _: &ConvertToUpperCase,
12746 window: &mut Window,
12747 cx: &mut Context<Self>,
12748 ) {
12749 self.manipulate_text(window, cx, |text| text.to_uppercase())
12750 }
12751
12752 pub fn convert_to_lower_case(
12753 &mut self,
12754 _: &ConvertToLowerCase,
12755 window: &mut Window,
12756 cx: &mut Context<Self>,
12757 ) {
12758 self.manipulate_text(window, cx, |text| text.to_lowercase())
12759 }
12760
12761 pub fn convert_to_title_case(
12762 &mut self,
12763 _: &ConvertToTitleCase,
12764 window: &mut Window,
12765 cx: &mut Context<Self>,
12766 ) {
12767 self.manipulate_text(window, cx, |text| {
12768 Self::convert_text_case(text, Case::Title)
12769 })
12770 }
12771
12772 pub fn convert_to_snake_case(
12773 &mut self,
12774 _: &ConvertToSnakeCase,
12775 window: &mut Window,
12776 cx: &mut Context<Self>,
12777 ) {
12778 self.manipulate_text(window, cx, |text| {
12779 Self::convert_text_case(text, Case::Snake)
12780 })
12781 }
12782
12783 pub fn convert_to_kebab_case(
12784 &mut self,
12785 _: &ConvertToKebabCase,
12786 window: &mut Window,
12787 cx: &mut Context<Self>,
12788 ) {
12789 self.manipulate_text(window, cx, |text| {
12790 Self::convert_text_case(text, Case::Kebab)
12791 })
12792 }
12793
12794 pub fn convert_to_upper_camel_case(
12795 &mut self,
12796 _: &ConvertToUpperCamelCase,
12797 window: &mut Window,
12798 cx: &mut Context<Self>,
12799 ) {
12800 self.manipulate_text(window, cx, |text| {
12801 Self::convert_text_case(text, Case::UpperCamel)
12802 })
12803 }
12804
12805 pub fn convert_to_lower_camel_case(
12806 &mut self,
12807 _: &ConvertToLowerCamelCase,
12808 window: &mut Window,
12809 cx: &mut Context<Self>,
12810 ) {
12811 self.manipulate_text(window, cx, |text| {
12812 Self::convert_text_case(text, Case::Camel)
12813 })
12814 }
12815
12816 pub fn convert_to_opposite_case(
12817 &mut self,
12818 _: &ConvertToOppositeCase,
12819 window: &mut Window,
12820 cx: &mut Context<Self>,
12821 ) {
12822 self.manipulate_text(window, cx, |text| {
12823 text.chars()
12824 .fold(String::with_capacity(text.len()), |mut t, c| {
12825 if c.is_uppercase() {
12826 t.extend(c.to_lowercase());
12827 } else {
12828 t.extend(c.to_uppercase());
12829 }
12830 t
12831 })
12832 })
12833 }
12834
12835 pub fn convert_to_sentence_case(
12836 &mut self,
12837 _: &ConvertToSentenceCase,
12838 window: &mut Window,
12839 cx: &mut Context<Self>,
12840 ) {
12841 self.manipulate_text(window, cx, |text| {
12842 Self::convert_text_case(text, Case::Sentence)
12843 })
12844 }
12845
12846 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12847 self.manipulate_text(window, cx, |text| {
12848 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12849 if has_upper_case_characters {
12850 text.to_lowercase()
12851 } else {
12852 text.to_uppercase()
12853 }
12854 })
12855 }
12856
12857 pub fn convert_to_rot13(
12858 &mut self,
12859 _: &ConvertToRot13,
12860 window: &mut Window,
12861 cx: &mut Context<Self>,
12862 ) {
12863 self.manipulate_text(window, cx, |text| {
12864 text.chars()
12865 .map(|c| match c {
12866 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12867 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12868 _ => c,
12869 })
12870 .collect()
12871 })
12872 }
12873
12874 fn convert_text_case(text: &str, case: Case) -> String {
12875 text.lines()
12876 .map(|line| {
12877 let trimmed_start = line.trim_start();
12878 let leading = &line[..line.len() - trimmed_start.len()];
12879 let trimmed = trimmed_start.trim_end();
12880 let trailing = &trimmed_start[trimmed.len()..];
12881 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12882 })
12883 .join("\n")
12884 }
12885
12886 pub fn convert_to_rot47(
12887 &mut self,
12888 _: &ConvertToRot47,
12889 window: &mut Window,
12890 cx: &mut Context<Self>,
12891 ) {
12892 self.manipulate_text(window, cx, |text| {
12893 text.chars()
12894 .map(|c| {
12895 let code_point = c as u32;
12896 if code_point >= 33 && code_point <= 126 {
12897 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12898 }
12899 c
12900 })
12901 .collect()
12902 })
12903 }
12904
12905 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12906 where
12907 Fn: FnMut(&str) -> String,
12908 {
12909 let buffer = self.buffer.read(cx).snapshot(cx);
12910
12911 let mut new_selections = Vec::new();
12912 let mut edits = Vec::new();
12913 let mut selection_adjustment = 0isize;
12914
12915 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12916 let selection_is_empty = selection.is_empty();
12917
12918 let (start, end) = if selection_is_empty {
12919 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12920 (word_range.start, word_range.end)
12921 } else {
12922 (
12923 buffer.point_to_offset(selection.start),
12924 buffer.point_to_offset(selection.end),
12925 )
12926 };
12927
12928 let text = buffer.text_for_range(start..end).collect::<String>();
12929 let old_length = text.len() as isize;
12930 let text = callback(&text);
12931
12932 new_selections.push(Selection {
12933 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12934 end: MultiBufferOffset(
12935 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12936 ),
12937 goal: SelectionGoal::None,
12938 id: selection.id,
12939 reversed: selection.reversed,
12940 });
12941
12942 selection_adjustment += old_length - text.len() as isize;
12943
12944 edits.push((start..end, text));
12945 }
12946
12947 self.transact(window, cx, |this, window, cx| {
12948 this.buffer.update(cx, |buffer, cx| {
12949 buffer.edit(edits, None, cx);
12950 });
12951
12952 this.change_selections(Default::default(), window, cx, |s| {
12953 s.select(new_selections);
12954 });
12955
12956 this.request_autoscroll(Autoscroll::fit(), cx);
12957 });
12958 }
12959
12960 pub fn move_selection_on_drop(
12961 &mut self,
12962 selection: &Selection<Anchor>,
12963 target: DisplayPoint,
12964 is_cut: bool,
12965 window: &mut Window,
12966 cx: &mut Context<Self>,
12967 ) {
12968 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12969 let buffer = display_map.buffer_snapshot();
12970 let mut edits = Vec::new();
12971 let insert_point = display_map
12972 .clip_point(target, Bias::Left)
12973 .to_point(&display_map);
12974 let text = buffer
12975 .text_for_range(selection.start..selection.end)
12976 .collect::<String>();
12977 if is_cut {
12978 edits.push(((selection.start..selection.end), String::new()));
12979 }
12980 let insert_anchor = buffer.anchor_before(insert_point);
12981 edits.push(((insert_anchor..insert_anchor), text));
12982 let last_edit_start = insert_anchor.bias_left(buffer);
12983 let last_edit_end = insert_anchor.bias_right(buffer);
12984 self.transact(window, cx, |this, window, cx| {
12985 this.buffer.update(cx, |buffer, cx| {
12986 buffer.edit(edits, None, cx);
12987 });
12988 this.change_selections(Default::default(), window, cx, |s| {
12989 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12990 });
12991 });
12992 }
12993
12994 pub fn clear_selection_drag_state(&mut self) {
12995 self.selection_drag_state = SelectionDragState::None;
12996 }
12997
12998 pub fn duplicate(
12999 &mut self,
13000 upwards: bool,
13001 whole_lines: bool,
13002 window: &mut Window,
13003 cx: &mut Context<Self>,
13004 ) {
13005 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13006
13007 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13008 let buffer = display_map.buffer_snapshot();
13009 let selections = self.selections.all::<Point>(&display_map);
13010
13011 let mut edits = Vec::new();
13012 let mut selections_iter = selections.iter().peekable();
13013 while let Some(selection) = selections_iter.next() {
13014 let mut rows = selection.spanned_rows(false, &display_map);
13015 // duplicate line-wise
13016 if whole_lines || selection.start == selection.end {
13017 // Avoid duplicating the same lines twice.
13018 while let Some(next_selection) = selections_iter.peek() {
13019 let next_rows = next_selection.spanned_rows(false, &display_map);
13020 if next_rows.start < rows.end {
13021 rows.end = next_rows.end;
13022 selections_iter.next().unwrap();
13023 } else {
13024 break;
13025 }
13026 }
13027
13028 // Copy the text from the selected row region and splice it either at the start
13029 // or end of the region.
13030 let start = Point::new(rows.start.0, 0);
13031 let end = Point::new(
13032 rows.end.previous_row().0,
13033 buffer.line_len(rows.end.previous_row()),
13034 );
13035
13036 let mut text = buffer.text_for_range(start..end).collect::<String>();
13037
13038 let insert_location = if upwards {
13039 // When duplicating upward, we need to insert before the current line.
13040 // If we're on the last line and it doesn't end with a newline,
13041 // we need to add a newline before the duplicated content.
13042 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
13043 && buffer.max_point().column > 0
13044 && !text.ends_with('\n');
13045
13046 if needs_leading_newline {
13047 text.insert(0, '\n');
13048 end
13049 } else {
13050 text.push('\n');
13051 Point::new(rows.start.0, 0)
13052 }
13053 } else {
13054 text.push('\n');
13055 start
13056 };
13057 edits.push((insert_location..insert_location, text));
13058 } else {
13059 // duplicate character-wise
13060 let start = selection.start;
13061 let end = selection.end;
13062 let text = buffer.text_for_range(start..end).collect::<String>();
13063 edits.push((selection.end..selection.end, text));
13064 }
13065 }
13066
13067 self.transact(window, cx, |this, window, cx| {
13068 this.buffer.update(cx, |buffer, cx| {
13069 buffer.edit(edits, None, cx);
13070 });
13071
13072 // When duplicating upward with whole lines, move the cursor to the duplicated line
13073 if upwards && whole_lines {
13074 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
13075
13076 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13077 let mut new_ranges = Vec::new();
13078 let selections = s.all::<Point>(&display_map);
13079 let mut selections_iter = selections.iter().peekable();
13080
13081 while let Some(first_selection) = selections_iter.next() {
13082 // Group contiguous selections together to find the total row span
13083 let mut group_selections = vec![first_selection];
13084 let mut rows = first_selection.spanned_rows(false, &display_map);
13085
13086 while let Some(next_selection) = selections_iter.peek() {
13087 let next_rows = next_selection.spanned_rows(false, &display_map);
13088 if next_rows.start < rows.end {
13089 rows.end = next_rows.end;
13090 group_selections.push(selections_iter.next().unwrap());
13091 } else {
13092 break;
13093 }
13094 }
13095
13096 let row_count = rows.end.0 - rows.start.0;
13097
13098 // Move all selections in this group up by the total number of duplicated rows
13099 for selection in group_selections {
13100 let new_start = Point::new(
13101 selection.start.row.saturating_sub(row_count),
13102 selection.start.column,
13103 );
13104
13105 let new_end = Point::new(
13106 selection.end.row.saturating_sub(row_count),
13107 selection.end.column,
13108 );
13109
13110 new_ranges.push(new_start..new_end);
13111 }
13112 }
13113
13114 s.select_ranges(new_ranges);
13115 });
13116 }
13117
13118 this.request_autoscroll(Autoscroll::fit(), cx);
13119 });
13120 }
13121
13122 pub fn duplicate_line_up(
13123 &mut self,
13124 _: &DuplicateLineUp,
13125 window: &mut Window,
13126 cx: &mut Context<Self>,
13127 ) {
13128 self.duplicate(true, true, window, cx);
13129 }
13130
13131 pub fn duplicate_line_down(
13132 &mut self,
13133 _: &DuplicateLineDown,
13134 window: &mut Window,
13135 cx: &mut Context<Self>,
13136 ) {
13137 self.duplicate(false, true, window, cx);
13138 }
13139
13140 pub fn duplicate_selection(
13141 &mut self,
13142 _: &DuplicateSelection,
13143 window: &mut Window,
13144 cx: &mut Context<Self>,
13145 ) {
13146 self.duplicate(false, false, window, cx);
13147 }
13148
13149 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13150 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13151 if self.mode.is_single_line() {
13152 cx.propagate();
13153 return;
13154 }
13155
13156 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13157 let buffer = self.buffer.read(cx).snapshot(cx);
13158
13159 let mut edits = Vec::new();
13160 let mut unfold_ranges = Vec::new();
13161 let mut refold_creases = Vec::new();
13162
13163 let selections = self.selections.all::<Point>(&display_map);
13164 let mut selections = selections.iter().peekable();
13165 let mut contiguous_row_selections = Vec::new();
13166 let mut new_selections = Vec::new();
13167
13168 while let Some(selection) = selections.next() {
13169 // Find all the selections that span a contiguous row range
13170 let (start_row, end_row) = consume_contiguous_rows(
13171 &mut contiguous_row_selections,
13172 selection,
13173 &display_map,
13174 &mut selections,
13175 );
13176
13177 // Move the text spanned by the row range to be before the line preceding the row range
13178 if start_row.0 > 0 {
13179 let range_to_move = Point::new(
13180 start_row.previous_row().0,
13181 buffer.line_len(start_row.previous_row()),
13182 )
13183 ..Point::new(
13184 end_row.previous_row().0,
13185 buffer.line_len(end_row.previous_row()),
13186 );
13187 let insertion_point = display_map
13188 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13189 .0;
13190
13191 // Don't move lines across excerpts
13192 if buffer
13193 .excerpt_containing(insertion_point..range_to_move.end)
13194 .is_some()
13195 {
13196 let text = buffer
13197 .text_for_range(range_to_move.clone())
13198 .flat_map(|s| s.chars())
13199 .skip(1)
13200 .chain(['\n'])
13201 .collect::<String>();
13202
13203 edits.push((
13204 buffer.anchor_after(range_to_move.start)
13205 ..buffer.anchor_before(range_to_move.end),
13206 String::new(),
13207 ));
13208 let insertion_anchor = buffer.anchor_after(insertion_point);
13209 edits.push((insertion_anchor..insertion_anchor, text));
13210
13211 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13212
13213 // Move selections up
13214 new_selections.extend(contiguous_row_selections.drain(..).map(
13215 |mut selection| {
13216 selection.start.row -= row_delta;
13217 selection.end.row -= row_delta;
13218 selection
13219 },
13220 ));
13221
13222 // Move folds up
13223 unfold_ranges.push(range_to_move.clone());
13224 for fold in display_map.folds_in_range(
13225 buffer.anchor_before(range_to_move.start)
13226 ..buffer.anchor_after(range_to_move.end),
13227 ) {
13228 let mut start = fold.range.start.to_point(&buffer);
13229 let mut end = fold.range.end.to_point(&buffer);
13230 start.row -= row_delta;
13231 end.row -= row_delta;
13232 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13233 }
13234 }
13235 }
13236
13237 // If we didn't move line(s), preserve the existing selections
13238 new_selections.append(&mut contiguous_row_selections);
13239 }
13240
13241 self.transact(window, cx, |this, window, cx| {
13242 this.unfold_ranges(&unfold_ranges, true, true, cx);
13243 this.buffer.update(cx, |buffer, cx| {
13244 for (range, text) in edits {
13245 buffer.edit([(range, text)], None, cx);
13246 }
13247 });
13248 this.fold_creases(refold_creases, true, window, cx);
13249 this.change_selections(Default::default(), window, cx, |s| {
13250 s.select(new_selections);
13251 })
13252 });
13253 }
13254
13255 pub fn move_line_down(
13256 &mut self,
13257 _: &MoveLineDown,
13258 window: &mut Window,
13259 cx: &mut Context<Self>,
13260 ) {
13261 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13262 if self.mode.is_single_line() {
13263 cx.propagate();
13264 return;
13265 }
13266
13267 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13268 let buffer = self.buffer.read(cx).snapshot(cx);
13269
13270 let mut edits = Vec::new();
13271 let mut unfold_ranges = Vec::new();
13272 let mut refold_creases = Vec::new();
13273
13274 let selections = self.selections.all::<Point>(&display_map);
13275 let mut selections = selections.iter().peekable();
13276 let mut contiguous_row_selections = Vec::new();
13277 let mut new_selections = Vec::new();
13278
13279 while let Some(selection) = selections.next() {
13280 // Find all the selections that span a contiguous row range
13281 let (start_row, end_row) = consume_contiguous_rows(
13282 &mut contiguous_row_selections,
13283 selection,
13284 &display_map,
13285 &mut selections,
13286 );
13287
13288 // Move the text spanned by the row range to be after the last line of the row range
13289 if end_row.0 <= buffer.max_point().row {
13290 let range_to_move =
13291 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13292 let insertion_point = display_map
13293 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13294 .0;
13295
13296 // Don't move lines across excerpt boundaries
13297 if buffer
13298 .excerpt_containing(range_to_move.start..insertion_point)
13299 .is_some()
13300 {
13301 let mut text = String::from("\n");
13302 text.extend(buffer.text_for_range(range_to_move.clone()));
13303 text.pop(); // Drop trailing newline
13304 edits.push((
13305 buffer.anchor_after(range_to_move.start)
13306 ..buffer.anchor_before(range_to_move.end),
13307 String::new(),
13308 ));
13309 let insertion_anchor = buffer.anchor_after(insertion_point);
13310 edits.push((insertion_anchor..insertion_anchor, text));
13311
13312 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13313
13314 // Move selections down
13315 new_selections.extend(contiguous_row_selections.drain(..).map(
13316 |mut selection| {
13317 selection.start.row += row_delta;
13318 selection.end.row += row_delta;
13319 selection
13320 },
13321 ));
13322
13323 // Move folds down
13324 unfold_ranges.push(range_to_move.clone());
13325 for fold in display_map.folds_in_range(
13326 buffer.anchor_before(range_to_move.start)
13327 ..buffer.anchor_after(range_to_move.end),
13328 ) {
13329 let mut start = fold.range.start.to_point(&buffer);
13330 let mut end = fold.range.end.to_point(&buffer);
13331 start.row += row_delta;
13332 end.row += row_delta;
13333 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13334 }
13335 }
13336 }
13337
13338 // If we didn't move line(s), preserve the existing selections
13339 new_selections.append(&mut contiguous_row_selections);
13340 }
13341
13342 self.transact(window, cx, |this, window, cx| {
13343 this.unfold_ranges(&unfold_ranges, true, true, cx);
13344 this.buffer.update(cx, |buffer, cx| {
13345 for (range, text) in edits {
13346 buffer.edit([(range, text)], None, cx);
13347 }
13348 });
13349 this.fold_creases(refold_creases, true, window, cx);
13350 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13351 });
13352 }
13353
13354 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13355 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13356 let text_layout_details = &self.text_layout_details(window, cx);
13357 self.transact(window, cx, |this, window, cx| {
13358 let edits = this.change_selections(Default::default(), window, cx, |s| {
13359 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13360 s.move_with(&mut |display_map, selection| {
13361 if !selection.is_empty() {
13362 return;
13363 }
13364
13365 let mut head = selection.head();
13366 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13367 if head.column() == display_map.line_len(head.row()) {
13368 transpose_offset = display_map
13369 .buffer_snapshot()
13370 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13371 }
13372
13373 if transpose_offset == MultiBufferOffset(0) {
13374 return;
13375 }
13376
13377 *head.column_mut() += 1;
13378 head = display_map.clip_point(head, Bias::Right);
13379 let goal = SelectionGoal::HorizontalPosition(
13380 display_map
13381 .x_for_display_point(head, text_layout_details)
13382 .into(),
13383 );
13384 selection.collapse_to(head, goal);
13385
13386 let transpose_start = display_map
13387 .buffer_snapshot()
13388 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13389 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13390 let transpose_end = display_map
13391 .buffer_snapshot()
13392 .clip_offset(transpose_offset + 1usize, Bias::Right);
13393 if let Some(ch) = display_map
13394 .buffer_snapshot()
13395 .chars_at(transpose_start)
13396 .next()
13397 {
13398 edits.push((transpose_start..transpose_offset, String::new()));
13399 edits.push((transpose_end..transpose_end, ch.to_string()));
13400 }
13401 }
13402 });
13403 edits
13404 });
13405 this.buffer
13406 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13407 let selections = this
13408 .selections
13409 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13410 this.change_selections(Default::default(), window, cx, |s| {
13411 s.select(selections);
13412 });
13413 });
13414 }
13415
13416 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13417 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13418 if self.mode.is_single_line() {
13419 cx.propagate();
13420 return;
13421 }
13422
13423 self.rewrap_impl(RewrapOptions::default(), cx)
13424 }
13425
13426 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13427 let buffer = self.buffer.read(cx).snapshot(cx);
13428 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13429
13430 #[derive(Clone, Debug, PartialEq)]
13431 enum CommentFormat {
13432 /// single line comment, with prefix for line
13433 Line(String),
13434 /// single line within a block comment, with prefix for line
13435 BlockLine(String),
13436 /// a single line of a block comment that includes the initial delimiter
13437 BlockCommentWithStart(BlockCommentConfig),
13438 /// a single line of a block comment that includes the ending delimiter
13439 BlockCommentWithEnd(BlockCommentConfig),
13440 }
13441
13442 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13443 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13444 let language_settings = buffer.language_settings_at(selection.head(), cx);
13445 let language_scope = buffer.language_scope_at(selection.head());
13446
13447 let indent_and_prefix_for_row =
13448 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13449 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13450 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13451 &language_scope
13452 {
13453 let indent_end = Point::new(row, indent.len);
13454 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13455 let line_text_after_indent = buffer
13456 .text_for_range(indent_end..line_end)
13457 .collect::<String>();
13458
13459 let is_within_comment_override = buffer
13460 .language_scope_at(indent_end)
13461 .is_some_and(|scope| scope.override_name() == Some("comment"));
13462 let comment_delimiters = if is_within_comment_override {
13463 // we are within a comment syntax node, but we don't
13464 // yet know what kind of comment: block, doc or line
13465 match (
13466 language_scope.documentation_comment(),
13467 language_scope.block_comment(),
13468 ) {
13469 (Some(config), _) | (_, Some(config))
13470 if buffer.contains_str_at(indent_end, &config.start) =>
13471 {
13472 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13473 }
13474 (Some(config), _) | (_, Some(config))
13475 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13476 {
13477 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13478 }
13479 (Some(config), _) | (_, Some(config))
13480 if buffer.contains_str_at(indent_end, &config.prefix) =>
13481 {
13482 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13483 }
13484 (_, _) => language_scope
13485 .line_comment_prefixes()
13486 .iter()
13487 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13488 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13489 }
13490 } else {
13491 // we not in an overridden comment node, but we may
13492 // be within a non-overridden line comment node
13493 language_scope
13494 .line_comment_prefixes()
13495 .iter()
13496 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13497 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13498 };
13499
13500 let rewrap_prefix = language_scope
13501 .rewrap_prefixes()
13502 .iter()
13503 .find_map(|prefix_regex| {
13504 prefix_regex.find(&line_text_after_indent).map(|mat| {
13505 if mat.start() == 0 {
13506 Some(mat.as_str().to_string())
13507 } else {
13508 None
13509 }
13510 })
13511 })
13512 .flatten();
13513 (comment_delimiters, rewrap_prefix)
13514 } else {
13515 (None, None)
13516 };
13517 (indent, comment_prefix, rewrap_prefix)
13518 };
13519
13520 let mut start_row = selection.start.row;
13521 let mut end_row = selection.end.row;
13522
13523 if selection.is_empty() {
13524 let cursor_row = selection.start.row;
13525
13526 let (mut indent_size, comment_prefix, _) = indent_and_prefix_for_row(cursor_row);
13527 let line_prefix = match &comment_prefix {
13528 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13529 Some(prefix.as_str())
13530 }
13531 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13532 prefix, ..
13533 })) => Some(prefix.as_ref()),
13534 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13535 start: _,
13536 end: _,
13537 prefix,
13538 tab_size,
13539 })) => {
13540 indent_size.len += tab_size;
13541 Some(prefix.as_ref())
13542 }
13543 None => None,
13544 };
13545 let indent_prefix = indent_size.chars().collect::<String>();
13546 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13547
13548 'expand_upwards: while start_row > 0 {
13549 let prev_row = start_row - 1;
13550 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13551 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13552 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13553 {
13554 start_row = prev_row;
13555 } else {
13556 break 'expand_upwards;
13557 }
13558 }
13559
13560 'expand_downwards: while end_row < buffer.max_point().row {
13561 let next_row = end_row + 1;
13562 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13563 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13564 && !buffer.is_line_blank(MultiBufferRow(next_row))
13565 {
13566 end_row = next_row;
13567 } else {
13568 break 'expand_downwards;
13569 }
13570 }
13571 }
13572
13573 let mut non_blank_rows_iter = (start_row..=end_row)
13574 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13575 .peekable();
13576
13577 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13578 row
13579 } else {
13580 return Vec::new();
13581 };
13582
13583 let mut ranges = Vec::new();
13584
13585 let mut current_range_start = first_row;
13586 let mut prev_row = first_row;
13587 let (
13588 mut current_range_indent,
13589 mut current_range_comment_delimiters,
13590 mut current_range_rewrap_prefix,
13591 ) = indent_and_prefix_for_row(first_row);
13592
13593 for row in non_blank_rows_iter.skip(1) {
13594 let has_paragraph_break = row > prev_row + 1;
13595
13596 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13597 indent_and_prefix_for_row(row);
13598
13599 let has_indent_change = row_indent != current_range_indent;
13600 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13601
13602 let has_boundary_change = has_comment_change
13603 || row_rewrap_prefix.is_some()
13604 || (has_indent_change && current_range_comment_delimiters.is_some());
13605
13606 if has_paragraph_break || has_boundary_change {
13607 ranges.push((
13608 language_settings.clone(),
13609 Point::new(current_range_start, 0)
13610 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13611 current_range_indent,
13612 current_range_comment_delimiters.clone(),
13613 current_range_rewrap_prefix.clone(),
13614 ));
13615 current_range_start = row;
13616 current_range_indent = row_indent;
13617 current_range_comment_delimiters = row_comment_delimiters;
13618 current_range_rewrap_prefix = row_rewrap_prefix;
13619 }
13620 prev_row = row;
13621 }
13622
13623 ranges.push((
13624 language_settings.clone(),
13625 Point::new(current_range_start, 0)
13626 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13627 current_range_indent,
13628 current_range_comment_delimiters,
13629 current_range_rewrap_prefix,
13630 ));
13631
13632 ranges
13633 });
13634
13635 let mut edits = Vec::new();
13636 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13637
13638 for (language_settings, wrap_range, mut indent_size, comment_prefix, rewrap_prefix) in
13639 wrap_ranges
13640 {
13641 let start_row = wrap_range.start.row;
13642 let end_row = wrap_range.end.row;
13643
13644 // Skip selections that overlap with a range that has already been rewrapped.
13645 let selection_range = start_row..end_row;
13646 if rewrapped_row_ranges
13647 .iter()
13648 .any(|range| range.overlaps(&selection_range))
13649 {
13650 continue;
13651 }
13652
13653 let tab_size = language_settings.tab_size;
13654
13655 let (line_prefix, inside_comment) = match &comment_prefix {
13656 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13657 (Some(prefix.as_str()), true)
13658 }
13659 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13660 (Some(prefix.as_ref()), true)
13661 }
13662 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13663 start: _,
13664 end: _,
13665 prefix,
13666 tab_size,
13667 })) => {
13668 indent_size.len += tab_size;
13669 (Some(prefix.as_ref()), true)
13670 }
13671 None => (None, false),
13672 };
13673 let indent_prefix = indent_size.chars().collect::<String>();
13674 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13675
13676 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13677 RewrapBehavior::InComments => inside_comment,
13678 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13679 RewrapBehavior::Anywhere => true,
13680 };
13681
13682 let should_rewrap = options.override_language_settings
13683 || allow_rewrap_based_on_language
13684 || self.hard_wrap.is_some();
13685 if !should_rewrap {
13686 continue;
13687 }
13688
13689 let start = Point::new(start_row, 0);
13690 let start_offset = ToOffset::to_offset(&start, &buffer);
13691 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13692 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13693 let mut first_line_delimiter = None;
13694 let mut last_line_delimiter = None;
13695 let Some(lines_without_prefixes) = selection_text
13696 .lines()
13697 .enumerate()
13698 .map(|(ix, line)| {
13699 let line_trimmed = line.trim_start();
13700 if rewrap_prefix.is_some() && ix > 0 {
13701 Ok(line_trimmed)
13702 } else if let Some(
13703 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13704 start,
13705 prefix,
13706 end,
13707 tab_size,
13708 })
13709 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13710 start,
13711 prefix,
13712 end,
13713 tab_size,
13714 }),
13715 ) = &comment_prefix
13716 {
13717 let line_trimmed = line_trimmed
13718 .strip_prefix(start.as_ref())
13719 .map(|s| {
13720 let mut indent_size = indent_size;
13721 indent_size.len -= tab_size;
13722 let indent_prefix: String = indent_size.chars().collect();
13723 first_line_delimiter = Some((indent_prefix, start));
13724 s.trim_start()
13725 })
13726 .unwrap_or(line_trimmed);
13727 let line_trimmed = line_trimmed
13728 .strip_suffix(end.as_ref())
13729 .map(|s| {
13730 last_line_delimiter = Some(end);
13731 s.trim_end()
13732 })
13733 .unwrap_or(line_trimmed);
13734 let line_trimmed = line_trimmed
13735 .strip_prefix(prefix.as_ref())
13736 .unwrap_or(line_trimmed);
13737 Ok(line_trimmed)
13738 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13739 line_trimmed.strip_prefix(prefix).with_context(|| {
13740 format!("line did not start with prefix {prefix:?}: {line:?}")
13741 })
13742 } else {
13743 line_trimmed
13744 .strip_prefix(&line_prefix.trim_start())
13745 .with_context(|| {
13746 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13747 })
13748 }
13749 })
13750 .collect::<Result<Vec<_>, _>>()
13751 .log_err()
13752 else {
13753 continue;
13754 };
13755
13756 let wrap_column = options.line_length.or(self.hard_wrap).unwrap_or_else(|| {
13757 buffer
13758 .language_settings_at(Point::new(start_row, 0), cx)
13759 .preferred_line_length as usize
13760 });
13761
13762 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13763 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13764 } else {
13765 line_prefix.clone()
13766 };
13767
13768 let wrapped_text = {
13769 let mut wrapped_text = wrap_with_prefix(
13770 line_prefix,
13771 subsequent_lines_prefix,
13772 lines_without_prefixes.join("\n"),
13773 wrap_column,
13774 tab_size,
13775 options.preserve_existing_whitespace,
13776 );
13777
13778 if let Some((indent, delimiter)) = first_line_delimiter {
13779 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13780 }
13781 if let Some(last_line) = last_line_delimiter {
13782 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13783 }
13784
13785 wrapped_text
13786 };
13787
13788 // TODO: should always use char-based diff while still supporting cursor behavior that
13789 // matches vim.
13790 let mut diff_options = DiffOptions::default();
13791 if options.override_language_settings {
13792 diff_options.max_word_diff_len = 0;
13793 diff_options.max_word_diff_line_count = 0;
13794 } else {
13795 diff_options.max_word_diff_len = usize::MAX;
13796 diff_options.max_word_diff_line_count = usize::MAX;
13797 }
13798
13799 for (old_range, new_text) in
13800 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13801 {
13802 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13803 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13804 edits.push((edit_start..edit_end, new_text));
13805 }
13806
13807 rewrapped_row_ranges.push(start_row..=end_row);
13808 }
13809
13810 self.buffer
13811 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13812 }
13813
13814 pub fn cut_common(
13815 &mut self,
13816 cut_no_selection_line: bool,
13817 window: &mut Window,
13818 cx: &mut Context<Self>,
13819 ) -> ClipboardItem {
13820 let mut text = String::new();
13821 let buffer = self.buffer.read(cx).snapshot(cx);
13822 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13823 let mut clipboard_selections = Vec::with_capacity(selections.len());
13824 {
13825 let max_point = buffer.max_point();
13826 let mut is_first = true;
13827 let mut prev_selection_was_entire_line = false;
13828 for selection in &mut selections {
13829 let is_entire_line =
13830 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13831 if is_entire_line {
13832 selection.start = Point::new(selection.start.row, 0);
13833 if !selection.is_empty() && selection.end.column == 0 {
13834 selection.end = cmp::min(max_point, selection.end);
13835 } else {
13836 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13837 }
13838 selection.goal = SelectionGoal::None;
13839 }
13840 if is_first {
13841 is_first = false;
13842 } else if !prev_selection_was_entire_line {
13843 text += "\n";
13844 }
13845 prev_selection_was_entire_line = is_entire_line;
13846 let mut len = 0;
13847 for chunk in buffer.text_for_range(selection.start..selection.end) {
13848 text.push_str(chunk);
13849 len += chunk.len();
13850 }
13851
13852 clipboard_selections.push(ClipboardSelection::for_buffer(
13853 len,
13854 is_entire_line,
13855 selection.range(),
13856 &buffer,
13857 self.project.as_ref(),
13858 cx,
13859 ));
13860 }
13861 }
13862
13863 self.transact(window, cx, |this, window, cx| {
13864 this.change_selections(Default::default(), window, cx, |s| {
13865 s.select(selections);
13866 });
13867 this.insert("", window, cx);
13868 });
13869 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13870 }
13871
13872 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13874 let item = self.cut_common(true, window, cx);
13875 cx.write_to_clipboard(item);
13876 }
13877
13878 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13879 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13880 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13881 s.move_with(&mut |snapshot, sel| {
13882 if sel.is_empty() {
13883 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13884 }
13885 if sel.is_empty() {
13886 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13887 }
13888 });
13889 });
13890 let item = self.cut_common(false, window, cx);
13891 cx.set_global(KillRing(item))
13892 }
13893
13894 pub fn kill_ring_yank(
13895 &mut self,
13896 _: &KillRingYank,
13897 window: &mut Window,
13898 cx: &mut Context<Self>,
13899 ) {
13900 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13901 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13902 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13903 (kill_ring.text().to_string(), kill_ring.metadata_json())
13904 } else {
13905 return;
13906 }
13907 } else {
13908 return;
13909 };
13910 self.do_paste(&text, metadata, false, window, cx);
13911 }
13912
13913 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13914 self.do_copy(true, cx);
13915 }
13916
13917 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13918 self.do_copy(false, cx);
13919 }
13920
13921 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13922 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13923 let buffer = self.buffer.read(cx).read(cx);
13924 let mut text = String::new();
13925 let mut clipboard_selections = Vec::with_capacity(selections.len());
13926
13927 let max_point = buffer.max_point();
13928 let mut is_first = true;
13929 for selection in &selections {
13930 let mut start = selection.start;
13931 let mut end = selection.end;
13932 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13933 let mut add_trailing_newline = false;
13934 if is_entire_line {
13935 start = Point::new(start.row, 0);
13936 let next_line_start = Point::new(end.row + 1, 0);
13937 if next_line_start <= max_point {
13938 end = next_line_start;
13939 } else {
13940 // We're on the last line without a trailing newline.
13941 // Copy to the end of the line and add a newline afterwards.
13942 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13943 add_trailing_newline = true;
13944 }
13945 }
13946
13947 let mut trimmed_selections = Vec::new();
13948 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13949 let row = MultiBufferRow(start.row);
13950 let first_indent = buffer.indent_size_for_line(row);
13951 if first_indent.len == 0 || start.column > first_indent.len {
13952 trimmed_selections.push(start..end);
13953 } else {
13954 trimmed_selections.push(
13955 Point::new(row.0, first_indent.len)
13956 ..Point::new(row.0, buffer.line_len(row)),
13957 );
13958 for row in start.row + 1..=end.row {
13959 let mut line_len = buffer.line_len(MultiBufferRow(row));
13960 if row == end.row {
13961 line_len = end.column;
13962 }
13963 if line_len == 0 {
13964 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13965 continue;
13966 }
13967 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13968 if row_indent_size.len >= first_indent.len {
13969 trimmed_selections
13970 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13971 } else {
13972 trimmed_selections.clear();
13973 trimmed_selections.push(start..end);
13974 break;
13975 }
13976 }
13977 }
13978 } else {
13979 trimmed_selections.push(start..end);
13980 }
13981
13982 let is_multiline_trim = trimmed_selections.len() > 1;
13983 let mut selection_len: usize = 0;
13984 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13985
13986 for trimmed_range in trimmed_selections {
13987 if is_first {
13988 is_first = false;
13989 } else if is_multiline_trim || !prev_selection_was_entire_line {
13990 text.push('\n');
13991 if is_multiline_trim {
13992 selection_len += 1;
13993 }
13994 }
13995 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13996 text.push_str(chunk);
13997 selection_len += chunk.len();
13998 }
13999 if add_trailing_newline {
14000 text.push('\n');
14001 selection_len += 1;
14002 }
14003 }
14004
14005 clipboard_selections.push(ClipboardSelection::for_buffer(
14006 selection_len,
14007 is_entire_line,
14008 start..end,
14009 &buffer,
14010 self.project.as_ref(),
14011 cx,
14012 ));
14013 }
14014
14015 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
14016 text,
14017 clipboard_selections,
14018 ));
14019 }
14020
14021 pub fn do_paste(
14022 &mut self,
14023 text: &String,
14024 clipboard_selections: Option<Vec<ClipboardSelection>>,
14025 handle_entire_lines: bool,
14026 window: &mut Window,
14027 cx: &mut Context<Self>,
14028 ) {
14029 if self.read_only(cx) {
14030 return;
14031 }
14032
14033 self.finalize_last_transaction(cx);
14034
14035 let clipboard_text = Cow::Borrowed(text.as_str());
14036
14037 self.transact(window, cx, |this, window, cx| {
14038 let had_active_edit_prediction = this.has_active_edit_prediction();
14039 let display_map = this.display_snapshot(cx);
14040 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
14041 let cursor_offset = this
14042 .selections
14043 .last::<MultiBufferOffset>(&display_map)
14044 .head();
14045
14046 if let Some(mut clipboard_selections) = clipboard_selections {
14047 let all_selections_were_entire_line =
14048 clipboard_selections.iter().all(|s| s.is_entire_line);
14049 let first_selection_indent_column =
14050 clipboard_selections.first().map(|s| s.first_line_indent);
14051 if clipboard_selections.len() != old_selections.len() {
14052 clipboard_selections.drain(..);
14053 }
14054 let mut auto_indent_on_paste = true;
14055
14056 this.buffer.update(cx, |buffer, cx| {
14057 let snapshot = buffer.read(cx);
14058 auto_indent_on_paste = snapshot
14059 .language_settings_at(cursor_offset, cx)
14060 .auto_indent_on_paste;
14061
14062 let mut start_offset = 0;
14063 let mut edits = Vec::new();
14064 let mut original_indent_columns = Vec::new();
14065 for (ix, selection) in old_selections.iter().enumerate() {
14066 let to_insert;
14067 let entire_line;
14068 let original_indent_column;
14069 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
14070 let end_offset = start_offset + clipboard_selection.len;
14071 to_insert = &clipboard_text[start_offset..end_offset];
14072 entire_line = clipboard_selection.is_entire_line;
14073 start_offset = if entire_line {
14074 end_offset
14075 } else {
14076 end_offset + 1
14077 };
14078 original_indent_column = Some(clipboard_selection.first_line_indent);
14079 } else {
14080 to_insert = &*clipboard_text;
14081 entire_line = all_selections_were_entire_line;
14082 original_indent_column = first_selection_indent_column
14083 }
14084
14085 let (range, to_insert) =
14086 if selection.is_empty() && handle_entire_lines && entire_line {
14087 // If the corresponding selection was empty when this slice of the
14088 // clipboard text was written, then the entire line containing the
14089 // selection was copied. If this selection is also currently empty,
14090 // then paste the line before the current line of the buffer.
14091 let column = selection.start.to_point(&snapshot).column as usize;
14092 let line_start = selection.start - column;
14093 (line_start..line_start, Cow::Borrowed(to_insert))
14094 } else {
14095 let language = snapshot.language_at(selection.head());
14096 let range = selection.range();
14097 if let Some(language) = language
14098 && language.name() == "Markdown"
14099 {
14100 edit_for_markdown_paste(
14101 &snapshot,
14102 range,
14103 to_insert,
14104 url::Url::parse(to_insert).ok(),
14105 )
14106 } else {
14107 (range, Cow::Borrowed(to_insert))
14108 }
14109 };
14110
14111 edits.push((range, to_insert));
14112 original_indent_columns.push(original_indent_column);
14113 }
14114 drop(snapshot);
14115
14116 buffer.edit(
14117 edits,
14118 if auto_indent_on_paste {
14119 Some(AutoindentMode::Block {
14120 original_indent_columns,
14121 })
14122 } else {
14123 None
14124 },
14125 cx,
14126 );
14127 });
14128
14129 let selections = this
14130 .selections
14131 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
14132 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14133 } else {
14134 let url = url::Url::parse(&clipboard_text).ok();
14135
14136 let auto_indent_mode = if !clipboard_text.is_empty() {
14137 Some(AutoindentMode::Block {
14138 original_indent_columns: Vec::new(),
14139 })
14140 } else {
14141 None
14142 };
14143
14144 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
14145 let snapshot = buffer.snapshot(cx);
14146
14147 let anchors = old_selections
14148 .iter()
14149 .map(|s| {
14150 let anchor = snapshot.anchor_after(s.head());
14151 s.map(|_| anchor)
14152 })
14153 .collect::<Vec<_>>();
14154
14155 let mut edits = Vec::new();
14156
14157 // When pasting text without metadata (e.g. copied from an
14158 // external editor using multiple cursors) and the number of
14159 // lines matches the number of selections, distribute one
14160 // line per cursor instead of pasting the whole text at each.
14161 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14162 let distribute_lines =
14163 old_selections.len() > 1 && lines.len() == old_selections.len();
14164
14165 for (ix, selection) in old_selections.iter().enumerate() {
14166 let language = snapshot.language_at(selection.head());
14167 let range = selection.range();
14168
14169 let text_for_cursor: &str = if distribute_lines {
14170 lines[ix]
14171 } else {
14172 &clipboard_text
14173 };
14174
14175 let (edit_range, edit_text) = if let Some(language) = language
14176 && language.name() == "Markdown"
14177 {
14178 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14179 } else {
14180 (range, Cow::Borrowed(text_for_cursor))
14181 };
14182
14183 edits.push((edit_range, edit_text));
14184 }
14185
14186 drop(snapshot);
14187 buffer.edit(edits, auto_indent_mode, cx);
14188
14189 anchors
14190 });
14191
14192 this.change_selections(Default::default(), window, cx, |s| {
14193 s.select_anchors(selection_anchors);
14194 });
14195 }
14196
14197 // 🤔 | .. | show_in_menu |
14198 // | .. | true true
14199 // | had_edit_prediction | false true
14200
14201 let trigger_in_words =
14202 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14203
14204 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14205 });
14206 }
14207
14208 pub fn diff_clipboard_with_selection(
14209 &mut self,
14210 _: &DiffClipboardWithSelection,
14211 window: &mut Window,
14212 cx: &mut Context<Self>,
14213 ) {
14214 let selections = self
14215 .selections
14216 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14217
14218 if selections.is_empty() {
14219 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14220 return;
14221 };
14222
14223 let clipboard_text = cx.read_from_clipboard().and_then(|item| {
14224 item.entries().iter().find_map(|entry| match entry {
14225 ClipboardEntry::String(text) => Some(text.text().to_string()),
14226 _ => None,
14227 })
14228 });
14229
14230 let Some(clipboard_text) = clipboard_text else {
14231 log::warn!("Clipboard doesn't contain text.");
14232 return;
14233 };
14234
14235 window.dispatch_action(
14236 Box::new(DiffClipboardWithSelectionData {
14237 clipboard_text,
14238 editor: cx.entity(),
14239 }),
14240 cx,
14241 );
14242 }
14243
14244 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14245 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14246 if let Some(item) = cx.read_from_clipboard() {
14247 let clipboard_string = item.entries().iter().find_map(|entry| match entry {
14248 ClipboardEntry::String(s) => Some(s),
14249 _ => None,
14250 });
14251 match clipboard_string {
14252 Some(clipboard_string) => self.do_paste(
14253 clipboard_string.text(),
14254 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14255 true,
14256 window,
14257 cx,
14258 ),
14259 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14260 }
14261 }
14262 }
14263
14264 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14265 if self.read_only(cx) {
14266 return;
14267 }
14268
14269 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14270
14271 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14272 if let Some((selections, _)) =
14273 self.selection_history.transaction(transaction_id).cloned()
14274 {
14275 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14276 s.select_anchors(selections.to_vec());
14277 });
14278 } else {
14279 log::error!(
14280 "No entry in selection_history found for undo. \
14281 This may correspond to a bug where undo does not update the selection. \
14282 If this is occurring, please add details to \
14283 https://github.com/zed-industries/zed/issues/22692"
14284 );
14285 }
14286 self.request_autoscroll(Autoscroll::fit(), cx);
14287 self.unmark_text(window, cx);
14288 self.refresh_edit_prediction(true, false, window, cx);
14289 cx.emit(EditorEvent::Edited { transaction_id });
14290 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14291 }
14292 }
14293
14294 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14295 if self.read_only(cx) {
14296 return;
14297 }
14298
14299 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14300
14301 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14302 if let Some((_, Some(selections))) =
14303 self.selection_history.transaction(transaction_id).cloned()
14304 {
14305 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14306 s.select_anchors(selections.to_vec());
14307 });
14308 } else {
14309 log::error!(
14310 "No entry in selection_history found for redo. \
14311 This may correspond to a bug where undo does not update the selection. \
14312 If this is occurring, please add details to \
14313 https://github.com/zed-industries/zed/issues/22692"
14314 );
14315 }
14316 self.request_autoscroll(Autoscroll::fit(), cx);
14317 self.unmark_text(window, cx);
14318 self.refresh_edit_prediction(true, false, window, cx);
14319 cx.emit(EditorEvent::Edited { transaction_id });
14320 }
14321 }
14322
14323 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14324 self.buffer
14325 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14326 }
14327
14328 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14329 self.buffer
14330 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14331 }
14332
14333 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14335 self.change_selections(Default::default(), window, cx, |s| {
14336 s.move_with(&mut |map, selection| {
14337 let cursor = if selection.is_empty() {
14338 movement::left(map, selection.start)
14339 } else {
14340 selection.start
14341 };
14342 selection.collapse_to(cursor, SelectionGoal::None);
14343 });
14344 })
14345 }
14346
14347 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14348 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14349 self.change_selections(Default::default(), window, cx, |s| {
14350 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14351 })
14352 }
14353
14354 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14356 self.change_selections(Default::default(), window, cx, |s| {
14357 s.move_with(&mut |map, selection| {
14358 let cursor = if selection.is_empty() {
14359 movement::right(map, selection.end)
14360 } else {
14361 selection.end
14362 };
14363 selection.collapse_to(cursor, SelectionGoal::None)
14364 });
14365 })
14366 }
14367
14368 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14369 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14370 self.change_selections(Default::default(), window, cx, |s| {
14371 s.move_heads_with(&mut |map, head, _| {
14372 (movement::right(map, head), SelectionGoal::None)
14373 });
14374 });
14375 }
14376
14377 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14378 if self.take_rename(true, window, cx).is_some() {
14379 return;
14380 }
14381
14382 if self.mode.is_single_line() {
14383 cx.propagate();
14384 return;
14385 }
14386
14387 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14388
14389 let text_layout_details = &self.text_layout_details(window, cx);
14390 let selection_count = self.selections.count();
14391 let first_selection = self.selections.first_anchor();
14392
14393 self.change_selections(Default::default(), window, cx, |s| {
14394 s.move_with(&mut |map, selection| {
14395 if !selection.is_empty() {
14396 selection.goal = SelectionGoal::None;
14397 }
14398 let (cursor, goal) = movement::up(
14399 map,
14400 selection.start,
14401 selection.goal,
14402 false,
14403 text_layout_details,
14404 );
14405 selection.collapse_to(cursor, goal);
14406 });
14407 });
14408
14409 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14410 {
14411 cx.propagate();
14412 }
14413 }
14414
14415 pub fn move_up_by_lines(
14416 &mut self,
14417 action: &MoveUpByLines,
14418 window: &mut Window,
14419 cx: &mut Context<Self>,
14420 ) {
14421 if self.take_rename(true, window, cx).is_some() {
14422 return;
14423 }
14424
14425 if self.mode.is_single_line() {
14426 cx.propagate();
14427 return;
14428 }
14429
14430 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14431
14432 let text_layout_details = &self.text_layout_details(window, cx);
14433
14434 self.change_selections(Default::default(), window, cx, |s| {
14435 s.move_with(&mut |map, selection| {
14436 if !selection.is_empty() {
14437 selection.goal = SelectionGoal::None;
14438 }
14439 let (cursor, goal) = movement::up_by_rows(
14440 map,
14441 selection.start,
14442 action.lines,
14443 selection.goal,
14444 false,
14445 text_layout_details,
14446 );
14447 selection.collapse_to(cursor, goal);
14448 });
14449 })
14450 }
14451
14452 pub fn move_down_by_lines(
14453 &mut self,
14454 action: &MoveDownByLines,
14455 window: &mut Window,
14456 cx: &mut Context<Self>,
14457 ) {
14458 if self.take_rename(true, window, cx).is_some() {
14459 return;
14460 }
14461
14462 if self.mode.is_single_line() {
14463 cx.propagate();
14464 return;
14465 }
14466
14467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14468
14469 let text_layout_details = &self.text_layout_details(window, cx);
14470
14471 self.change_selections(Default::default(), window, cx, |s| {
14472 s.move_with(&mut |map, selection| {
14473 if !selection.is_empty() {
14474 selection.goal = SelectionGoal::None;
14475 }
14476 let (cursor, goal) = movement::down_by_rows(
14477 map,
14478 selection.start,
14479 action.lines,
14480 selection.goal,
14481 false,
14482 text_layout_details,
14483 );
14484 selection.collapse_to(cursor, goal);
14485 });
14486 })
14487 }
14488
14489 pub fn select_down_by_lines(
14490 &mut self,
14491 action: &SelectDownByLines,
14492 window: &mut Window,
14493 cx: &mut Context<Self>,
14494 ) {
14495 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14496 let text_layout_details = &self.text_layout_details(window, cx);
14497 self.change_selections(Default::default(), window, cx, |s| {
14498 s.move_heads_with(&mut |map, head, goal| {
14499 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14500 })
14501 })
14502 }
14503
14504 pub fn select_up_by_lines(
14505 &mut self,
14506 action: &SelectUpByLines,
14507 window: &mut Window,
14508 cx: &mut Context<Self>,
14509 ) {
14510 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14511 let text_layout_details = &self.text_layout_details(window, cx);
14512 self.change_selections(Default::default(), window, cx, |s| {
14513 s.move_heads_with(&mut |map, head, goal| {
14514 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14515 })
14516 })
14517 }
14518
14519 pub fn select_page_up(
14520 &mut self,
14521 _: &SelectPageUp,
14522 window: &mut Window,
14523 cx: &mut Context<Self>,
14524 ) {
14525 let Some(row_count) = self.visible_row_count() else {
14526 return;
14527 };
14528
14529 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14530
14531 let text_layout_details = &self.text_layout_details(window, cx);
14532
14533 self.change_selections(Default::default(), window, cx, |s| {
14534 s.move_heads_with(&mut |map, head, goal| {
14535 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14536 })
14537 })
14538 }
14539
14540 pub fn move_page_up(
14541 &mut self,
14542 action: &MovePageUp,
14543 window: &mut Window,
14544 cx: &mut Context<Self>,
14545 ) {
14546 if self.take_rename(true, window, cx).is_some() {
14547 return;
14548 }
14549
14550 if self
14551 .context_menu
14552 .borrow_mut()
14553 .as_mut()
14554 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14555 .unwrap_or(false)
14556 {
14557 return;
14558 }
14559
14560 if matches!(self.mode, EditorMode::SingleLine) {
14561 cx.propagate();
14562 return;
14563 }
14564
14565 let Some(row_count) = self.visible_row_count() else {
14566 return;
14567 };
14568
14569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14570
14571 let effects = if action.center_cursor {
14572 SelectionEffects::scroll(Autoscroll::center())
14573 } else {
14574 SelectionEffects::default()
14575 };
14576
14577 let text_layout_details = &self.text_layout_details(window, cx);
14578
14579 self.change_selections(effects, window, cx, |s| {
14580 s.move_with(&mut |map, selection| {
14581 if !selection.is_empty() {
14582 selection.goal = SelectionGoal::None;
14583 }
14584 let (cursor, goal) = movement::up_by_rows(
14585 map,
14586 selection.end,
14587 row_count,
14588 selection.goal,
14589 false,
14590 text_layout_details,
14591 );
14592 selection.collapse_to(cursor, goal);
14593 });
14594 });
14595 }
14596
14597 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14598 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14599 let text_layout_details = &self.text_layout_details(window, cx);
14600 self.change_selections(Default::default(), window, cx, |s| {
14601 s.move_heads_with(&mut |map, head, goal| {
14602 movement::up(map, head, goal, false, text_layout_details)
14603 })
14604 })
14605 }
14606
14607 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14608 self.take_rename(true, window, cx);
14609
14610 if self.mode.is_single_line() {
14611 cx.propagate();
14612 return;
14613 }
14614
14615 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14616
14617 let text_layout_details = &self.text_layout_details(window, cx);
14618 let selection_count = self.selections.count();
14619 let first_selection = self.selections.first_anchor();
14620
14621 self.change_selections(Default::default(), window, cx, |s| {
14622 s.move_with(&mut |map, selection| {
14623 if !selection.is_empty() {
14624 selection.goal = SelectionGoal::None;
14625 }
14626 let (cursor, goal) = movement::down(
14627 map,
14628 selection.end,
14629 selection.goal,
14630 false,
14631 text_layout_details,
14632 );
14633 selection.collapse_to(cursor, goal);
14634 });
14635 });
14636
14637 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14638 {
14639 cx.propagate();
14640 }
14641 }
14642
14643 pub fn select_page_down(
14644 &mut self,
14645 _: &SelectPageDown,
14646 window: &mut Window,
14647 cx: &mut Context<Self>,
14648 ) {
14649 let Some(row_count) = self.visible_row_count() else {
14650 return;
14651 };
14652
14653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14654
14655 let text_layout_details = &self.text_layout_details(window, cx);
14656
14657 self.change_selections(Default::default(), window, cx, |s| {
14658 s.move_heads_with(&mut |map, head, goal| {
14659 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14660 })
14661 })
14662 }
14663
14664 pub fn move_page_down(
14665 &mut self,
14666 action: &MovePageDown,
14667 window: &mut Window,
14668 cx: &mut Context<Self>,
14669 ) {
14670 if self.take_rename(true, window, cx).is_some() {
14671 return;
14672 }
14673
14674 if self
14675 .context_menu
14676 .borrow_mut()
14677 .as_mut()
14678 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14679 .unwrap_or(false)
14680 {
14681 return;
14682 }
14683
14684 if matches!(self.mode, EditorMode::SingleLine) {
14685 cx.propagate();
14686 return;
14687 }
14688
14689 let Some(row_count) = self.visible_row_count() else {
14690 return;
14691 };
14692
14693 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14694
14695 let effects = if action.center_cursor {
14696 SelectionEffects::scroll(Autoscroll::center())
14697 } else {
14698 SelectionEffects::default()
14699 };
14700
14701 let text_layout_details = &self.text_layout_details(window, cx);
14702 self.change_selections(effects, window, cx, |s| {
14703 s.move_with(&mut |map, selection| {
14704 if !selection.is_empty() {
14705 selection.goal = SelectionGoal::None;
14706 }
14707 let (cursor, goal) = movement::down_by_rows(
14708 map,
14709 selection.end,
14710 row_count,
14711 selection.goal,
14712 false,
14713 text_layout_details,
14714 );
14715 selection.collapse_to(cursor, goal);
14716 });
14717 });
14718 }
14719
14720 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14722 let text_layout_details = &self.text_layout_details(window, cx);
14723 self.change_selections(Default::default(), window, cx, |s| {
14724 s.move_heads_with(&mut |map, head, goal| {
14725 movement::down(map, head, goal, false, text_layout_details)
14726 })
14727 });
14728 }
14729
14730 pub fn context_menu_first(
14731 &mut self,
14732 _: &ContextMenuFirst,
14733 window: &mut Window,
14734 cx: &mut Context<Self>,
14735 ) {
14736 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14737 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14738 }
14739 }
14740
14741 pub fn context_menu_prev(
14742 &mut self,
14743 _: &ContextMenuPrevious,
14744 window: &mut Window,
14745 cx: &mut Context<Self>,
14746 ) {
14747 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14748 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14749 }
14750 }
14751
14752 pub fn context_menu_next(
14753 &mut self,
14754 _: &ContextMenuNext,
14755 window: &mut Window,
14756 cx: &mut Context<Self>,
14757 ) {
14758 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14759 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14760 }
14761 }
14762
14763 pub fn context_menu_last(
14764 &mut self,
14765 _: &ContextMenuLast,
14766 window: &mut Window,
14767 cx: &mut Context<Self>,
14768 ) {
14769 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14770 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14771 }
14772 }
14773
14774 pub fn signature_help_prev(
14775 &mut self,
14776 _: &SignatureHelpPrevious,
14777 _: &mut Window,
14778 cx: &mut Context<Self>,
14779 ) {
14780 if let Some(popover) = self.signature_help_state.popover_mut() {
14781 if popover.current_signature == 0 {
14782 popover.current_signature = popover.signatures.len() - 1;
14783 } else {
14784 popover.current_signature -= 1;
14785 }
14786 cx.notify();
14787 }
14788 }
14789
14790 pub fn signature_help_next(
14791 &mut self,
14792 _: &SignatureHelpNext,
14793 _: &mut Window,
14794 cx: &mut Context<Self>,
14795 ) {
14796 if let Some(popover) = self.signature_help_state.popover_mut() {
14797 if popover.current_signature + 1 == popover.signatures.len() {
14798 popover.current_signature = 0;
14799 } else {
14800 popover.current_signature += 1;
14801 }
14802 cx.notify();
14803 }
14804 }
14805
14806 pub fn move_to_previous_word_start(
14807 &mut self,
14808 _: &MoveToPreviousWordStart,
14809 window: &mut Window,
14810 cx: &mut Context<Self>,
14811 ) {
14812 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14813 self.change_selections(Default::default(), window, cx, |s| {
14814 s.move_cursors_with(&mut |map, head, _| {
14815 (
14816 movement::previous_word_start(map, head),
14817 SelectionGoal::None,
14818 )
14819 });
14820 })
14821 }
14822
14823 pub fn move_to_previous_subword_start(
14824 &mut self,
14825 _: &MoveToPreviousSubwordStart,
14826 window: &mut Window,
14827 cx: &mut Context<Self>,
14828 ) {
14829 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14830 self.change_selections(Default::default(), window, cx, |s| {
14831 s.move_cursors_with(&mut |map, head, _| {
14832 (
14833 movement::previous_subword_start(map, head),
14834 SelectionGoal::None,
14835 )
14836 });
14837 })
14838 }
14839
14840 pub fn select_to_previous_word_start(
14841 &mut self,
14842 _: &SelectToPreviousWordStart,
14843 window: &mut Window,
14844 cx: &mut Context<Self>,
14845 ) {
14846 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14847 self.change_selections(Default::default(), window, cx, |s| {
14848 s.move_heads_with(&mut |map, head, _| {
14849 (
14850 movement::previous_word_start(map, head),
14851 SelectionGoal::None,
14852 )
14853 });
14854 })
14855 }
14856
14857 pub fn select_to_previous_subword_start(
14858 &mut self,
14859 _: &SelectToPreviousSubwordStart,
14860 window: &mut Window,
14861 cx: &mut Context<Self>,
14862 ) {
14863 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14864 self.change_selections(Default::default(), window, cx, |s| {
14865 s.move_heads_with(&mut |map, head, _| {
14866 (
14867 movement::previous_subword_start(map, head),
14868 SelectionGoal::None,
14869 )
14870 });
14871 })
14872 }
14873
14874 pub fn delete_to_previous_word_start(
14875 &mut self,
14876 action: &DeleteToPreviousWordStart,
14877 window: &mut Window,
14878 cx: &mut Context<Self>,
14879 ) {
14880 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14881 self.transact(window, cx, |this, window, cx| {
14882 this.select_autoclose_pair(window, cx);
14883 this.change_selections(Default::default(), window, cx, |s| {
14884 s.move_with(&mut |map, selection| {
14885 if selection.is_empty() {
14886 let mut cursor = if action.ignore_newlines {
14887 movement::previous_word_start(map, selection.head())
14888 } else {
14889 movement::previous_word_start_or_newline(map, selection.head())
14890 };
14891 cursor = movement::adjust_greedy_deletion(
14892 map,
14893 selection.head(),
14894 cursor,
14895 action.ignore_brackets,
14896 );
14897 selection.set_head(cursor, SelectionGoal::None);
14898 }
14899 });
14900 });
14901 this.insert("", window, cx);
14902 });
14903 }
14904
14905 pub fn delete_to_previous_subword_start(
14906 &mut self,
14907 action: &DeleteToPreviousSubwordStart,
14908 window: &mut Window,
14909 cx: &mut Context<Self>,
14910 ) {
14911 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14912 self.transact(window, cx, |this, window, cx| {
14913 this.select_autoclose_pair(window, cx);
14914 this.change_selections(Default::default(), window, cx, |s| {
14915 s.move_with(&mut |map, selection| {
14916 if selection.is_empty() {
14917 let mut cursor = if action.ignore_newlines {
14918 movement::previous_subword_start(map, selection.head())
14919 } else {
14920 movement::previous_subword_start_or_newline(map, selection.head())
14921 };
14922 cursor = movement::adjust_greedy_deletion(
14923 map,
14924 selection.head(),
14925 cursor,
14926 action.ignore_brackets,
14927 );
14928 selection.set_head(cursor, SelectionGoal::None);
14929 }
14930 });
14931 });
14932 this.insert("", window, cx);
14933 });
14934 }
14935
14936 pub fn move_to_next_word_end(
14937 &mut self,
14938 _: &MoveToNextWordEnd,
14939 window: &mut Window,
14940 cx: &mut Context<Self>,
14941 ) {
14942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14943 self.change_selections(Default::default(), window, cx, |s| {
14944 s.move_cursors_with(&mut |map, head, _| {
14945 (movement::next_word_end(map, head), SelectionGoal::None)
14946 });
14947 })
14948 }
14949
14950 pub fn move_to_next_subword_end(
14951 &mut self,
14952 _: &MoveToNextSubwordEnd,
14953 window: &mut Window,
14954 cx: &mut Context<Self>,
14955 ) {
14956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14957 self.change_selections(Default::default(), window, cx, |s| {
14958 s.move_cursors_with(&mut |map, head, _| {
14959 (movement::next_subword_end(map, head), SelectionGoal::None)
14960 });
14961 })
14962 }
14963
14964 pub fn select_to_next_word_end(
14965 &mut self,
14966 _: &SelectToNextWordEnd,
14967 window: &mut Window,
14968 cx: &mut Context<Self>,
14969 ) {
14970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14971 self.change_selections(Default::default(), window, cx, |s| {
14972 s.move_heads_with(&mut |map, head, _| {
14973 (movement::next_word_end(map, head), SelectionGoal::None)
14974 });
14975 })
14976 }
14977
14978 pub fn select_to_next_subword_end(
14979 &mut self,
14980 _: &SelectToNextSubwordEnd,
14981 window: &mut Window,
14982 cx: &mut Context<Self>,
14983 ) {
14984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14985 self.change_selections(Default::default(), window, cx, |s| {
14986 s.move_heads_with(&mut |map, head, _| {
14987 (movement::next_subword_end(map, head), SelectionGoal::None)
14988 });
14989 })
14990 }
14991
14992 pub fn delete_to_next_word_end(
14993 &mut self,
14994 action: &DeleteToNextWordEnd,
14995 window: &mut Window,
14996 cx: &mut Context<Self>,
14997 ) {
14998 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14999 self.transact(window, cx, |this, window, cx| {
15000 this.change_selections(Default::default(), window, cx, |s| {
15001 s.move_with(&mut |map, selection| {
15002 if selection.is_empty() {
15003 let mut cursor = if action.ignore_newlines {
15004 movement::next_word_end(map, selection.head())
15005 } else {
15006 movement::next_word_end_or_newline(map, selection.head())
15007 };
15008 cursor = movement::adjust_greedy_deletion(
15009 map,
15010 selection.head(),
15011 cursor,
15012 action.ignore_brackets,
15013 );
15014 selection.set_head(cursor, SelectionGoal::None);
15015 }
15016 });
15017 });
15018 this.insert("", window, cx);
15019 });
15020 }
15021
15022 pub fn delete_to_next_subword_end(
15023 &mut self,
15024 action: &DeleteToNextSubwordEnd,
15025 window: &mut Window,
15026 cx: &mut Context<Self>,
15027 ) {
15028 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15029 self.transact(window, cx, |this, window, cx| {
15030 this.change_selections(Default::default(), window, cx, |s| {
15031 s.move_with(&mut |map, selection| {
15032 if selection.is_empty() {
15033 let mut cursor = if action.ignore_newlines {
15034 movement::next_subword_end(map, selection.head())
15035 } else {
15036 movement::next_subword_end_or_newline(map, selection.head())
15037 };
15038 cursor = movement::adjust_greedy_deletion(
15039 map,
15040 selection.head(),
15041 cursor,
15042 action.ignore_brackets,
15043 );
15044 selection.set_head(cursor, SelectionGoal::None);
15045 }
15046 });
15047 });
15048 this.insert("", window, cx);
15049 });
15050 }
15051
15052 pub fn move_to_beginning_of_line(
15053 &mut self,
15054 action: &MoveToBeginningOfLine,
15055 window: &mut Window,
15056 cx: &mut Context<Self>,
15057 ) {
15058 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15059 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15060 self.change_selections(Default::default(), window, cx, |s| {
15061 s.move_cursors_with(&mut |map, head, _| {
15062 (
15063 movement::indented_line_beginning(
15064 map,
15065 head,
15066 action.stop_at_soft_wraps,
15067 stop_at_indent,
15068 ),
15069 SelectionGoal::None,
15070 )
15071 });
15072 })
15073 }
15074
15075 pub fn select_to_beginning_of_line(
15076 &mut self,
15077 action: &SelectToBeginningOfLine,
15078 window: &mut Window,
15079 cx: &mut Context<Self>,
15080 ) {
15081 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15083 self.change_selections(Default::default(), window, cx, |s| {
15084 s.move_heads_with(&mut |map, head, _| {
15085 (
15086 movement::indented_line_beginning(
15087 map,
15088 head,
15089 action.stop_at_soft_wraps,
15090 stop_at_indent,
15091 ),
15092 SelectionGoal::None,
15093 )
15094 });
15095 });
15096 }
15097
15098 pub fn delete_to_beginning_of_line(
15099 &mut self,
15100 action: &DeleteToBeginningOfLine,
15101 window: &mut Window,
15102 cx: &mut Context<Self>,
15103 ) {
15104 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15105 self.transact(window, cx, |this, window, cx| {
15106 this.change_selections(Default::default(), window, cx, |s| {
15107 s.move_with(&mut |_, selection| {
15108 selection.reversed = true;
15109 });
15110 });
15111
15112 this.select_to_beginning_of_line(
15113 &SelectToBeginningOfLine {
15114 stop_at_soft_wraps: false,
15115 stop_at_indent: action.stop_at_indent,
15116 },
15117 window,
15118 cx,
15119 );
15120 this.backspace(&Backspace, window, cx);
15121 });
15122 }
15123
15124 pub fn move_to_end_of_line(
15125 &mut self,
15126 action: &MoveToEndOfLine,
15127 window: &mut Window,
15128 cx: &mut Context<Self>,
15129 ) {
15130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15131 self.change_selections(Default::default(), window, cx, |s| {
15132 s.move_cursors_with(&mut |map, head, _| {
15133 (
15134 movement::line_end(map, head, action.stop_at_soft_wraps),
15135 SelectionGoal::None,
15136 )
15137 });
15138 })
15139 }
15140
15141 pub fn select_to_end_of_line(
15142 &mut self,
15143 action: &SelectToEndOfLine,
15144 window: &mut Window,
15145 cx: &mut Context<Self>,
15146 ) {
15147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15148 self.change_selections(Default::default(), window, cx, |s| {
15149 s.move_heads_with(&mut |map, head, _| {
15150 (
15151 movement::line_end(map, head, action.stop_at_soft_wraps),
15152 SelectionGoal::None,
15153 )
15154 });
15155 })
15156 }
15157
15158 pub fn delete_to_end_of_line(
15159 &mut self,
15160 _: &DeleteToEndOfLine,
15161 window: &mut Window,
15162 cx: &mut Context<Self>,
15163 ) {
15164 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15165 self.transact(window, cx, |this, window, cx| {
15166 this.select_to_end_of_line(
15167 &SelectToEndOfLine {
15168 stop_at_soft_wraps: false,
15169 },
15170 window,
15171 cx,
15172 );
15173 this.delete(&Delete, window, cx);
15174 });
15175 }
15176
15177 pub fn cut_to_end_of_line(
15178 &mut self,
15179 action: &CutToEndOfLine,
15180 window: &mut Window,
15181 cx: &mut Context<Self>,
15182 ) {
15183 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15184 self.transact(window, cx, |this, window, cx| {
15185 this.select_to_end_of_line(
15186 &SelectToEndOfLine {
15187 stop_at_soft_wraps: false,
15188 },
15189 window,
15190 cx,
15191 );
15192 if !action.stop_at_newlines {
15193 this.change_selections(Default::default(), window, cx, |s| {
15194 s.move_with(&mut |_, sel| {
15195 if sel.is_empty() {
15196 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15197 }
15198 });
15199 });
15200 }
15201 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15202 let item = this.cut_common(false, window, cx);
15203 cx.write_to_clipboard(item);
15204 });
15205 }
15206
15207 pub fn move_to_start_of_paragraph(
15208 &mut self,
15209 _: &MoveToStartOfParagraph,
15210 window: &mut Window,
15211 cx: &mut Context<Self>,
15212 ) {
15213 if matches!(self.mode, EditorMode::SingleLine) {
15214 cx.propagate();
15215 return;
15216 }
15217 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15218 self.change_selections(Default::default(), window, cx, |s| {
15219 s.move_with(&mut |map, selection| {
15220 selection.collapse_to(
15221 movement::start_of_paragraph(map, selection.head(), 1),
15222 SelectionGoal::None,
15223 )
15224 });
15225 })
15226 }
15227
15228 pub fn move_to_end_of_paragraph(
15229 &mut self,
15230 _: &MoveToEndOfParagraph,
15231 window: &mut Window,
15232 cx: &mut Context<Self>,
15233 ) {
15234 if matches!(self.mode, EditorMode::SingleLine) {
15235 cx.propagate();
15236 return;
15237 }
15238 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15239 self.change_selections(Default::default(), window, cx, |s| {
15240 s.move_with(&mut |map, selection| {
15241 selection.collapse_to(
15242 movement::end_of_paragraph(map, selection.head(), 1),
15243 SelectionGoal::None,
15244 )
15245 });
15246 })
15247 }
15248
15249 pub fn select_to_start_of_paragraph(
15250 &mut self,
15251 _: &SelectToStartOfParagraph,
15252 window: &mut Window,
15253 cx: &mut Context<Self>,
15254 ) {
15255 if matches!(self.mode, EditorMode::SingleLine) {
15256 cx.propagate();
15257 return;
15258 }
15259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15260 self.change_selections(Default::default(), window, cx, |s| {
15261 s.move_heads_with(&mut |map, head, _| {
15262 (
15263 movement::start_of_paragraph(map, head, 1),
15264 SelectionGoal::None,
15265 )
15266 });
15267 })
15268 }
15269
15270 pub fn select_to_end_of_paragraph(
15271 &mut self,
15272 _: &SelectToEndOfParagraph,
15273 window: &mut Window,
15274 cx: &mut Context<Self>,
15275 ) {
15276 if matches!(self.mode, EditorMode::SingleLine) {
15277 cx.propagate();
15278 return;
15279 }
15280 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15281 self.change_selections(Default::default(), window, cx, |s| {
15282 s.move_heads_with(&mut |map, head, _| {
15283 (
15284 movement::end_of_paragraph(map, head, 1),
15285 SelectionGoal::None,
15286 )
15287 });
15288 })
15289 }
15290
15291 pub fn move_to_start_of_excerpt(
15292 &mut self,
15293 _: &MoveToStartOfExcerpt,
15294 window: &mut Window,
15295 cx: &mut Context<Self>,
15296 ) {
15297 if matches!(self.mode, EditorMode::SingleLine) {
15298 cx.propagate();
15299 return;
15300 }
15301 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15302 self.change_selections(Default::default(), window, cx, |s| {
15303 s.move_with(&mut |map, selection| {
15304 selection.collapse_to(
15305 movement::start_of_excerpt(
15306 map,
15307 selection.head(),
15308 workspace::searchable::Direction::Prev,
15309 ),
15310 SelectionGoal::None,
15311 )
15312 });
15313 })
15314 }
15315
15316 pub fn move_to_start_of_next_excerpt(
15317 &mut self,
15318 _: &MoveToStartOfNextExcerpt,
15319 window: &mut Window,
15320 cx: &mut Context<Self>,
15321 ) {
15322 if matches!(self.mode, EditorMode::SingleLine) {
15323 cx.propagate();
15324 return;
15325 }
15326
15327 self.change_selections(Default::default(), window, cx, |s| {
15328 s.move_with(&mut |map, selection| {
15329 selection.collapse_to(
15330 movement::start_of_excerpt(
15331 map,
15332 selection.head(),
15333 workspace::searchable::Direction::Next,
15334 ),
15335 SelectionGoal::None,
15336 )
15337 });
15338 })
15339 }
15340
15341 pub fn move_to_end_of_excerpt(
15342 &mut self,
15343 _: &MoveToEndOfExcerpt,
15344 window: &mut Window,
15345 cx: &mut Context<Self>,
15346 ) {
15347 if matches!(self.mode, EditorMode::SingleLine) {
15348 cx.propagate();
15349 return;
15350 }
15351 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15352 self.change_selections(Default::default(), window, cx, |s| {
15353 s.move_with(&mut |map, selection| {
15354 selection.collapse_to(
15355 movement::end_of_excerpt(
15356 map,
15357 selection.head(),
15358 workspace::searchable::Direction::Next,
15359 ),
15360 SelectionGoal::None,
15361 )
15362 });
15363 })
15364 }
15365
15366 pub fn move_to_end_of_previous_excerpt(
15367 &mut self,
15368 _: &MoveToEndOfPreviousExcerpt,
15369 window: &mut Window,
15370 cx: &mut Context<Self>,
15371 ) {
15372 if matches!(self.mode, EditorMode::SingleLine) {
15373 cx.propagate();
15374 return;
15375 }
15376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15377 self.change_selections(Default::default(), window, cx, |s| {
15378 s.move_with(&mut |map, selection| {
15379 selection.collapse_to(
15380 movement::end_of_excerpt(
15381 map,
15382 selection.head(),
15383 workspace::searchable::Direction::Prev,
15384 ),
15385 SelectionGoal::None,
15386 )
15387 });
15388 })
15389 }
15390
15391 pub fn select_to_start_of_excerpt(
15392 &mut self,
15393 _: &SelectToStartOfExcerpt,
15394 window: &mut Window,
15395 cx: &mut Context<Self>,
15396 ) {
15397 if matches!(self.mode, EditorMode::SingleLine) {
15398 cx.propagate();
15399 return;
15400 }
15401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15402 self.change_selections(Default::default(), window, cx, |s| {
15403 s.move_heads_with(&mut |map, head, _| {
15404 (
15405 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15406 SelectionGoal::None,
15407 )
15408 });
15409 })
15410 }
15411
15412 pub fn select_to_start_of_next_excerpt(
15413 &mut self,
15414 _: &SelectToStartOfNextExcerpt,
15415 window: &mut Window,
15416 cx: &mut Context<Self>,
15417 ) {
15418 if matches!(self.mode, EditorMode::SingleLine) {
15419 cx.propagate();
15420 return;
15421 }
15422 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15423 self.change_selections(Default::default(), window, cx, |s| {
15424 s.move_heads_with(&mut |map, head, _| {
15425 (
15426 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15427 SelectionGoal::None,
15428 )
15429 });
15430 })
15431 }
15432
15433 pub fn select_to_end_of_excerpt(
15434 &mut self,
15435 _: &SelectToEndOfExcerpt,
15436 window: &mut Window,
15437 cx: &mut Context<Self>,
15438 ) {
15439 if matches!(self.mode, EditorMode::SingleLine) {
15440 cx.propagate();
15441 return;
15442 }
15443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15444 self.change_selections(Default::default(), window, cx, |s| {
15445 s.move_heads_with(&mut |map, head, _| {
15446 (
15447 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15448 SelectionGoal::None,
15449 )
15450 });
15451 })
15452 }
15453
15454 pub fn select_to_end_of_previous_excerpt(
15455 &mut self,
15456 _: &SelectToEndOfPreviousExcerpt,
15457 window: &mut Window,
15458 cx: &mut Context<Self>,
15459 ) {
15460 if matches!(self.mode, EditorMode::SingleLine) {
15461 cx.propagate();
15462 return;
15463 }
15464 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15465 self.change_selections(Default::default(), window, cx, |s| {
15466 s.move_heads_with(&mut |map, head, _| {
15467 (
15468 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15469 SelectionGoal::None,
15470 )
15471 });
15472 })
15473 }
15474
15475 pub fn move_to_beginning(
15476 &mut self,
15477 _: &MoveToBeginning,
15478 window: &mut Window,
15479 cx: &mut Context<Self>,
15480 ) {
15481 if matches!(self.mode, EditorMode::SingleLine) {
15482 cx.propagate();
15483 return;
15484 }
15485 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15486 self.change_selections(Default::default(), window, cx, |s| {
15487 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15488 });
15489 }
15490
15491 pub fn select_to_beginning(
15492 &mut self,
15493 _: &SelectToBeginning,
15494 window: &mut Window,
15495 cx: &mut Context<Self>,
15496 ) {
15497 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15498 selection.set_head(Point::zero(), SelectionGoal::None);
15499 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15500 self.change_selections(Default::default(), window, cx, |s| {
15501 s.select(vec![selection]);
15502 });
15503 }
15504
15505 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15506 if matches!(self.mode, EditorMode::SingleLine) {
15507 cx.propagate();
15508 return;
15509 }
15510 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15511 let cursor = self.buffer.read(cx).read(cx).len();
15512 self.change_selections(Default::default(), window, cx, |s| {
15513 s.select_ranges(vec![cursor..cursor])
15514 });
15515 }
15516
15517 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15518 self.nav_history = nav_history;
15519 }
15520
15521 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15522 self.nav_history.as_ref()
15523 }
15524
15525 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15526 self.push_to_nav_history(
15527 self.selections.newest_anchor().head(),
15528 None,
15529 false,
15530 true,
15531 cx,
15532 );
15533 }
15534
15535 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15536 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15537 let buffer = self.buffer.read(cx).read(cx);
15538 let cursor_position = cursor_anchor.to_point(&buffer);
15539 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15540 let scroll_top_row = scroll_anchor.top_row(&buffer);
15541 drop(buffer);
15542
15543 NavigationData {
15544 cursor_anchor,
15545 cursor_position,
15546 scroll_anchor,
15547 scroll_top_row,
15548 }
15549 }
15550
15551 fn navigation_entry(
15552 &self,
15553 cursor_anchor: Anchor,
15554 cx: &mut Context<Self>,
15555 ) -> Option<NavigationEntry> {
15556 let Some(history) = self.nav_history.clone() else {
15557 return None;
15558 };
15559 let data = self.navigation_data(cursor_anchor, cx);
15560 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15561 }
15562
15563 fn push_to_nav_history(
15564 &mut self,
15565 cursor_anchor: Anchor,
15566 new_position: Option<Point>,
15567 is_deactivate: bool,
15568 always: bool,
15569 cx: &mut Context<Self>,
15570 ) {
15571 let data = self.navigation_data(cursor_anchor, cx);
15572 if let Some(nav_history) = self.nav_history.as_mut() {
15573 if let Some(new_position) = new_position {
15574 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15575 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15576 return;
15577 }
15578 }
15579
15580 let cursor_row = data.cursor_position.row;
15581 nav_history.push(Some(data), Some(cursor_row), cx);
15582 cx.emit(EditorEvent::PushedToNavHistory {
15583 anchor: cursor_anchor,
15584 is_deactivate,
15585 })
15586 }
15587 }
15588
15589 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15591 let buffer = self.buffer.read(cx).snapshot(cx);
15592 let mut selection = self
15593 .selections
15594 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15595 selection.set_head(buffer.len(), SelectionGoal::None);
15596 self.change_selections(Default::default(), window, cx, |s| {
15597 s.select(vec![selection]);
15598 });
15599 }
15600
15601 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15602 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15603 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15604 s.select_ranges([Anchor::min()..Anchor::max()]);
15605 });
15606 }
15607
15608 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15609 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15610 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15611 let mut selections = self.selections.all::<Point>(&display_map);
15612 let max_point = display_map.buffer_snapshot().max_point();
15613 for selection in &mut selections {
15614 let rows = selection.spanned_rows(true, &display_map);
15615 selection.start = Point::new(rows.start.0, 0);
15616 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15617 selection.reversed = false;
15618 }
15619 self.change_selections(Default::default(), window, cx, |s| {
15620 s.select(selections);
15621 });
15622 }
15623
15624 pub fn split_selection_into_lines(
15625 &mut self,
15626 action: &SplitSelectionIntoLines,
15627 window: &mut Window,
15628 cx: &mut Context<Self>,
15629 ) {
15630 let selections = self
15631 .selections
15632 .all::<Point>(&self.display_snapshot(cx))
15633 .into_iter()
15634 .map(|selection| selection.start..selection.end)
15635 .collect::<Vec<_>>();
15636 self.unfold_ranges(&selections, true, false, cx);
15637
15638 let mut new_selection_ranges = Vec::new();
15639 {
15640 let buffer = self.buffer.read(cx).read(cx);
15641 for selection in selections {
15642 for row in selection.start.row..selection.end.row {
15643 let line_start = Point::new(row, 0);
15644 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15645
15646 if action.keep_selections {
15647 // Keep the selection range for each line
15648 let selection_start = if row == selection.start.row {
15649 selection.start
15650 } else {
15651 line_start
15652 };
15653 new_selection_ranges.push(selection_start..line_end);
15654 } else {
15655 // Collapse to cursor at end of line
15656 new_selection_ranges.push(line_end..line_end);
15657 }
15658 }
15659
15660 let is_multiline_selection = selection.start.row != selection.end.row;
15661 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15662 // so this action feels more ergonomic when paired with other selection operations
15663 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15664 if !should_skip_last {
15665 if action.keep_selections {
15666 if is_multiline_selection {
15667 let line_start = Point::new(selection.end.row, 0);
15668 new_selection_ranges.push(line_start..selection.end);
15669 } else {
15670 new_selection_ranges.push(selection.start..selection.end);
15671 }
15672 } else {
15673 new_selection_ranges.push(selection.end..selection.end);
15674 }
15675 }
15676 }
15677 }
15678 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15679 s.select_ranges(new_selection_ranges);
15680 });
15681 }
15682
15683 pub fn add_selection_above(
15684 &mut self,
15685 action: &AddSelectionAbove,
15686 window: &mut Window,
15687 cx: &mut Context<Self>,
15688 ) {
15689 self.add_selection(true, action.skip_soft_wrap, window, cx);
15690 }
15691
15692 pub fn add_selection_below(
15693 &mut self,
15694 action: &AddSelectionBelow,
15695 window: &mut Window,
15696 cx: &mut Context<Self>,
15697 ) {
15698 self.add_selection(false, action.skip_soft_wrap, window, cx);
15699 }
15700
15701 fn add_selection(
15702 &mut self,
15703 above: bool,
15704 skip_soft_wrap: bool,
15705 window: &mut Window,
15706 cx: &mut Context<Self>,
15707 ) {
15708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15709
15710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15711 let all_selections = self.selections.all::<Point>(&display_map);
15712 let text_layout_details = self.text_layout_details(window, cx);
15713
15714 let (mut columnar_selections, new_selections_to_columnarize) = {
15715 if let Some(state) = self.add_selections_state.as_ref() {
15716 let columnar_selection_ids: HashSet<_> = state
15717 .groups
15718 .iter()
15719 .flat_map(|group| group.stack.iter())
15720 .copied()
15721 .collect();
15722
15723 all_selections
15724 .into_iter()
15725 .partition(|s| columnar_selection_ids.contains(&s.id))
15726 } else {
15727 (Vec::new(), all_selections)
15728 }
15729 };
15730
15731 let mut state = self
15732 .add_selections_state
15733 .take()
15734 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15735
15736 for selection in new_selections_to_columnarize {
15737 let range = selection.display_range(&display_map).sorted();
15738 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15739 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15740 let positions = start_x.min(end_x)..start_x.max(end_x);
15741 let mut stack = Vec::new();
15742 for row in range.start.row().0..=range.end.row().0 {
15743 if let Some(selection) = self.selections.build_columnar_selection(
15744 &display_map,
15745 DisplayRow(row),
15746 &positions,
15747 selection.reversed,
15748 &text_layout_details,
15749 ) {
15750 stack.push(selection.id);
15751 columnar_selections.push(selection);
15752 }
15753 }
15754 if !stack.is_empty() {
15755 if above {
15756 stack.reverse();
15757 }
15758 state.groups.push(AddSelectionsGroup { above, stack });
15759 }
15760 }
15761
15762 let mut final_selections = Vec::new();
15763 let end_row = if above {
15764 DisplayRow(0)
15765 } else {
15766 display_map.max_point().row()
15767 };
15768
15769 // When `skip_soft_wrap` is true, we use UTF-16 columns instead of pixel
15770 // positions to place new selections, so we need to keep track of the
15771 // column range of the oldest selection in each group, because
15772 // intermediate selections may have been clamped to shorter lines.
15773 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15774 let mut map = HashMap::default();
15775 for group in state.groups.iter() {
15776 if let Some(oldest_id) = group.stack.first() {
15777 if let Some(oldest_selection) =
15778 columnar_selections.iter().find(|s| s.id == *oldest_id)
15779 {
15780 let snapshot = display_map.buffer_snapshot();
15781 let start_col =
15782 snapshot.point_to_point_utf16(oldest_selection.start).column;
15783 let end_col = snapshot.point_to_point_utf16(oldest_selection.end).column;
15784 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15785 for id in &group.stack {
15786 map.insert(*id, goal_columns.clone());
15787 }
15788 }
15789 }
15790 }
15791 map
15792 } else {
15793 HashMap::default()
15794 };
15795
15796 let mut last_added_item_per_group = HashMap::default();
15797 for group in state.groups.iter_mut() {
15798 if let Some(last_id) = group.stack.last() {
15799 last_added_item_per_group.insert(*last_id, group);
15800 }
15801 }
15802
15803 for selection in columnar_selections {
15804 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15805 if above == group.above {
15806 let range = selection.display_range(&display_map).sorted();
15807 debug_assert_eq!(range.start.row(), range.end.row());
15808 let row = range.start.row();
15809 let positions =
15810 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15811 Pixels::from(start)..Pixels::from(end)
15812 } else {
15813 let start_x =
15814 display_map.x_for_display_point(range.start, &text_layout_details);
15815 let end_x =
15816 display_map.x_for_display_point(range.end, &text_layout_details);
15817 start_x.min(end_x)..start_x.max(end_x)
15818 };
15819
15820 let maybe_new_selection = if skip_soft_wrap {
15821 let goal_columns = goal_columns_by_selection_id
15822 .remove(&selection.id)
15823 .unwrap_or_else(|| {
15824 let snapshot = display_map.buffer_snapshot();
15825 let start_col =
15826 snapshot.point_to_point_utf16(selection.start).column;
15827 let end_col = snapshot.point_to_point_utf16(selection.end).column;
15828 start_col.min(end_col)..start_col.max(end_col)
15829 });
15830 self.selections.find_next_columnar_selection_by_buffer_row(
15831 &display_map,
15832 row,
15833 end_row,
15834 above,
15835 &goal_columns,
15836 selection.reversed,
15837 &text_layout_details,
15838 )
15839 } else {
15840 self.selections.find_next_columnar_selection_by_display_row(
15841 &display_map,
15842 row,
15843 end_row,
15844 above,
15845 &positions,
15846 selection.reversed,
15847 &text_layout_details,
15848 )
15849 };
15850
15851 if let Some(new_selection) = maybe_new_selection {
15852 group.stack.push(new_selection.id);
15853 if above {
15854 final_selections.push(new_selection);
15855 final_selections.push(selection);
15856 } else {
15857 final_selections.push(selection);
15858 final_selections.push(new_selection);
15859 }
15860 } else {
15861 final_selections.push(selection);
15862 }
15863 } else {
15864 group.stack.pop();
15865 }
15866 } else {
15867 final_selections.push(selection);
15868 }
15869 }
15870
15871 self.change_selections(Default::default(), window, cx, |s| {
15872 s.select(final_selections);
15873 });
15874
15875 let final_selection_ids: HashSet<_> = self
15876 .selections
15877 .all::<Point>(&display_map)
15878 .iter()
15879 .map(|s| s.id)
15880 .collect();
15881 state.groups.retain_mut(|group| {
15882 // selections might get merged above so we remove invalid items from stacks
15883 group.stack.retain(|id| final_selection_ids.contains(id));
15884
15885 // single selection in stack can be treated as initial state
15886 group.stack.len() > 1
15887 });
15888
15889 if !state.groups.is_empty() {
15890 self.add_selections_state = Some(state);
15891 }
15892 }
15893
15894 pub fn insert_snippet_at_selections(
15895 &mut self,
15896 action: &InsertSnippet,
15897 window: &mut Window,
15898 cx: &mut Context<Self>,
15899 ) {
15900 self.try_insert_snippet_at_selections(action, window, cx)
15901 .log_err();
15902 }
15903
15904 fn try_insert_snippet_at_selections(
15905 &mut self,
15906 action: &InsertSnippet,
15907 window: &mut Window,
15908 cx: &mut Context<Self>,
15909 ) -> Result<()> {
15910 let insertion_ranges = self
15911 .selections
15912 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15913 .into_iter()
15914 .map(|selection| selection.range())
15915 .collect_vec();
15916
15917 let snippet = if let Some(snippet_body) = &action.snippet {
15918 if action.language.is_none() && action.name.is_none() {
15919 Snippet::parse(snippet_body)?
15920 } else {
15921 bail!("`snippet` is mutually exclusive with `language` and `name`")
15922 }
15923 } else if let Some(name) = &action.name {
15924 let project = self.project().context("no project")?;
15925 let snippet_store = project.read(cx).snippets().read(cx);
15926 let snippet = snippet_store
15927 .snippets_for(action.language.clone(), cx)
15928 .into_iter()
15929 .find(|snippet| snippet.name == *name)
15930 .context("snippet not found")?;
15931 Snippet::parse(&snippet.body)?
15932 } else {
15933 // todo(andrew): open modal to select snippet
15934 bail!("`name` or `snippet` is required")
15935 };
15936
15937 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15938 }
15939
15940 fn select_match_ranges(
15941 &mut self,
15942 range: Range<MultiBufferOffset>,
15943 reversed: bool,
15944 replace_newest: bool,
15945 auto_scroll: Option<Autoscroll>,
15946 window: &mut Window,
15947 cx: &mut Context<Editor>,
15948 ) {
15949 self.unfold_ranges(
15950 std::slice::from_ref(&range),
15951 false,
15952 auto_scroll.is_some(),
15953 cx,
15954 );
15955 let effects = if let Some(scroll) = auto_scroll {
15956 SelectionEffects::scroll(scroll)
15957 } else {
15958 SelectionEffects::no_scroll()
15959 };
15960 self.change_selections(effects, window, cx, |s| {
15961 if replace_newest {
15962 s.delete(s.newest_anchor().id);
15963 }
15964 if reversed {
15965 s.insert_range(range.end..range.start);
15966 } else {
15967 s.insert_range(range);
15968 }
15969 });
15970 }
15971
15972 pub fn select_next_match_internal(
15973 &mut self,
15974 display_map: &DisplaySnapshot,
15975 replace_newest: bool,
15976 autoscroll: Option<Autoscroll>,
15977 window: &mut Window,
15978 cx: &mut Context<Self>,
15979 ) -> Result<()> {
15980 let buffer = display_map.buffer_snapshot();
15981 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15982 if let Some(mut select_next_state) = self.select_next_state.take() {
15983 let query = &select_next_state.query;
15984 if !select_next_state.done {
15985 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15986 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15987 let mut next_selected_range = None;
15988
15989 let bytes_after_last_selection =
15990 buffer.bytes_in_range(last_selection.end..buffer.len());
15991 let bytes_before_first_selection =
15992 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15993 let query_matches = query
15994 .stream_find_iter(bytes_after_last_selection)
15995 .map(|result| (last_selection.end, result))
15996 .chain(
15997 query
15998 .stream_find_iter(bytes_before_first_selection)
15999 .map(|result| (MultiBufferOffset(0), result)),
16000 );
16001
16002 for (start_offset, query_match) in query_matches {
16003 let query_match = query_match.unwrap(); // can only fail due to I/O
16004 let offset_range =
16005 start_offset + query_match.start()..start_offset + query_match.end();
16006
16007 if !select_next_state.wordwise
16008 || (!buffer.is_inside_word(offset_range.start, None)
16009 && !buffer.is_inside_word(offset_range.end, None))
16010 {
16011 let idx = selections
16012 .partition_point(|selection| selection.end <= offset_range.start);
16013 let overlaps = selections
16014 .get(idx)
16015 .map_or(false, |selection| selection.start < offset_range.end);
16016
16017 if !overlaps {
16018 next_selected_range = Some(offset_range);
16019 break;
16020 }
16021 }
16022 }
16023
16024 if let Some(next_selected_range) = next_selected_range {
16025 self.select_match_ranges(
16026 next_selected_range,
16027 last_selection.reversed,
16028 replace_newest,
16029 autoscroll,
16030 window,
16031 cx,
16032 );
16033 } else {
16034 select_next_state.done = true;
16035 }
16036 }
16037
16038 self.select_next_state = Some(select_next_state);
16039 } else {
16040 let mut only_carets = true;
16041 let mut same_text_selected = true;
16042 let mut selected_text = None;
16043
16044 let mut selections_iter = selections.iter().peekable();
16045 while let Some(selection) = selections_iter.next() {
16046 if selection.start != selection.end {
16047 only_carets = false;
16048 }
16049
16050 if same_text_selected {
16051 if selected_text.is_none() {
16052 selected_text =
16053 Some(buffer.text_for_range(selection.range()).collect::<String>());
16054 }
16055
16056 if let Some(next_selection) = selections_iter.peek() {
16057 if next_selection.len() == selection.len() {
16058 let next_selected_text = buffer
16059 .text_for_range(next_selection.range())
16060 .collect::<String>();
16061 if Some(next_selected_text) != selected_text {
16062 same_text_selected = false;
16063 selected_text = None;
16064 }
16065 } else {
16066 same_text_selected = false;
16067 selected_text = None;
16068 }
16069 }
16070 }
16071 }
16072
16073 if only_carets {
16074 for selection in &mut selections {
16075 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16076 selection.start = word_range.start;
16077 selection.end = word_range.end;
16078 selection.goal = SelectionGoal::None;
16079 selection.reversed = false;
16080 self.select_match_ranges(
16081 selection.start..selection.end,
16082 selection.reversed,
16083 replace_newest,
16084 autoscroll,
16085 window,
16086 cx,
16087 );
16088 }
16089
16090 if selections.len() == 1 {
16091 let selection = selections
16092 .last()
16093 .expect("ensured that there's only one selection");
16094 let query = buffer
16095 .text_for_range(selection.start..selection.end)
16096 .collect::<String>();
16097 let is_empty = query.is_empty();
16098 let select_state = SelectNextState {
16099 query: self.build_query(&[query], cx)?,
16100 wordwise: true,
16101 done: is_empty,
16102 };
16103 self.select_next_state = Some(select_state);
16104 } else {
16105 self.select_next_state = None;
16106 }
16107 } else if let Some(selected_text) = selected_text {
16108 self.select_next_state = Some(SelectNextState {
16109 query: self.build_query(&[selected_text], cx)?,
16110 wordwise: false,
16111 done: false,
16112 });
16113 self.select_next_match_internal(
16114 display_map,
16115 replace_newest,
16116 autoscroll,
16117 window,
16118 cx,
16119 )?;
16120 }
16121 }
16122 Ok(())
16123 }
16124
16125 pub fn select_all_matches(
16126 &mut self,
16127 _action: &SelectAllMatches,
16128 window: &mut Window,
16129 cx: &mut Context<Self>,
16130 ) -> Result<()> {
16131 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16132
16133 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16134
16135 self.select_next_match_internal(&display_map, false, None, window, cx)?;
16136 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
16137 else {
16138 return Ok(());
16139 };
16140
16141 let mut new_selections = Vec::new();
16142 let initial_selection = self.selections.oldest::<MultiBufferOffset>(&display_map);
16143 let reversed = initial_selection.reversed;
16144 let buffer = display_map.buffer_snapshot();
16145 let query_matches = select_next_state
16146 .query
16147 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
16148
16149 for query_match in query_matches.into_iter() {
16150 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
16151 let offset_range = if reversed {
16152 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
16153 } else {
16154 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
16155 };
16156
16157 let is_partial_word_match = select_next_state.wordwise
16158 && (buffer.is_inside_word(offset_range.start, None)
16159 || buffer.is_inside_word(offset_range.end, None));
16160
16161 let is_initial_selection = MultiBufferOffset(query_match.start())
16162 == initial_selection.start
16163 && MultiBufferOffset(query_match.end()) == initial_selection.end;
16164
16165 if !is_partial_word_match && !is_initial_selection {
16166 new_selections.push(offset_range);
16167 }
16168 }
16169
16170 // Ensure that the initial range is the last selection, as
16171 // `MutableSelectionsCollection::select_ranges` makes the last selection
16172 // the newest selection, which the editor then relies on as the primary
16173 // cursor for scroll targeting. Without this, the last match would then
16174 // be automatically focused when the user started editing the selected
16175 // matches.
16176 let initial_directed_range = if reversed {
16177 initial_selection.end..initial_selection.start
16178 } else {
16179 initial_selection.start..initial_selection.end
16180 };
16181 new_selections.push(initial_directed_range);
16182
16183 select_next_state.done = true;
16184 self.unfold_ranges(&new_selections, false, false, cx);
16185 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16186 selections.select_ranges(new_selections)
16187 });
16188
16189 Ok(())
16190 }
16191
16192 pub fn select_next(
16193 &mut self,
16194 action: &SelectNext,
16195 window: &mut Window,
16196 cx: &mut Context<Self>,
16197 ) -> Result<()> {
16198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16199 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16200 self.select_next_match_internal(
16201 &display_map,
16202 action.replace_newest,
16203 Some(Autoscroll::newest()),
16204 window,
16205 cx,
16206 )
16207 }
16208
16209 pub fn select_previous(
16210 &mut self,
16211 action: &SelectPrevious,
16212 window: &mut Window,
16213 cx: &mut Context<Self>,
16214 ) -> Result<()> {
16215 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16216 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16217 let buffer = display_map.buffer_snapshot();
16218 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16219 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16220 let query = &select_prev_state.query;
16221 if !select_prev_state.done {
16222 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16223 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16224 let mut next_selected_range = None;
16225 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16226 let bytes_before_last_selection =
16227 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16228 let bytes_after_first_selection =
16229 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16230 let query_matches = query
16231 .stream_find_iter(bytes_before_last_selection)
16232 .map(|result| (last_selection.start, result))
16233 .chain(
16234 query
16235 .stream_find_iter(bytes_after_first_selection)
16236 .map(|result| (buffer.len(), result)),
16237 );
16238 for (end_offset, query_match) in query_matches {
16239 let query_match = query_match.unwrap(); // can only fail due to I/O
16240 let offset_range =
16241 end_offset - query_match.end()..end_offset - query_match.start();
16242
16243 if !select_prev_state.wordwise
16244 || (!buffer.is_inside_word(offset_range.start, None)
16245 && !buffer.is_inside_word(offset_range.end, None))
16246 {
16247 next_selected_range = Some(offset_range);
16248 break;
16249 }
16250 }
16251
16252 if let Some(next_selected_range) = next_selected_range {
16253 self.select_match_ranges(
16254 next_selected_range,
16255 last_selection.reversed,
16256 action.replace_newest,
16257 Some(Autoscroll::newest()),
16258 window,
16259 cx,
16260 );
16261 } else {
16262 select_prev_state.done = true;
16263 }
16264 }
16265
16266 self.select_prev_state = Some(select_prev_state);
16267 } else {
16268 let mut only_carets = true;
16269 let mut same_text_selected = true;
16270 let mut selected_text = None;
16271
16272 let mut selections_iter = selections.iter().peekable();
16273 while let Some(selection) = selections_iter.next() {
16274 if selection.start != selection.end {
16275 only_carets = false;
16276 }
16277
16278 if same_text_selected {
16279 if selected_text.is_none() {
16280 selected_text =
16281 Some(buffer.text_for_range(selection.range()).collect::<String>());
16282 }
16283
16284 if let Some(next_selection) = selections_iter.peek() {
16285 if next_selection.len() == selection.len() {
16286 let next_selected_text = buffer
16287 .text_for_range(next_selection.range())
16288 .collect::<String>();
16289 if Some(next_selected_text) != selected_text {
16290 same_text_selected = false;
16291 selected_text = None;
16292 }
16293 } else {
16294 same_text_selected = false;
16295 selected_text = None;
16296 }
16297 }
16298 }
16299 }
16300
16301 if only_carets {
16302 for selection in &mut selections {
16303 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16304 selection.start = word_range.start;
16305 selection.end = word_range.end;
16306 selection.goal = SelectionGoal::None;
16307 selection.reversed = false;
16308 self.select_match_ranges(
16309 selection.start..selection.end,
16310 selection.reversed,
16311 action.replace_newest,
16312 Some(Autoscroll::newest()),
16313 window,
16314 cx,
16315 );
16316 }
16317 if selections.len() == 1 {
16318 let selection = selections
16319 .last()
16320 .expect("ensured that there's only one selection");
16321 let query = buffer
16322 .text_for_range(selection.start..selection.end)
16323 .collect::<String>();
16324 let is_empty = query.is_empty();
16325 let select_state = SelectNextState {
16326 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16327 wordwise: true,
16328 done: is_empty,
16329 };
16330 self.select_prev_state = Some(select_state);
16331 } else {
16332 self.select_prev_state = None;
16333 }
16334 } else if let Some(selected_text) = selected_text {
16335 self.select_prev_state = Some(SelectNextState {
16336 query: self
16337 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16338 wordwise: false,
16339 done: false,
16340 });
16341 self.select_previous(action, window, cx)?;
16342 }
16343 }
16344 Ok(())
16345 }
16346
16347 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16348 /// setting the case sensitivity based on the global
16349 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16350 /// editor's settings.
16351 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16352 where
16353 I: IntoIterator<Item = P>,
16354 P: AsRef<[u8]>,
16355 {
16356 let case_sensitive = self
16357 .select_next_is_case_sensitive
16358 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16359
16360 let mut builder = AhoCorasickBuilder::new();
16361 builder.ascii_case_insensitive(!case_sensitive);
16362 builder.build(patterns)
16363 }
16364
16365 pub fn find_next_match(
16366 &mut self,
16367 _: &FindNextMatch,
16368 window: &mut Window,
16369 cx: &mut Context<Self>,
16370 ) -> Result<()> {
16371 let selections = self.selections.disjoint_anchors_arc();
16372 match selections.first() {
16373 Some(first) if selections.len() >= 2 => {
16374 self.change_selections(Default::default(), window, cx, |s| {
16375 s.select_ranges([first.range()]);
16376 });
16377 }
16378 _ => self.select_next(
16379 &SelectNext {
16380 replace_newest: true,
16381 },
16382 window,
16383 cx,
16384 )?,
16385 }
16386 Ok(())
16387 }
16388
16389 pub fn find_previous_match(
16390 &mut self,
16391 _: &FindPreviousMatch,
16392 window: &mut Window,
16393 cx: &mut Context<Self>,
16394 ) -> Result<()> {
16395 let selections = self.selections.disjoint_anchors_arc();
16396 match selections.last() {
16397 Some(last) if selections.len() >= 2 => {
16398 self.change_selections(Default::default(), window, cx, |s| {
16399 s.select_ranges([last.range()]);
16400 });
16401 }
16402 _ => self.select_previous(
16403 &SelectPrevious {
16404 replace_newest: true,
16405 },
16406 window,
16407 cx,
16408 )?,
16409 }
16410 Ok(())
16411 }
16412
16413 pub fn toggle_comments(
16414 &mut self,
16415 action: &ToggleComments,
16416 window: &mut Window,
16417 cx: &mut Context<Self>,
16418 ) {
16419 if self.read_only(cx) {
16420 return;
16421 }
16422 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16423 let text_layout_details = &self.text_layout_details(window, cx);
16424 self.transact(window, cx, |this, window, cx| {
16425 let mut selections = this
16426 .selections
16427 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16428 let mut edits = Vec::new();
16429 let mut selection_edit_ranges = Vec::new();
16430 let mut last_toggled_row = None;
16431 let snapshot = this.buffer.read(cx).read(cx);
16432 let empty_str: Arc<str> = Arc::default();
16433 let mut suffixes_inserted = Vec::new();
16434 let ignore_indent = action.ignore_indent;
16435
16436 fn comment_prefix_range(
16437 snapshot: &MultiBufferSnapshot,
16438 row: MultiBufferRow,
16439 comment_prefix: &str,
16440 comment_prefix_whitespace: &str,
16441 ignore_indent: bool,
16442 ) -> Range<Point> {
16443 let indent_size = if ignore_indent {
16444 0
16445 } else {
16446 snapshot.indent_size_for_line(row).len
16447 };
16448
16449 let start = Point::new(row.0, indent_size);
16450
16451 let mut line_bytes = snapshot
16452 .bytes_in_range(start..snapshot.max_point())
16453 .flatten()
16454 .copied();
16455
16456 // If this line currently begins with the line comment prefix, then record
16457 // the range containing the prefix.
16458 if line_bytes
16459 .by_ref()
16460 .take(comment_prefix.len())
16461 .eq(comment_prefix.bytes())
16462 {
16463 // Include any whitespace that matches the comment prefix.
16464 let matching_whitespace_len = line_bytes
16465 .zip(comment_prefix_whitespace.bytes())
16466 .take_while(|(a, b)| a == b)
16467 .count() as u32;
16468 let end = Point::new(
16469 start.row,
16470 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16471 );
16472 start..end
16473 } else {
16474 start..start
16475 }
16476 }
16477
16478 fn comment_suffix_range(
16479 snapshot: &MultiBufferSnapshot,
16480 row: MultiBufferRow,
16481 comment_suffix: &str,
16482 comment_suffix_has_leading_space: bool,
16483 ) -> Range<Point> {
16484 let end = Point::new(row.0, snapshot.line_len(row));
16485 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16486
16487 let mut line_end_bytes = snapshot
16488 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16489 .flatten()
16490 .copied();
16491
16492 let leading_space_len = if suffix_start_column > 0
16493 && line_end_bytes.next() == Some(b' ')
16494 && comment_suffix_has_leading_space
16495 {
16496 1
16497 } else {
16498 0
16499 };
16500
16501 // If this line currently begins with the line comment prefix, then record
16502 // the range containing the prefix.
16503 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16504 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16505 start..end
16506 } else {
16507 end..end
16508 }
16509 }
16510
16511 // TODO: Handle selections that cross excerpts
16512 for selection in &mut selections {
16513 let start_column = snapshot
16514 .indent_size_for_line(MultiBufferRow(selection.start.row))
16515 .len;
16516 let language = if let Some(language) =
16517 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16518 {
16519 language
16520 } else {
16521 continue;
16522 };
16523
16524 selection_edit_ranges.clear();
16525
16526 // If multiple selections contain a given row, avoid processing that
16527 // row more than once.
16528 let mut start_row = MultiBufferRow(selection.start.row);
16529 if last_toggled_row == Some(start_row) {
16530 start_row = start_row.next_row();
16531 }
16532 let end_row =
16533 if selection.end.row > selection.start.row && selection.end.column == 0 {
16534 MultiBufferRow(selection.end.row - 1)
16535 } else {
16536 MultiBufferRow(selection.end.row)
16537 };
16538 last_toggled_row = Some(end_row);
16539
16540 if start_row > end_row {
16541 continue;
16542 }
16543
16544 // If the language has line comments, toggle those.
16545 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16546
16547 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16548 if ignore_indent {
16549 full_comment_prefixes = full_comment_prefixes
16550 .into_iter()
16551 .map(|s| Arc::from(s.trim_end()))
16552 .collect();
16553 }
16554
16555 if !full_comment_prefixes.is_empty() {
16556 let first_prefix = full_comment_prefixes
16557 .first()
16558 .expect("prefixes is non-empty");
16559 let prefix_trimmed_lengths = full_comment_prefixes
16560 .iter()
16561 .map(|p| p.trim_end_matches(' ').len())
16562 .collect::<SmallVec<[usize; 4]>>();
16563
16564 let mut all_selection_lines_are_comments = true;
16565
16566 for row in start_row.0..=end_row.0 {
16567 let row = MultiBufferRow(row);
16568 if start_row < end_row && snapshot.is_line_blank(row) {
16569 continue;
16570 }
16571
16572 let prefix_range = full_comment_prefixes
16573 .iter()
16574 .zip(prefix_trimmed_lengths.iter().copied())
16575 .map(|(prefix, trimmed_prefix_len)| {
16576 comment_prefix_range(
16577 snapshot.deref(),
16578 row,
16579 &prefix[..trimmed_prefix_len],
16580 &prefix[trimmed_prefix_len..],
16581 ignore_indent,
16582 )
16583 })
16584 .max_by_key(|range| range.end.column - range.start.column)
16585 .expect("prefixes is non-empty");
16586
16587 if prefix_range.is_empty() {
16588 all_selection_lines_are_comments = false;
16589 }
16590
16591 selection_edit_ranges.push(prefix_range);
16592 }
16593
16594 if all_selection_lines_are_comments {
16595 edits.extend(
16596 selection_edit_ranges
16597 .iter()
16598 .cloned()
16599 .map(|range| (range, empty_str.clone())),
16600 );
16601 } else {
16602 let min_column = selection_edit_ranges
16603 .iter()
16604 .map(|range| range.start.column)
16605 .min()
16606 .unwrap_or(0);
16607 edits.extend(selection_edit_ranges.iter().map(|range| {
16608 let position = Point::new(range.start.row, min_column);
16609 (position..position, first_prefix.clone())
16610 }));
16611 }
16612 } else if let Some(BlockCommentConfig {
16613 start: full_comment_prefix,
16614 end: comment_suffix,
16615 ..
16616 }) = language.block_comment()
16617 {
16618 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16619 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16620 let prefix_range = comment_prefix_range(
16621 snapshot.deref(),
16622 start_row,
16623 comment_prefix,
16624 comment_prefix_whitespace,
16625 ignore_indent,
16626 );
16627 let suffix_range = comment_suffix_range(
16628 snapshot.deref(),
16629 end_row,
16630 comment_suffix.trim_start_matches(' '),
16631 comment_suffix.starts_with(' '),
16632 );
16633
16634 if prefix_range.is_empty() || suffix_range.is_empty() {
16635 edits.push((
16636 prefix_range.start..prefix_range.start,
16637 full_comment_prefix.clone(),
16638 ));
16639 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16640 suffixes_inserted.push((end_row, comment_suffix.len()));
16641 } else {
16642 edits.push((prefix_range, empty_str.clone()));
16643 edits.push((suffix_range, empty_str.clone()));
16644 }
16645 } else {
16646 continue;
16647 }
16648 }
16649
16650 drop(snapshot);
16651 this.buffer.update(cx, |buffer, cx| {
16652 buffer.edit(edits, None, cx);
16653 });
16654
16655 // Adjust selections so that they end before any comment suffixes that
16656 // were inserted.
16657 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16658 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16659 let snapshot = this.buffer.read(cx).read(cx);
16660 for selection in &mut selections {
16661 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16662 match row.cmp(&MultiBufferRow(selection.end.row)) {
16663 Ordering::Less => {
16664 suffixes_inserted.next();
16665 continue;
16666 }
16667 Ordering::Greater => break,
16668 Ordering::Equal => {
16669 if selection.end.column == snapshot.line_len(row) {
16670 if selection.is_empty() {
16671 selection.start.column -= suffix_len as u32;
16672 }
16673 selection.end.column -= suffix_len as u32;
16674 }
16675 break;
16676 }
16677 }
16678 }
16679 }
16680
16681 drop(snapshot);
16682 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16683
16684 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16685 let selections_on_single_row = selections.windows(2).all(|selections| {
16686 selections[0].start.row == selections[1].start.row
16687 && selections[0].end.row == selections[1].end.row
16688 && selections[0].start.row == selections[0].end.row
16689 });
16690 let selections_selecting = selections
16691 .iter()
16692 .any(|selection| selection.start != selection.end);
16693 let advance_downwards = action.advance_downwards
16694 && selections_on_single_row
16695 && !selections_selecting
16696 && !matches!(this.mode, EditorMode::SingleLine);
16697
16698 if advance_downwards {
16699 let snapshot = this.buffer.read(cx).snapshot(cx);
16700
16701 this.change_selections(Default::default(), window, cx, |s| {
16702 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16703 let mut point = display_point.to_point(display_snapshot);
16704 point.row += 1;
16705 point = snapshot.clip_point(point, Bias::Left);
16706 let display_point = point.to_display_point(display_snapshot);
16707 let goal = SelectionGoal::HorizontalPosition(
16708 display_snapshot
16709 .x_for_display_point(display_point, text_layout_details)
16710 .into(),
16711 );
16712 (display_point, goal)
16713 })
16714 });
16715 }
16716 });
16717 }
16718
16719 pub fn select_enclosing_symbol(
16720 &mut self,
16721 _: &SelectEnclosingSymbol,
16722 window: &mut Window,
16723 cx: &mut Context<Self>,
16724 ) {
16725 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16726
16727 let buffer = self.buffer.read(cx).snapshot(cx);
16728 let old_selections = self
16729 .selections
16730 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16731 .into_boxed_slice();
16732
16733 fn update_selection(
16734 selection: &Selection<MultiBufferOffset>,
16735 buffer_snap: &MultiBufferSnapshot,
16736 ) -> Option<Selection<MultiBufferOffset>> {
16737 let cursor = selection.head();
16738 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16739 for symbol in symbols.iter().rev() {
16740 let start = symbol.range.start.to_offset(buffer_snap);
16741 let end = symbol.range.end.to_offset(buffer_snap);
16742 let new_range = start..end;
16743 if start < selection.start || end > selection.end {
16744 return Some(Selection {
16745 id: selection.id,
16746 start: new_range.start,
16747 end: new_range.end,
16748 goal: SelectionGoal::None,
16749 reversed: selection.reversed,
16750 });
16751 }
16752 }
16753 None
16754 }
16755
16756 let mut selected_larger_symbol = false;
16757 let new_selections = old_selections
16758 .iter()
16759 .map(|selection| match update_selection(selection, &buffer) {
16760 Some(new_selection) => {
16761 if new_selection.range() != selection.range() {
16762 selected_larger_symbol = true;
16763 }
16764 new_selection
16765 }
16766 None => selection.clone(),
16767 })
16768 .collect::<Vec<_>>();
16769
16770 if selected_larger_symbol {
16771 self.change_selections(Default::default(), window, cx, |s| {
16772 s.select(new_selections);
16773 });
16774 }
16775 }
16776
16777 pub fn select_larger_syntax_node(
16778 &mut self,
16779 _: &SelectLargerSyntaxNode,
16780 window: &mut Window,
16781 cx: &mut Context<Self>,
16782 ) {
16783 let Some(visible_row_count) = self.visible_row_count() else {
16784 return;
16785 };
16786 let old_selections: Box<[_]> = self
16787 .selections
16788 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16789 .into();
16790 if old_selections.is_empty() {
16791 return;
16792 }
16793
16794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16795
16796 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16797 let buffer = self.buffer.read(cx).snapshot(cx);
16798
16799 let mut selected_larger_node = false;
16800 let mut new_selections = old_selections
16801 .iter()
16802 .map(|selection| {
16803 let old_range = selection.start..selection.end;
16804
16805 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16806 // manually select word at selection
16807 if ["string_content", "inline"].contains(&node.kind()) {
16808 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16809 // ignore if word is already selected
16810 if !word_range.is_empty() && old_range != word_range {
16811 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16812 // only select word if start and end point belongs to same word
16813 if word_range == last_word_range {
16814 selected_larger_node = true;
16815 return Selection {
16816 id: selection.id,
16817 start: word_range.start,
16818 end: word_range.end,
16819 goal: SelectionGoal::None,
16820 reversed: selection.reversed,
16821 };
16822 }
16823 }
16824 }
16825 }
16826
16827 let mut new_range = old_range.clone();
16828 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16829 new_range = range;
16830 if !node.is_named() {
16831 continue;
16832 }
16833 if !display_map.intersects_fold(new_range.start)
16834 && !display_map.intersects_fold(new_range.end)
16835 {
16836 break;
16837 }
16838 }
16839
16840 selected_larger_node |= new_range != old_range;
16841 Selection {
16842 id: selection.id,
16843 start: new_range.start,
16844 end: new_range.end,
16845 goal: SelectionGoal::None,
16846 reversed: selection.reversed,
16847 }
16848 })
16849 .collect::<Vec<_>>();
16850
16851 if !selected_larger_node {
16852 return; // don't put this call in the history
16853 }
16854
16855 // scroll based on transformation done to the last selection created by the user
16856 let (last_old, last_new) = old_selections
16857 .last()
16858 .zip(new_selections.last().cloned())
16859 .expect("old_selections isn't empty");
16860
16861 let is_selection_reversed = if new_selections.len() == 1 {
16862 let should_be_reversed = last_old.start != last_new.start;
16863 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
16864 should_be_reversed
16865 } else {
16866 last_new.reversed
16867 };
16868
16869 if selected_larger_node {
16870 self.select_syntax_node_history.disable_clearing = true;
16871 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16872 s.select(new_selections.clone());
16873 });
16874 self.select_syntax_node_history.disable_clearing = false;
16875 }
16876
16877 let start_row = last_new.start.to_display_point(&display_map).row().0;
16878 let end_row = last_new.end.to_display_point(&display_map).row().0;
16879 let selection_height = end_row - start_row + 1;
16880 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16881
16882 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16883 let scroll_behavior = if fits_on_the_screen {
16884 self.request_autoscroll(Autoscroll::fit(), cx);
16885 SelectSyntaxNodeScrollBehavior::FitSelection
16886 } else if is_selection_reversed {
16887 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16888 SelectSyntaxNodeScrollBehavior::CursorTop
16889 } else {
16890 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16891 SelectSyntaxNodeScrollBehavior::CursorBottom
16892 };
16893
16894 let old_selections: Box<[Selection<Anchor>]> = old_selections
16895 .iter()
16896 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16897 .collect();
16898 self.select_syntax_node_history.push((
16899 old_selections,
16900 scroll_behavior,
16901 is_selection_reversed,
16902 ));
16903 }
16904
16905 pub fn select_smaller_syntax_node(
16906 &mut self,
16907 _: &SelectSmallerSyntaxNode,
16908 window: &mut Window,
16909 cx: &mut Context<Self>,
16910 ) {
16911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16912
16913 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16914 self.select_syntax_node_history.pop()
16915 {
16916 if let Some(selection) = selections.last_mut() {
16917 selection.reversed = is_selection_reversed;
16918 }
16919
16920 let snapshot = self.buffer.read(cx).snapshot(cx);
16921 let selections: Vec<Selection<MultiBufferOffset>> = selections
16922 .iter()
16923 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16924 .collect();
16925
16926 self.select_syntax_node_history.disable_clearing = true;
16927 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16928 s.select(selections);
16929 });
16930 self.select_syntax_node_history.disable_clearing = false;
16931
16932 match scroll_behavior {
16933 SelectSyntaxNodeScrollBehavior::CursorTop => {
16934 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16935 }
16936 SelectSyntaxNodeScrollBehavior::FitSelection => {
16937 self.request_autoscroll(Autoscroll::fit(), cx);
16938 }
16939 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16940 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16941 }
16942 }
16943 }
16944 }
16945
16946 pub fn unwrap_syntax_node(
16947 &mut self,
16948 _: &UnwrapSyntaxNode,
16949 window: &mut Window,
16950 cx: &mut Context<Self>,
16951 ) {
16952 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16953
16954 let buffer = self.buffer.read(cx).snapshot(cx);
16955 let selections = self
16956 .selections
16957 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16958 .into_iter()
16959 // subtracting the offset requires sorting
16960 .sorted_by_key(|i| i.start);
16961
16962 let full_edits = selections
16963 .into_iter()
16964 .filter_map(|selection| {
16965 let child = if selection.is_empty()
16966 && let Some((_, ancestor_range)) =
16967 buffer.syntax_ancestor(selection.start..selection.end)
16968 {
16969 ancestor_range
16970 } else {
16971 selection.range()
16972 };
16973
16974 let mut parent = child.clone();
16975 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16976 parent = ancestor_range;
16977 if parent.start < child.start || parent.end > child.end {
16978 break;
16979 }
16980 }
16981
16982 if parent == child {
16983 return None;
16984 }
16985 let text = buffer.text_for_range(child).collect::<String>();
16986 Some((selection.id, parent, text))
16987 })
16988 .collect::<Vec<_>>();
16989 if full_edits.is_empty() {
16990 return;
16991 }
16992
16993 self.transact(window, cx, |this, window, cx| {
16994 this.buffer.update(cx, |buffer, cx| {
16995 buffer.edit(
16996 full_edits
16997 .iter()
16998 .map(|(_, p, t)| (p.clone(), t.clone()))
16999 .collect::<Vec<_>>(),
17000 None,
17001 cx,
17002 );
17003 });
17004 this.change_selections(Default::default(), window, cx, |s| {
17005 let mut offset = 0;
17006 let mut selections = vec![];
17007 for (id, parent, text) in full_edits {
17008 let start = parent.start - offset;
17009 offset += (parent.end - parent.start) - text.len();
17010 selections.push(Selection {
17011 id,
17012 start,
17013 end: start + text.len(),
17014 reversed: false,
17015 goal: Default::default(),
17016 });
17017 }
17018 s.select(selections);
17019 });
17020 });
17021 }
17022
17023 pub fn select_next_syntax_node(
17024 &mut self,
17025 _: &SelectNextSyntaxNode,
17026 window: &mut Window,
17027 cx: &mut Context<Self>,
17028 ) {
17029 let old_selections: Box<[_]> = self
17030 .selections
17031 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17032 .into();
17033 if old_selections.is_empty() {
17034 return;
17035 }
17036
17037 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17038
17039 let buffer = self.buffer.read(cx).snapshot(cx);
17040 let mut selected_sibling = false;
17041
17042 let new_selections = old_selections
17043 .iter()
17044 .map(|selection| {
17045 let old_range = selection.start..selection.end;
17046
17047 let old_range =
17048 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17049 let excerpt = buffer.excerpt_containing(old_range.clone());
17050
17051 if let Some(mut excerpt) = excerpt
17052 && let Some(node) = excerpt
17053 .buffer()
17054 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
17055 {
17056 let new_range = excerpt.map_range_from_buffer(
17057 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17058 );
17059 selected_sibling = true;
17060 Selection {
17061 id: selection.id,
17062 start: new_range.start,
17063 end: new_range.end,
17064 goal: SelectionGoal::None,
17065 reversed: selection.reversed,
17066 }
17067 } else {
17068 selection.clone()
17069 }
17070 })
17071 .collect::<Vec<_>>();
17072
17073 if selected_sibling {
17074 self.change_selections(
17075 SelectionEffects::scroll(Autoscroll::fit()),
17076 window,
17077 cx,
17078 |s| {
17079 s.select(new_selections);
17080 },
17081 );
17082 }
17083 }
17084
17085 pub fn select_prev_syntax_node(
17086 &mut self,
17087 _: &SelectPreviousSyntaxNode,
17088 window: &mut Window,
17089 cx: &mut Context<Self>,
17090 ) {
17091 let old_selections: Box<[_]> = self
17092 .selections
17093 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17094 .into();
17095 if old_selections.is_empty() {
17096 return;
17097 }
17098
17099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17100
17101 let buffer = self.buffer.read(cx).snapshot(cx);
17102 let mut selected_sibling = false;
17103
17104 let new_selections = old_selections
17105 .iter()
17106 .map(|selection| {
17107 let old_range = selection.start..selection.end;
17108 let old_range =
17109 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17110 let excerpt = buffer.excerpt_containing(old_range.clone());
17111
17112 if let Some(mut excerpt) = excerpt
17113 && let Some(node) = excerpt
17114 .buffer()
17115 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
17116 {
17117 let new_range = excerpt.map_range_from_buffer(
17118 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17119 );
17120 selected_sibling = true;
17121 Selection {
17122 id: selection.id,
17123 start: new_range.start,
17124 end: new_range.end,
17125 goal: SelectionGoal::None,
17126 reversed: selection.reversed,
17127 }
17128 } else {
17129 selection.clone()
17130 }
17131 })
17132 .collect::<Vec<_>>();
17133
17134 if selected_sibling {
17135 self.change_selections(
17136 SelectionEffects::scroll(Autoscroll::fit()),
17137 window,
17138 cx,
17139 |s| {
17140 s.select(new_selections);
17141 },
17142 );
17143 }
17144 }
17145
17146 pub fn move_to_start_of_larger_syntax_node(
17147 &mut self,
17148 _: &MoveToStartOfLargerSyntaxNode,
17149 window: &mut Window,
17150 cx: &mut Context<Self>,
17151 ) {
17152 self.move_cursors_to_syntax_nodes(window, cx, false);
17153 }
17154
17155 pub fn move_to_end_of_larger_syntax_node(
17156 &mut self,
17157 _: &MoveToEndOfLargerSyntaxNode,
17158 window: &mut Window,
17159 cx: &mut Context<Self>,
17160 ) {
17161 self.move_cursors_to_syntax_nodes(window, cx, true);
17162 }
17163
17164 fn find_syntax_node_boundary(
17165 &self,
17166 selection_pos: MultiBufferOffset,
17167 move_to_end: bool,
17168 display_map: &DisplaySnapshot,
17169 buffer: &MultiBufferSnapshot,
17170 ) -> MultiBufferOffset {
17171 let old_range = selection_pos..selection_pos;
17172 let mut new_pos = selection_pos;
17173 let mut search_range = old_range;
17174 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17175 search_range = range.clone();
17176 if !node.is_named()
17177 || display_map.intersects_fold(range.start)
17178 || display_map.intersects_fold(range.end)
17179 // If cursor is already at the end of the syntax node, continue searching
17180 || (move_to_end && range.end == selection_pos)
17181 // If cursor is already at the start of the syntax node, continue searching
17182 || (!move_to_end && range.start == selection_pos)
17183 {
17184 continue;
17185 }
17186
17187 // If we found a string_content node, find the largest parent that is still string_content
17188 // Enables us to skip to the end of strings without taking multiple steps inside the string
17189 let (_, final_range) = if node.kind() == "string_content" {
17190 let mut current_node = node;
17191 let mut current_range = range;
17192 while let Some((parent, parent_range)) =
17193 buffer.syntax_ancestor(current_range.clone())
17194 {
17195 if parent.kind() == "string_content" {
17196 current_node = parent;
17197 current_range = parent_range;
17198 } else {
17199 break;
17200 }
17201 }
17202
17203 (current_node, current_range)
17204 } else {
17205 (node, range)
17206 };
17207
17208 new_pos = if move_to_end {
17209 final_range.end
17210 } else {
17211 final_range.start
17212 };
17213
17214 break;
17215 }
17216
17217 new_pos
17218 }
17219
17220 fn move_cursors_to_syntax_nodes(
17221 &mut self,
17222 window: &mut Window,
17223 cx: &mut Context<Self>,
17224 move_to_end: bool,
17225 ) -> bool {
17226 let old_selections: Box<[_]> = self
17227 .selections
17228 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17229 .into();
17230 if old_selections.is_empty() {
17231 return false;
17232 }
17233
17234 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17235
17236 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17237 let buffer = self.buffer.read(cx).snapshot(cx);
17238
17239 let mut any_cursor_moved = false;
17240 let new_selections = old_selections
17241 .iter()
17242 .map(|selection| {
17243 if !selection.is_empty() {
17244 return selection.clone();
17245 }
17246
17247 let selection_pos = selection.head();
17248 let new_pos = self.find_syntax_node_boundary(
17249 selection_pos,
17250 move_to_end,
17251 &display_map,
17252 &buffer,
17253 );
17254
17255 any_cursor_moved |= new_pos != selection_pos;
17256
17257 Selection {
17258 id: selection.id,
17259 start: new_pos,
17260 end: new_pos,
17261 goal: SelectionGoal::None,
17262 reversed: false,
17263 }
17264 })
17265 .collect::<Vec<_>>();
17266
17267 self.change_selections(Default::default(), window, cx, |s| {
17268 s.select(new_selections);
17269 });
17270 self.request_autoscroll(Autoscroll::newest(), cx);
17271
17272 any_cursor_moved
17273 }
17274
17275 pub fn select_to_start_of_larger_syntax_node(
17276 &mut self,
17277 _: &SelectToStartOfLargerSyntaxNode,
17278 window: &mut Window,
17279 cx: &mut Context<Self>,
17280 ) {
17281 self.select_to_syntax_nodes(window, cx, false);
17282 }
17283
17284 pub fn select_to_end_of_larger_syntax_node(
17285 &mut self,
17286 _: &SelectToEndOfLargerSyntaxNode,
17287 window: &mut Window,
17288 cx: &mut Context<Self>,
17289 ) {
17290 self.select_to_syntax_nodes(window, cx, true);
17291 }
17292
17293 fn select_to_syntax_nodes(
17294 &mut self,
17295 window: &mut Window,
17296 cx: &mut Context<Self>,
17297 move_to_end: bool,
17298 ) {
17299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17300
17301 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17302 let buffer = self.buffer.read(cx).snapshot(cx);
17303 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17304
17305 let new_selections = old_selections
17306 .iter()
17307 .map(|selection| {
17308 let new_pos = self.find_syntax_node_boundary(
17309 selection.head(),
17310 move_to_end,
17311 &display_map,
17312 &buffer,
17313 );
17314
17315 let mut new_selection = selection.clone();
17316 new_selection.set_head(new_pos, SelectionGoal::None);
17317 new_selection
17318 })
17319 .collect::<Vec<_>>();
17320
17321 self.change_selections(Default::default(), window, cx, |s| {
17322 s.select(new_selections);
17323 });
17324 }
17325
17326 pub fn move_to_enclosing_bracket(
17327 &mut self,
17328 _: &MoveToEnclosingBracket,
17329 window: &mut Window,
17330 cx: &mut Context<Self>,
17331 ) {
17332 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17333 self.change_selections(Default::default(), window, cx, |s| {
17334 s.move_offsets_with(&mut |snapshot, selection| {
17335 let Some(enclosing_bracket_ranges) =
17336 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17337 else {
17338 return;
17339 };
17340
17341 let mut best_length = usize::MAX;
17342 let mut best_inside = false;
17343 let mut best_in_bracket_range = false;
17344 let mut best_destination = None;
17345 for (open, close) in enclosing_bracket_ranges {
17346 let close = close.to_inclusive();
17347 let length = *close.end() - open.start;
17348 let inside = selection.start >= open.end && selection.end <= *close.start();
17349 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17350 || close.contains(&selection.head());
17351
17352 // If best is next to a bracket and current isn't, skip
17353 if !in_bracket_range && best_in_bracket_range {
17354 continue;
17355 }
17356
17357 // Prefer smaller lengths unless best is inside and current isn't
17358 if length > best_length && (best_inside || !inside) {
17359 continue;
17360 }
17361
17362 best_length = length;
17363 best_inside = inside;
17364 best_in_bracket_range = in_bracket_range;
17365 best_destination = Some(
17366 if close.contains(&selection.start) && close.contains(&selection.end) {
17367 if inside { open.end } else { open.start }
17368 } else if inside {
17369 *close.start()
17370 } else {
17371 *close.end()
17372 },
17373 );
17374 }
17375
17376 if let Some(destination) = best_destination {
17377 selection.collapse_to(destination, SelectionGoal::None);
17378 }
17379 })
17380 });
17381 }
17382
17383 pub fn undo_selection(
17384 &mut self,
17385 _: &UndoSelection,
17386 window: &mut Window,
17387 cx: &mut Context<Self>,
17388 ) {
17389 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17390 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17391 self.selection_history.mode = SelectionHistoryMode::Undoing;
17392 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17393 this.end_selection(window, cx);
17394 this.change_selections(
17395 SelectionEffects::scroll(Autoscroll::newest()),
17396 window,
17397 cx,
17398 |s| s.select_anchors(entry.selections.to_vec()),
17399 );
17400 });
17401 self.selection_history.mode = SelectionHistoryMode::Normal;
17402
17403 self.select_next_state = entry.select_next_state;
17404 self.select_prev_state = entry.select_prev_state;
17405 self.add_selections_state = entry.add_selections_state;
17406 }
17407 }
17408
17409 pub fn redo_selection(
17410 &mut self,
17411 _: &RedoSelection,
17412 window: &mut Window,
17413 cx: &mut Context<Self>,
17414 ) {
17415 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17416 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17417 self.selection_history.mode = SelectionHistoryMode::Redoing;
17418 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17419 this.end_selection(window, cx);
17420 this.change_selections(
17421 SelectionEffects::scroll(Autoscroll::newest()),
17422 window,
17423 cx,
17424 |s| s.select_anchors(entry.selections.to_vec()),
17425 );
17426 });
17427 self.selection_history.mode = SelectionHistoryMode::Normal;
17428
17429 self.select_next_state = entry.select_next_state;
17430 self.select_prev_state = entry.select_prev_state;
17431 self.add_selections_state = entry.add_selections_state;
17432 }
17433 }
17434
17435 pub fn expand_excerpts(
17436 &mut self,
17437 action: &ExpandExcerpts,
17438 _: &mut Window,
17439 cx: &mut Context<Self>,
17440 ) {
17441 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17442 }
17443
17444 pub fn expand_excerpts_down(
17445 &mut self,
17446 action: &ExpandExcerptsDown,
17447 _: &mut Window,
17448 cx: &mut Context<Self>,
17449 ) {
17450 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17451 }
17452
17453 pub fn expand_excerpts_up(
17454 &mut self,
17455 action: &ExpandExcerptsUp,
17456 _: &mut Window,
17457 cx: &mut Context<Self>,
17458 ) {
17459 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17460 }
17461
17462 pub fn expand_excerpts_for_direction(
17463 &mut self,
17464 lines: u32,
17465 direction: ExpandExcerptDirection,
17466 cx: &mut Context<Self>,
17467 ) {
17468 let selections = self.selections.disjoint_anchors_arc();
17469
17470 let lines = if lines == 0 {
17471 EditorSettings::get_global(cx).expand_excerpt_lines
17472 } else {
17473 lines
17474 };
17475
17476 let snapshot = self.buffer.read(cx).snapshot(cx);
17477 let excerpt_ids = selections
17478 .iter()
17479 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17480 .unique()
17481 .sorted()
17482 .collect::<Vec<_>>();
17483
17484 if self.delegate_expand_excerpts {
17485 cx.emit(EditorEvent::ExpandExcerptsRequested {
17486 excerpt_ids,
17487 lines,
17488 direction,
17489 });
17490 return;
17491 }
17492
17493 self.buffer.update(cx, |buffer, cx| {
17494 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17495 })
17496 }
17497
17498 pub fn expand_excerpt(
17499 &mut self,
17500 excerpt: ExcerptId,
17501 direction: ExpandExcerptDirection,
17502 window: &mut Window,
17503 cx: &mut Context<Self>,
17504 ) {
17505 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17506
17507 if self.delegate_expand_excerpts {
17508 cx.emit(EditorEvent::ExpandExcerptsRequested {
17509 excerpt_ids: vec![excerpt],
17510 lines: lines_to_expand,
17511 direction,
17512 });
17513 return;
17514 }
17515
17516 let current_scroll_position = self.scroll_position(cx);
17517 let mut scroll = None;
17518
17519 if direction == ExpandExcerptDirection::Down {
17520 let multi_buffer = self.buffer.read(cx);
17521 let snapshot = multi_buffer.snapshot(cx);
17522 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17523 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17524 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17525 {
17526 let buffer_snapshot = buffer.read(cx).snapshot();
17527 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17528 let last_row = buffer_snapshot.max_point().row;
17529 let lines_below = last_row.saturating_sub(excerpt_end_row);
17530 if lines_below >= lines_to_expand {
17531 scroll = Some(
17532 current_scroll_position
17533 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17534 );
17535 }
17536 }
17537 }
17538 if direction == ExpandExcerptDirection::Up
17539 && self
17540 .buffer
17541 .read(cx)
17542 .snapshot(cx)
17543 .excerpt_before(excerpt)
17544 .is_none()
17545 {
17546 scroll = Some(current_scroll_position);
17547 }
17548
17549 self.buffer.update(cx, |buffer, cx| {
17550 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17551 });
17552
17553 if let Some(new_scroll_position) = scroll {
17554 self.set_scroll_position(new_scroll_position, window, cx);
17555 }
17556 }
17557
17558 pub fn go_to_singleton_buffer_point(
17559 &mut self,
17560 point: Point,
17561 window: &mut Window,
17562 cx: &mut Context<Self>,
17563 ) {
17564 self.go_to_singleton_buffer_range(point..point, window, cx);
17565 }
17566
17567 pub fn go_to_singleton_buffer_range(
17568 &mut self,
17569 range: Range<Point>,
17570 window: &mut Window,
17571 cx: &mut Context<Self>,
17572 ) {
17573 let multibuffer = self.buffer().read(cx);
17574 let Some(buffer) = multibuffer.as_singleton() else {
17575 return;
17576 };
17577 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17578 return;
17579 };
17580 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17581 return;
17582 };
17583 self.change_selections(
17584 SelectionEffects::default().nav_history(true),
17585 window,
17586 cx,
17587 |s| s.select_anchor_ranges([start..end]),
17588 );
17589 }
17590
17591 pub fn go_to_diagnostic(
17592 &mut self,
17593 action: &GoToDiagnostic,
17594 window: &mut Window,
17595 cx: &mut Context<Self>,
17596 ) {
17597 if !self.diagnostics_enabled() {
17598 return;
17599 }
17600 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17601 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17602 }
17603
17604 pub fn go_to_prev_diagnostic(
17605 &mut self,
17606 action: &GoToPreviousDiagnostic,
17607 window: &mut Window,
17608 cx: &mut Context<Self>,
17609 ) {
17610 if !self.diagnostics_enabled() {
17611 return;
17612 }
17613 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17614 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17615 }
17616
17617 pub fn go_to_diagnostic_impl(
17618 &mut self,
17619 direction: Direction,
17620 severity: GoToDiagnosticSeverityFilter,
17621 window: &mut Window,
17622 cx: &mut Context<Self>,
17623 ) {
17624 let buffer = self.buffer.read(cx).snapshot(cx);
17625 let selection = self
17626 .selections
17627 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17628
17629 let mut active_group_id = None;
17630 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17631 && active_group.active_range.start.to_offset(&buffer) == selection.start
17632 {
17633 active_group_id = Some(active_group.group_id);
17634 }
17635
17636 fn filtered<'a>(
17637 severity: GoToDiagnosticSeverityFilter,
17638 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17639 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17640 diagnostics
17641 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17642 .filter(|entry| entry.range.start != entry.range.end)
17643 .filter(|entry| !entry.diagnostic.is_unnecessary)
17644 }
17645
17646 let before = filtered(
17647 severity,
17648 buffer
17649 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17650 .filter(|entry| entry.range.start <= selection.start),
17651 );
17652 let after = filtered(
17653 severity,
17654 buffer
17655 .diagnostics_in_range(selection.start..buffer.len())
17656 .filter(|entry| entry.range.start >= selection.start),
17657 );
17658
17659 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17660 if direction == Direction::Prev {
17661 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17662 {
17663 for diagnostic in prev_diagnostics.into_iter().rev() {
17664 if diagnostic.range.start != selection.start
17665 || active_group_id
17666 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17667 {
17668 found = Some(diagnostic);
17669 break 'outer;
17670 }
17671 }
17672 }
17673 } else {
17674 for diagnostic in after.chain(before) {
17675 if diagnostic.range.start != selection.start
17676 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17677 {
17678 found = Some(diagnostic);
17679 break;
17680 }
17681 }
17682 }
17683 let Some(next_diagnostic) = found else {
17684 return;
17685 };
17686
17687 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17688 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17689 return;
17690 };
17691 let snapshot = self.snapshot(window, cx);
17692 if snapshot.intersects_fold(next_diagnostic.range.start) {
17693 self.unfold_ranges(
17694 std::slice::from_ref(&next_diagnostic.range),
17695 true,
17696 false,
17697 cx,
17698 );
17699 }
17700 self.change_selections(Default::default(), window, cx, |s| {
17701 s.select_ranges(vec![
17702 next_diagnostic.range.start..next_diagnostic.range.start,
17703 ])
17704 });
17705 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17706 self.refresh_edit_prediction(false, true, window, cx);
17707 }
17708
17709 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17710 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17711 let snapshot = self.snapshot(window, cx);
17712 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17713 self.go_to_hunk_before_or_after_position(
17714 &snapshot,
17715 selection.head(),
17716 Direction::Next,
17717 true,
17718 window,
17719 cx,
17720 );
17721 }
17722
17723 pub fn go_to_hunk_before_or_after_position(
17724 &mut self,
17725 snapshot: &EditorSnapshot,
17726 position: Point,
17727 direction: Direction,
17728 wrap_around: bool,
17729 window: &mut Window,
17730 cx: &mut Context<Editor>,
17731 ) {
17732 let row = if direction == Direction::Next {
17733 self.hunk_after_position(snapshot, position, wrap_around)
17734 .map(|hunk| hunk.row_range.start)
17735 } else {
17736 self.hunk_before_position(snapshot, position, wrap_around)
17737 };
17738
17739 if let Some(row) = row {
17740 let destination = Point::new(row.0, 0);
17741 let autoscroll = Autoscroll::center();
17742
17743 self.unfold_ranges(&[destination..destination], false, false, cx);
17744 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17745 s.select_ranges([destination..destination]);
17746 });
17747 }
17748 }
17749
17750 fn hunk_after_position(
17751 &mut self,
17752 snapshot: &EditorSnapshot,
17753 position: Point,
17754 wrap_around: bool,
17755 ) -> Option<MultiBufferDiffHunk> {
17756 let result = snapshot
17757 .buffer_snapshot()
17758 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17759 .find(|hunk| hunk.row_range.start.0 > position.row);
17760
17761 if wrap_around {
17762 result.or_else(|| {
17763 snapshot
17764 .buffer_snapshot()
17765 .diff_hunks_in_range(Point::zero()..position)
17766 .find(|hunk| hunk.row_range.end.0 < position.row)
17767 })
17768 } else {
17769 result
17770 }
17771 }
17772
17773 fn go_to_prev_hunk(
17774 &mut self,
17775 _: &GoToPreviousHunk,
17776 window: &mut Window,
17777 cx: &mut Context<Self>,
17778 ) {
17779 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17780 let snapshot = self.snapshot(window, cx);
17781 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17782 self.go_to_hunk_before_or_after_position(
17783 &snapshot,
17784 selection.head(),
17785 Direction::Prev,
17786 true,
17787 window,
17788 cx,
17789 );
17790 }
17791
17792 fn hunk_before_position(
17793 &mut self,
17794 snapshot: &EditorSnapshot,
17795 position: Point,
17796 wrap_around: bool,
17797 ) -> Option<MultiBufferRow> {
17798 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17799
17800 if wrap_around {
17801 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17802 } else {
17803 result
17804 }
17805 }
17806
17807 fn go_to_next_change(
17808 &mut self,
17809 _: &GoToNextChange,
17810 window: &mut Window,
17811 cx: &mut Context<Self>,
17812 ) {
17813 if let Some(selections) = self
17814 .change_list
17815 .next_change(1, Direction::Next)
17816 .map(|s| s.to_vec())
17817 {
17818 self.change_selections(Default::default(), window, cx, |s| {
17819 let map = s.display_snapshot();
17820 s.select_display_ranges(selections.iter().map(|a| {
17821 let point = a.to_display_point(&map);
17822 point..point
17823 }))
17824 })
17825 }
17826 }
17827
17828 fn go_to_previous_change(
17829 &mut self,
17830 _: &GoToPreviousChange,
17831 window: &mut Window,
17832 cx: &mut Context<Self>,
17833 ) {
17834 if let Some(selections) = self
17835 .change_list
17836 .next_change(1, Direction::Prev)
17837 .map(|s| s.to_vec())
17838 {
17839 self.change_selections(Default::default(), window, cx, |s| {
17840 let map = s.display_snapshot();
17841 s.select_display_ranges(selections.iter().map(|a| {
17842 let point = a.to_display_point(&map);
17843 point..point
17844 }))
17845 })
17846 }
17847 }
17848
17849 pub fn go_to_next_document_highlight(
17850 &mut self,
17851 _: &GoToNextDocumentHighlight,
17852 window: &mut Window,
17853 cx: &mut Context<Self>,
17854 ) {
17855 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17856 }
17857
17858 pub fn go_to_prev_document_highlight(
17859 &mut self,
17860 _: &GoToPreviousDocumentHighlight,
17861 window: &mut Window,
17862 cx: &mut Context<Self>,
17863 ) {
17864 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17865 }
17866
17867 pub fn go_to_document_highlight_before_or_after_position(
17868 &mut self,
17869 direction: Direction,
17870 window: &mut Window,
17871 cx: &mut Context<Editor>,
17872 ) {
17873 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17874 let snapshot = self.snapshot(window, cx);
17875 let buffer = &snapshot.buffer_snapshot();
17876 let position = self
17877 .selections
17878 .newest::<Point>(&snapshot.display_snapshot)
17879 .head();
17880 let anchor_position = buffer.anchor_after(position);
17881
17882 // Get all document highlights (both read and write)
17883 let mut all_highlights = Vec::new();
17884
17885 if let Some((_, read_highlights)) = self
17886 .background_highlights
17887 .get(&HighlightKey::DocumentHighlightRead)
17888 {
17889 all_highlights.extend(read_highlights.iter());
17890 }
17891
17892 if let Some((_, write_highlights)) = self
17893 .background_highlights
17894 .get(&HighlightKey::DocumentHighlightWrite)
17895 {
17896 all_highlights.extend(write_highlights.iter());
17897 }
17898
17899 if all_highlights.is_empty() {
17900 return;
17901 }
17902
17903 // Sort highlights by position
17904 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17905
17906 let target_highlight = match direction {
17907 Direction::Next => {
17908 // Find the first highlight after the current position
17909 all_highlights
17910 .iter()
17911 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17912 }
17913 Direction::Prev => {
17914 // Find the last highlight before the current position
17915 all_highlights
17916 .iter()
17917 .rev()
17918 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17919 }
17920 };
17921
17922 if let Some(highlight) = target_highlight {
17923 let destination = highlight.start.to_point(buffer);
17924 let autoscroll = Autoscroll::center();
17925
17926 self.unfold_ranges(&[destination..destination], false, false, cx);
17927 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17928 s.select_ranges([destination..destination]);
17929 });
17930 }
17931 }
17932
17933 fn go_to_line<T: 'static>(
17934 &mut self,
17935 position: Anchor,
17936 highlight_color: Option<Hsla>,
17937 window: &mut Window,
17938 cx: &mut Context<Self>,
17939 ) {
17940 let snapshot = self.snapshot(window, cx).display_snapshot;
17941 let position = position.to_point(&snapshot.buffer_snapshot());
17942 let start = snapshot
17943 .buffer_snapshot()
17944 .clip_point(Point::new(position.row, 0), Bias::Left);
17945 let end = start + Point::new(1, 0);
17946 let start = snapshot.buffer_snapshot().anchor_before(start);
17947 let end = snapshot.buffer_snapshot().anchor_before(end);
17948
17949 self.highlight_rows::<T>(
17950 start..end,
17951 highlight_color
17952 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17953 Default::default(),
17954 cx,
17955 );
17956
17957 if self.buffer.read(cx).is_singleton() {
17958 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17959 }
17960 }
17961
17962 pub fn go_to_definition(
17963 &mut self,
17964 _: &GoToDefinition,
17965 window: &mut Window,
17966 cx: &mut Context<Self>,
17967 ) -> Task<Result<Navigated>> {
17968 let definition =
17969 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17970 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17971 cx.spawn_in(window, async move |editor, cx| {
17972 if definition.await? == Navigated::Yes {
17973 return Ok(Navigated::Yes);
17974 }
17975 match fallback_strategy {
17976 GoToDefinitionFallback::None => Ok(Navigated::No),
17977 GoToDefinitionFallback::FindAllReferences => {
17978 match editor.update_in(cx, |editor, window, cx| {
17979 editor.find_all_references(&FindAllReferences::default(), window, cx)
17980 })? {
17981 Some(references) => references.await,
17982 None => Ok(Navigated::No),
17983 }
17984 }
17985 }
17986 })
17987 }
17988
17989 pub fn go_to_declaration(
17990 &mut self,
17991 _: &GoToDeclaration,
17992 window: &mut Window,
17993 cx: &mut Context<Self>,
17994 ) -> Task<Result<Navigated>> {
17995 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17996 }
17997
17998 pub fn go_to_declaration_split(
17999 &mut self,
18000 _: &GoToDeclaration,
18001 window: &mut Window,
18002 cx: &mut Context<Self>,
18003 ) -> Task<Result<Navigated>> {
18004 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
18005 }
18006
18007 pub fn go_to_implementation(
18008 &mut self,
18009 _: &GoToImplementation,
18010 window: &mut Window,
18011 cx: &mut Context<Self>,
18012 ) -> Task<Result<Navigated>> {
18013 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
18014 }
18015
18016 pub fn go_to_implementation_split(
18017 &mut self,
18018 _: &GoToImplementationSplit,
18019 window: &mut Window,
18020 cx: &mut Context<Self>,
18021 ) -> Task<Result<Navigated>> {
18022 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
18023 }
18024
18025 pub fn go_to_type_definition(
18026 &mut self,
18027 _: &GoToTypeDefinition,
18028 window: &mut Window,
18029 cx: &mut Context<Self>,
18030 ) -> Task<Result<Navigated>> {
18031 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
18032 }
18033
18034 pub fn go_to_definition_split(
18035 &mut self,
18036 _: &GoToDefinitionSplit,
18037 window: &mut Window,
18038 cx: &mut Context<Self>,
18039 ) -> Task<Result<Navigated>> {
18040 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
18041 }
18042
18043 pub fn go_to_type_definition_split(
18044 &mut self,
18045 _: &GoToTypeDefinitionSplit,
18046 window: &mut Window,
18047 cx: &mut Context<Self>,
18048 ) -> Task<Result<Navigated>> {
18049 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18050 }
18051
18052 fn go_to_definition_of_kind(
18053 &mut self,
18054 kind: GotoDefinitionKind,
18055 split: bool,
18056 window: &mut Window,
18057 cx: &mut Context<Self>,
18058 ) -> Task<Result<Navigated>> {
18059 let Some(provider) = self.semantics_provider.clone() else {
18060 return Task::ready(Ok(Navigated::No));
18061 };
18062 let head = self
18063 .selections
18064 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18065 .head();
18066 let buffer = self.buffer.read(cx);
18067 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18068 return Task::ready(Ok(Navigated::No));
18069 };
18070 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18071 return Task::ready(Ok(Navigated::No));
18072 };
18073
18074 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18075
18076 cx.spawn_in(window, async move |editor, cx| {
18077 let Some(definitions) = definitions.await? else {
18078 return Ok(Navigated::No);
18079 };
18080 let navigated = editor
18081 .update_in(cx, |editor, window, cx| {
18082 editor.navigate_to_hover_links(
18083 Some(kind),
18084 definitions
18085 .into_iter()
18086 .filter(|location| {
18087 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18088 })
18089 .map(HoverLink::Text)
18090 .collect::<Vec<_>>(),
18091 nav_entry,
18092 split,
18093 window,
18094 cx,
18095 )
18096 })?
18097 .await?;
18098 anyhow::Ok(navigated)
18099 })
18100 }
18101
18102 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18103 let selection = self.selections.newest_anchor();
18104 let head = selection.head();
18105 let tail = selection.tail();
18106
18107 let Some((buffer, start_position)) =
18108 self.buffer.read(cx).text_anchor_for_position(head, cx)
18109 else {
18110 return;
18111 };
18112
18113 let end_position = if head != tail {
18114 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18115 return;
18116 };
18117 Some(pos)
18118 } else {
18119 None
18120 };
18121
18122 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18123 let url = if let Some(end_pos) = end_position {
18124 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18125 } else {
18126 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18127 };
18128
18129 if let Some(url) = url {
18130 cx.update(|window, cx| {
18131 if parse_zed_link(&url, cx).is_some() {
18132 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18133 } else {
18134 cx.open_url(&url);
18135 }
18136 })?;
18137 }
18138
18139 anyhow::Ok(())
18140 });
18141
18142 url_finder.detach();
18143 }
18144
18145 pub fn open_selected_filename(
18146 &mut self,
18147 _: &OpenSelectedFilename,
18148 window: &mut Window,
18149 cx: &mut Context<Self>,
18150 ) {
18151 let Some(workspace) = self.workspace() else {
18152 return;
18153 };
18154
18155 let position = self.selections.newest_anchor().head();
18156
18157 let Some((buffer, buffer_position)) =
18158 self.buffer.read(cx).text_anchor_for_position(position, cx)
18159 else {
18160 return;
18161 };
18162
18163 let project = self.project.clone();
18164
18165 cx.spawn_in(window, async move |_, cx| {
18166 let result = find_file(&buffer, project, buffer_position, cx).await;
18167
18168 if let Some((_, path)) = result {
18169 workspace
18170 .update_in(cx, |workspace, window, cx| {
18171 workspace.open_resolved_path(path, window, cx)
18172 })?
18173 .await?;
18174 }
18175 anyhow::Ok(())
18176 })
18177 .detach();
18178 }
18179
18180 pub(crate) fn navigate_to_hover_links(
18181 &mut self,
18182 kind: Option<GotoDefinitionKind>,
18183 definitions: Vec<HoverLink>,
18184 origin: Option<NavigationEntry>,
18185 split: bool,
18186 window: &mut Window,
18187 cx: &mut Context<Editor>,
18188 ) -> Task<Result<Navigated>> {
18189 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18190 let mut first_url_or_file = None;
18191 let definitions: Vec<_> = definitions
18192 .into_iter()
18193 .filter_map(|def| match def {
18194 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18195 HoverLink::InlayHint(lsp_location, server_id) => {
18196 let computation =
18197 self.compute_target_location(lsp_location, server_id, window, cx);
18198 Some(cx.background_spawn(computation))
18199 }
18200 HoverLink::Url(url) => {
18201 first_url_or_file = Some(Either::Left(url));
18202 None
18203 }
18204 HoverLink::File(path) => {
18205 first_url_or_file = Some(Either::Right(path));
18206 None
18207 }
18208 })
18209 .collect();
18210
18211 let workspace = self.workspace();
18212
18213 let excerpt_context_lines = multi_buffer::excerpt_context_lines(cx);
18214 cx.spawn_in(window, async move |editor, cx| {
18215 let locations: Vec<Location> = future::join_all(definitions)
18216 .await
18217 .into_iter()
18218 .filter_map(|location| location.transpose())
18219 .collect::<Result<_>>()
18220 .context("location tasks")?;
18221 let mut locations = cx.update(|_, cx| {
18222 locations
18223 .into_iter()
18224 .map(|location| {
18225 let buffer = location.buffer.read(cx);
18226 (location.buffer, location.range.to_point(buffer))
18227 })
18228 .into_group_map()
18229 })?;
18230 let mut num_locations = 0;
18231 for ranges in locations.values_mut() {
18232 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18233 ranges.dedup();
18234 // Merge overlapping or contained ranges. After sorting by
18235 // (start, Reverse(end)), we can merge in a single pass:
18236 // if the next range starts before the current one ends,
18237 // extend the current range's end if needed.
18238 let mut i = 0;
18239 while i + 1 < ranges.len() {
18240 if ranges[i + 1].start <= ranges[i].end {
18241 let merged_end = ranges[i].end.max(ranges[i + 1].end);
18242 ranges[i].end = merged_end;
18243 ranges.remove(i + 1);
18244 } else {
18245 i += 1;
18246 }
18247 }
18248 let fits_in_one_excerpt = ranges
18249 .iter()
18250 .tuple_windows()
18251 .all(|(a, b)| b.start.row - a.end.row <= 2 * excerpt_context_lines);
18252 num_locations += if fits_in_one_excerpt { 1 } else { ranges.len() };
18253 }
18254
18255 if num_locations > 1 {
18256 let tab_kind = match kind {
18257 Some(GotoDefinitionKind::Implementation) => "Implementations",
18258 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18259 Some(GotoDefinitionKind::Declaration) => "Declarations",
18260 Some(GotoDefinitionKind::Type) => "Types",
18261 };
18262 let title = editor
18263 .update_in(cx, |_, _, cx| {
18264 let target = locations
18265 .iter()
18266 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18267 .map(|(buffer, location)| {
18268 buffer
18269 .read(cx)
18270 .text_for_range(location.clone())
18271 .collect::<String>()
18272 })
18273 .filter(|text| !text.contains('\n'))
18274 .unique()
18275 .take(3)
18276 .join(", ");
18277 if target.is_empty() {
18278 tab_kind.to_owned()
18279 } else {
18280 format!("{tab_kind} for {target}")
18281 }
18282 })
18283 .context("buffer title")?;
18284
18285 let Some(workspace) = workspace else {
18286 return Ok(Navigated::No);
18287 };
18288
18289 let opened = workspace
18290 .update_in(cx, |workspace, window, cx| {
18291 let allow_preview = PreviewTabsSettings::get_global(cx)
18292 .enable_preview_multibuffer_from_code_navigation;
18293 if let Some((target_editor, target_pane)) =
18294 Self::open_locations_in_multibuffer(
18295 workspace,
18296 locations,
18297 title,
18298 split,
18299 allow_preview,
18300 MultibufferSelectionMode::First,
18301 window,
18302 cx,
18303 )
18304 {
18305 // We create our own nav history instead of using
18306 // `target_editor.nav_history` because `nav_history`
18307 // seems to be populated asynchronously when an item
18308 // is added to a pane
18309 let mut nav_history = target_pane
18310 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18311 target_editor.update(cx, |editor, cx| {
18312 let nav_data = editor
18313 .navigation_data(editor.selections.newest_anchor().head(), cx);
18314 let target =
18315 Some(nav_history.navigation_entry(Some(
18316 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18317 )));
18318 nav_history.push_tag(origin, target);
18319 })
18320 }
18321 })
18322 .is_ok();
18323
18324 anyhow::Ok(Navigated::from_bool(opened))
18325 } else if num_locations == 0 {
18326 // If there is one url or file, open it directly
18327 match first_url_or_file {
18328 Some(Either::Left(url)) => {
18329 cx.update(|window, cx| {
18330 if parse_zed_link(&url, cx).is_some() {
18331 window
18332 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18333 } else {
18334 cx.open_url(&url);
18335 }
18336 })?;
18337 Ok(Navigated::Yes)
18338 }
18339 Some(Either::Right(path)) => {
18340 // TODO(andrew): respect preview tab settings
18341 // `enable_keep_preview_on_code_navigation` and
18342 // `enable_preview_file_from_code_navigation`
18343 let Some(workspace) = workspace else {
18344 return Ok(Navigated::No);
18345 };
18346 workspace
18347 .update_in(cx, |workspace, window, cx| {
18348 workspace.open_resolved_path(path, window, cx)
18349 })?
18350 .await?;
18351 Ok(Navigated::Yes)
18352 }
18353 None => Ok(Navigated::No),
18354 }
18355 } else {
18356 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18357
18358 editor.update_in(cx, |editor, window, cx| {
18359 let target_ranges = target_ranges
18360 .into_iter()
18361 .map(|r| editor.range_for_match(&r))
18362 .map(collapse_multiline_range)
18363 .collect::<Vec<_>>();
18364 if !split
18365 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18366 {
18367 let multibuffer = editor.buffer.read(cx);
18368 let target_ranges = target_ranges
18369 .into_iter()
18370 .filter_map(|r| {
18371 let start = multibuffer.buffer_point_to_anchor(
18372 &target_buffer,
18373 r.start,
18374 cx,
18375 )?;
18376 let end = multibuffer.buffer_point_to_anchor(
18377 &target_buffer,
18378 r.end,
18379 cx,
18380 )?;
18381 Some(start..end)
18382 })
18383 .collect::<Vec<_>>();
18384 if target_ranges.is_empty() {
18385 return Navigated::No;
18386 }
18387
18388 editor.change_selections(
18389 SelectionEffects::default().nav_history(true),
18390 window,
18391 cx,
18392 |s| s.select_anchor_ranges(target_ranges),
18393 );
18394
18395 let target =
18396 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18397 if let Some(mut nav_history) = editor.nav_history.clone() {
18398 nav_history.push_tag(origin, target);
18399 }
18400 } else {
18401 let Some(workspace) = workspace else {
18402 return Navigated::No;
18403 };
18404 let pane = workspace.read(cx).active_pane().clone();
18405 window.defer(cx, move |window, cx| {
18406 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18407 workspace.update(cx, |workspace, cx| {
18408 let pane = if split {
18409 workspace.adjacent_pane(window, cx)
18410 } else {
18411 workspace.active_pane().clone()
18412 };
18413
18414 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18415 let keep_old_preview = preview_tabs_settings
18416 .enable_keep_preview_on_code_navigation;
18417 let allow_new_preview = preview_tabs_settings
18418 .enable_preview_file_from_code_navigation;
18419
18420 let editor = workspace.open_project_item(
18421 pane.clone(),
18422 target_buffer.clone(),
18423 true,
18424 true,
18425 keep_old_preview,
18426 allow_new_preview,
18427 window,
18428 cx,
18429 );
18430 (editor, pane)
18431 });
18432 // We create our own nav history instead of using
18433 // `target_editor.nav_history` because `nav_history`
18434 // seems to be populated asynchronously when an item
18435 // is added to a pane
18436 let mut nav_history = target_pane
18437 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18438 target_editor.update(cx, |target_editor, cx| {
18439 // When selecting a definition in a different buffer, disable the nav history
18440 // to avoid creating a history entry at the previous cursor location.
18441 pane.update(cx, |pane, _| pane.disable_history());
18442
18443 let multibuffer = target_editor.buffer.read(cx);
18444 let Some(target_buffer) = multibuffer.as_singleton() else {
18445 return Navigated::No;
18446 };
18447 let target_ranges = target_ranges
18448 .into_iter()
18449 .filter_map(|r| {
18450 let start = multibuffer.buffer_point_to_anchor(
18451 &target_buffer,
18452 r.start,
18453 cx,
18454 )?;
18455 let end = multibuffer.buffer_point_to_anchor(
18456 &target_buffer,
18457 r.end,
18458 cx,
18459 )?;
18460 Some(start..end)
18461 })
18462 .collect::<Vec<_>>();
18463 if target_ranges.is_empty() {
18464 return Navigated::No;
18465 }
18466
18467 target_editor.change_selections(
18468 SelectionEffects::default().nav_history(true),
18469 window,
18470 cx,
18471 |s| s.select_anchor_ranges(target_ranges),
18472 );
18473
18474 let nav_data = target_editor.navigation_data(
18475 target_editor.selections.newest_anchor().head(),
18476 cx,
18477 );
18478 let target =
18479 Some(nav_history.navigation_entry(Some(
18480 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18481 )));
18482 nav_history.push_tag(origin, target);
18483 pane.update(cx, |pane, _| pane.enable_history());
18484 Navigated::Yes
18485 });
18486 });
18487 }
18488 Navigated::Yes
18489 })
18490 }
18491 })
18492 }
18493
18494 fn compute_target_location(
18495 &self,
18496 lsp_location: lsp::Location,
18497 server_id: LanguageServerId,
18498 window: &mut Window,
18499 cx: &mut Context<Self>,
18500 ) -> Task<anyhow::Result<Option<Location>>> {
18501 let Some(project) = self.project.clone() else {
18502 return Task::ready(Ok(None));
18503 };
18504
18505 cx.spawn_in(window, async move |editor, cx| {
18506 let location_task = editor.update(cx, |_, cx| {
18507 project.update(cx, |project, cx| {
18508 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18509 })
18510 })?;
18511 let location = Some({
18512 let target_buffer_handle = location_task.await.context("open local buffer")?;
18513 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18514 let target_start = target_buffer
18515 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18516 let target_end = target_buffer
18517 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18518 target_buffer.anchor_after(target_start)
18519 ..target_buffer.anchor_before(target_end)
18520 });
18521 Location {
18522 buffer: target_buffer_handle,
18523 range,
18524 }
18525 });
18526 Ok(location)
18527 })
18528 }
18529
18530 fn go_to_next_reference(
18531 &mut self,
18532 _: &GoToNextReference,
18533 window: &mut Window,
18534 cx: &mut Context<Self>,
18535 ) {
18536 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18537 if let Some(task) = task {
18538 task.detach();
18539 };
18540 }
18541
18542 fn go_to_prev_reference(
18543 &mut self,
18544 _: &GoToPreviousReference,
18545 window: &mut Window,
18546 cx: &mut Context<Self>,
18547 ) {
18548 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18549 if let Some(task) = task {
18550 task.detach();
18551 };
18552 }
18553
18554 fn go_to_symbol_by_offset(
18555 &mut self,
18556 window: &mut Window,
18557 cx: &mut Context<Self>,
18558 offset: i8,
18559 ) -> Task<Result<()>> {
18560 let editor_snapshot = self.snapshot(window, cx);
18561
18562 // We don't care about multi-buffer symbols
18563 let Some((excerpt_id, _, _)) = editor_snapshot.as_singleton() else {
18564 return Task::ready(Ok(()));
18565 };
18566
18567 let cursor_offset = self
18568 .selections
18569 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18570 .head();
18571
18572 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18573 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18574 let buffer = ed.buffer.read(cx).as_singleton()?;
18575 Some(buffer.read(cx).remote_id())
18576 }) else {
18577 return Ok(());
18578 };
18579
18580 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18581 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18582
18583 let multi_snapshot = editor_snapshot.buffer();
18584 let buffer_range = |range: &Range<_>| {
18585 Anchor::range_in_buffer(excerpt_id, range.clone()).to_offset(multi_snapshot)
18586 };
18587
18588 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18589 let current_idx = outline_items
18590 .iter()
18591 .enumerate()
18592 .filter_map(|(idx, item)| {
18593 // Find the closest outline item by distance between outline text and cursor location
18594 let source_range = buffer_range(&item.source_range_for_text);
18595 let distance_to_closest_endpoint = cmp::min(
18596 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18597 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18598 );
18599
18600 let item_towards_offset =
18601 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18602 == (offset as isize).signum();
18603
18604 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18605
18606 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18607 // we should not already be within the outline's source range. We then pick the closest outline
18608 // item.
18609 (item_towards_offset && !source_range_contains_cursor)
18610 .then_some((distance_to_closest_endpoint, idx))
18611 })
18612 .min()
18613 .map(|(_, idx)| idx);
18614
18615 let Some(idx) = current_idx else {
18616 return;
18617 };
18618
18619 let range = buffer_range(&outline_items[idx].source_range_for_text);
18620 let selection = [range.start..range.start];
18621
18622 let _ = editor
18623 .update(acx, |editor, ecx| {
18624 editor.change_selections(
18625 SelectionEffects::scroll(Autoscroll::newest()),
18626 window,
18627 ecx,
18628 |s| s.select_ranges(selection),
18629 );
18630 })
18631 .ok();
18632 })?;
18633
18634 Ok(())
18635 })
18636 }
18637
18638 fn go_to_next_symbol(
18639 &mut self,
18640 _: &GoToNextSymbol,
18641 window: &mut Window,
18642 cx: &mut Context<Self>,
18643 ) {
18644 self.go_to_symbol_by_offset(window, cx, 1).detach();
18645 }
18646
18647 fn go_to_previous_symbol(
18648 &mut self,
18649 _: &GoToPreviousSymbol,
18650 window: &mut Window,
18651 cx: &mut Context<Self>,
18652 ) {
18653 self.go_to_symbol_by_offset(window, cx, -1).detach();
18654 }
18655
18656 pub fn go_to_reference_before_or_after_position(
18657 &mut self,
18658 direction: Direction,
18659 count: usize,
18660 window: &mut Window,
18661 cx: &mut Context<Self>,
18662 ) -> Option<Task<Result<()>>> {
18663 let selection = self.selections.newest_anchor();
18664 let head = selection.head();
18665
18666 let multi_buffer = self.buffer.read(cx);
18667
18668 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18669 let workspace = self.workspace()?;
18670 let project = workspace.read(cx).project().clone();
18671 let references =
18672 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18673 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18674 let Some(locations) = references.await? else {
18675 return Ok(());
18676 };
18677
18678 if locations.is_empty() {
18679 // totally normal - the cursor may be on something which is not
18680 // a symbol (e.g. a keyword)
18681 log::info!("no references found under cursor");
18682 return Ok(());
18683 }
18684
18685 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18686
18687 let (locations, current_location_index) =
18688 multi_buffer.update(cx, |multi_buffer, cx| {
18689 let mut locations = locations
18690 .into_iter()
18691 .filter_map(|loc| {
18692 let start = multi_buffer.buffer_anchor_to_anchor(
18693 &loc.buffer,
18694 loc.range.start,
18695 cx,
18696 )?;
18697 let end = multi_buffer.buffer_anchor_to_anchor(
18698 &loc.buffer,
18699 loc.range.end,
18700 cx,
18701 )?;
18702 Some(start..end)
18703 })
18704 .collect::<Vec<_>>();
18705
18706 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18707 // There is an O(n) implementation, but given this list will be
18708 // small (usually <100 items), the extra O(log(n)) factor isn't
18709 // worth the (surprisingly large amount of) extra complexity.
18710 locations
18711 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18712
18713 let head_offset = head.to_offset(&multi_buffer_snapshot);
18714
18715 let current_location_index = locations.iter().position(|loc| {
18716 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18717 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18718 });
18719
18720 (locations, current_location_index)
18721 });
18722
18723 let Some(current_location_index) = current_location_index else {
18724 // This indicates something has gone wrong, because we already
18725 // handle the "no references" case above
18726 log::error!(
18727 "failed to find current reference under cursor. Total references: {}",
18728 locations.len()
18729 );
18730 return Ok(());
18731 };
18732
18733 let destination_location_index = match direction {
18734 Direction::Next => (current_location_index + count) % locations.len(),
18735 Direction::Prev => {
18736 (current_location_index + locations.len() - count % locations.len())
18737 % locations.len()
18738 }
18739 };
18740
18741 // TODO(cameron): is this needed?
18742 // the thinking is to avoid "jumping to the current location" (avoid
18743 // polluting "jumplist" in vim terms)
18744 if current_location_index == destination_location_index {
18745 return Ok(());
18746 }
18747
18748 let Range { start, end } = locations[destination_location_index];
18749
18750 editor.update_in(cx, |editor, window, cx| {
18751 let effects = SelectionEffects::default();
18752
18753 editor.unfold_ranges(&[start..end], false, false, cx);
18754 editor.change_selections(effects, window, cx, |s| {
18755 s.select_ranges([start..start]);
18756 });
18757 })?;
18758
18759 Ok(())
18760 }))
18761 }
18762
18763 pub fn find_all_references(
18764 &mut self,
18765 action: &FindAllReferences,
18766 window: &mut Window,
18767 cx: &mut Context<Self>,
18768 ) -> Option<Task<Result<Navigated>>> {
18769 let always_open_multibuffer = action.always_open_multibuffer;
18770 let selection = self.selections.newest_anchor();
18771 let multi_buffer = self.buffer.read(cx);
18772 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18773 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18774 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18775 let head = selection_offset.head();
18776
18777 let head_anchor = multi_buffer_snapshot.anchor_at(
18778 head,
18779 if head < selection_offset.tail() {
18780 Bias::Right
18781 } else {
18782 Bias::Left
18783 },
18784 );
18785
18786 match self
18787 .find_all_references_task_sources
18788 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18789 {
18790 Ok(_) => {
18791 log::info!(
18792 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18793 );
18794 return None;
18795 }
18796 Err(i) => {
18797 self.find_all_references_task_sources.insert(i, head_anchor);
18798 }
18799 }
18800
18801 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18802 let workspace = self.workspace()?;
18803 let project = workspace.read(cx).project().clone();
18804 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18805 Some(cx.spawn_in(window, async move |editor, cx| {
18806 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18807 if let Ok(i) = editor
18808 .find_all_references_task_sources
18809 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18810 {
18811 editor.find_all_references_task_sources.remove(i);
18812 }
18813 });
18814
18815 let Some(locations) = references.await? else {
18816 return anyhow::Ok(Navigated::No);
18817 };
18818 let mut locations = cx.update(|_, cx| {
18819 locations
18820 .into_iter()
18821 .map(|location| {
18822 let buffer = location.buffer.read(cx);
18823 (location.buffer, location.range.to_point(buffer))
18824 })
18825 // if special-casing the single-match case, remove ranges
18826 // that intersect current selection
18827 .filter(|(location_buffer, location)| {
18828 if always_open_multibuffer || &buffer != location_buffer {
18829 return true;
18830 }
18831
18832 !location.contains_inclusive(&selection_point.range())
18833 })
18834 .into_group_map()
18835 })?;
18836 if locations.is_empty() {
18837 return anyhow::Ok(Navigated::No);
18838 }
18839 for ranges in locations.values_mut() {
18840 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18841 ranges.dedup();
18842 }
18843 let mut num_locations = 0;
18844 for ranges in locations.values_mut() {
18845 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18846 ranges.dedup();
18847 num_locations += ranges.len();
18848 }
18849
18850 if num_locations == 1 && !always_open_multibuffer {
18851 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18852 let target_range = target_ranges.first().unwrap().clone();
18853
18854 return editor.update_in(cx, |editor, window, cx| {
18855 let range = target_range.to_point(target_buffer.read(cx));
18856 let range = editor.range_for_match(&range);
18857 let range = range.start..range.start;
18858
18859 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18860 editor.go_to_singleton_buffer_range(range, window, cx);
18861 } else {
18862 let pane = workspace.read(cx).active_pane().clone();
18863 window.defer(cx, move |window, cx| {
18864 let target_editor: Entity<Self> =
18865 workspace.update(cx, |workspace, cx| {
18866 let pane = workspace.active_pane().clone();
18867
18868 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18869 let keep_old_preview = preview_tabs_settings
18870 .enable_keep_preview_on_code_navigation;
18871 let allow_new_preview = preview_tabs_settings
18872 .enable_preview_file_from_code_navigation;
18873
18874 workspace.open_project_item(
18875 pane,
18876 target_buffer.clone(),
18877 true,
18878 true,
18879 keep_old_preview,
18880 allow_new_preview,
18881 window,
18882 cx,
18883 )
18884 });
18885 target_editor.update(cx, |target_editor, cx| {
18886 // When selecting a definition in a different buffer, disable the nav history
18887 // to avoid creating a history entry at the previous cursor location.
18888 pane.update(cx, |pane, _| pane.disable_history());
18889 target_editor.go_to_singleton_buffer_range(range, window, cx);
18890 pane.update(cx, |pane, _| pane.enable_history());
18891 });
18892 });
18893 }
18894 Navigated::No
18895 });
18896 }
18897
18898 workspace.update_in(cx, |workspace, window, cx| {
18899 let target = locations
18900 .iter()
18901 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18902 .map(|(buffer, location)| {
18903 buffer
18904 .read(cx)
18905 .text_for_range(location.clone())
18906 .collect::<String>()
18907 })
18908 .filter(|text| !text.contains('\n'))
18909 .unique()
18910 .take(3)
18911 .join(", ");
18912 let title = if target.is_empty() {
18913 "References".to_owned()
18914 } else {
18915 format!("References to {target}")
18916 };
18917 let allow_preview = PreviewTabsSettings::get_global(cx)
18918 .enable_preview_multibuffer_from_code_navigation;
18919 Self::open_locations_in_multibuffer(
18920 workspace,
18921 locations,
18922 title,
18923 false,
18924 allow_preview,
18925 MultibufferSelectionMode::First,
18926 window,
18927 cx,
18928 );
18929 Navigated::Yes
18930 })
18931 }))
18932 }
18933
18934 /// Opens a multibuffer with the given project locations in it.
18935 pub fn open_locations_in_multibuffer(
18936 workspace: &mut Workspace,
18937 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18938 title: String,
18939 split: bool,
18940 allow_preview: bool,
18941 multibuffer_selection_mode: MultibufferSelectionMode,
18942 window: &mut Window,
18943 cx: &mut Context<Workspace>,
18944 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18945 if locations.is_empty() {
18946 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18947 return None;
18948 }
18949
18950 let capability = workspace.project().read(cx).capability();
18951 let mut ranges = <Vec<Range<Anchor>>>::new();
18952
18953 // a key to find existing multibuffer editors with the same set of locations
18954 // to prevent us from opening more and more multibuffer tabs for searches and the like
18955 let mut key = (title.clone(), vec![]);
18956 let excerpt_buffer = cx.new(|cx| {
18957 let key = &mut key.1;
18958 let mut multibuffer = MultiBuffer::new(capability);
18959 for (buffer, mut ranges_for_buffer) in locations {
18960 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18961 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18962 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18963 PathKey::for_buffer(&buffer, cx),
18964 buffer.clone(),
18965 ranges_for_buffer,
18966 multibuffer_context_lines(cx),
18967 cx,
18968 );
18969 ranges.extend(new_ranges)
18970 }
18971
18972 multibuffer.with_title(title)
18973 });
18974 let existing = workspace.active_pane().update(cx, |pane, cx| {
18975 pane.items()
18976 .filter_map(|item| item.downcast::<Editor>())
18977 .find(|editor| {
18978 editor
18979 .read(cx)
18980 .lookup_key
18981 .as_ref()
18982 .and_then(|it| {
18983 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18984 })
18985 .is_some_and(|it| *it == key)
18986 })
18987 });
18988 let was_existing = existing.is_some();
18989 let editor = existing.unwrap_or_else(|| {
18990 cx.new(|cx| {
18991 let mut editor = Editor::for_multibuffer(
18992 excerpt_buffer,
18993 Some(workspace.project().clone()),
18994 window,
18995 cx,
18996 );
18997 editor.lookup_key = Some(Box::new(key));
18998 editor
18999 })
19000 });
19001 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
19002 MultibufferSelectionMode::First => {
19003 if let Some(first_range) = ranges.first() {
19004 editor.change_selections(
19005 SelectionEffects::no_scroll(),
19006 window,
19007 cx,
19008 |selections| {
19009 selections.clear_disjoint();
19010 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
19011 },
19012 );
19013 }
19014 editor.highlight_background(
19015 HighlightKey::Editor,
19016 &ranges,
19017 |_, theme| theme.colors().editor_highlighted_line_background,
19018 cx,
19019 );
19020 }
19021 MultibufferSelectionMode::All => {
19022 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19023 selections.clear_disjoint();
19024 selections.select_anchor_ranges(ranges);
19025 });
19026 }
19027 });
19028
19029 let item = Box::new(editor.clone());
19030
19031 let pane = if split {
19032 workspace.adjacent_pane(window, cx)
19033 } else {
19034 workspace.active_pane().clone()
19035 };
19036 let activate_pane = split;
19037
19038 let mut destination_index = None;
19039 pane.update(cx, |pane, cx| {
19040 if allow_preview && !was_existing {
19041 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
19042 }
19043 if was_existing && !allow_preview {
19044 pane.unpreview_item_if_preview(item.item_id());
19045 }
19046 pane.add_item(item, activate_pane, true, destination_index, window, cx);
19047 });
19048
19049 Some((editor, pane))
19050 }
19051
19052 pub fn rename(
19053 &mut self,
19054 _: &Rename,
19055 window: &mut Window,
19056 cx: &mut Context<Self>,
19057 ) -> Option<Task<Result<()>>> {
19058 use language::ToOffset as _;
19059
19060 let provider = self.semantics_provider.clone()?;
19061 let selection = self.selections.newest_anchor().clone();
19062 let (cursor_buffer, cursor_buffer_position) = self
19063 .buffer
19064 .read(cx)
19065 .text_anchor_for_position(selection.head(), cx)?;
19066 let (tail_buffer, cursor_buffer_position_end) = self
19067 .buffer
19068 .read(cx)
19069 .text_anchor_for_position(selection.tail(), cx)?;
19070 if tail_buffer != cursor_buffer {
19071 return None;
19072 }
19073
19074 let snapshot = cursor_buffer.read(cx).snapshot();
19075 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
19076 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
19077 let prepare_rename = provider
19078 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
19079 .unwrap_or_else(|| Task::ready(Ok(None)));
19080 drop(snapshot);
19081
19082 Some(cx.spawn_in(window, async move |this, cx| {
19083 let rename_range = if let Some(range) = prepare_rename.await? {
19084 Some(range)
19085 } else {
19086 this.update(cx, |this, cx| {
19087 let buffer = this.buffer.read(cx).snapshot(cx);
19088 let mut buffer_highlights = this
19089 .document_highlights_for_position(selection.head(), &buffer)
19090 .filter(|highlight| {
19091 highlight.start.excerpt_id == selection.head().excerpt_id
19092 && highlight.end.excerpt_id == selection.head().excerpt_id
19093 });
19094 buffer_highlights
19095 .next()
19096 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
19097 })?
19098 };
19099 if let Some(rename_range) = rename_range {
19100 this.update_in(cx, |this, window, cx| {
19101 let snapshot = cursor_buffer.read(cx).snapshot();
19102 let rename_buffer_range = rename_range.to_offset(&snapshot);
19103 let cursor_offset_in_rename_range =
19104 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
19105 let cursor_offset_in_rename_range_end =
19106 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
19107
19108 this.take_rename(false, window, cx);
19109 let buffer = this.buffer.read(cx).read(cx);
19110 let cursor_offset = selection.head().to_offset(&buffer);
19111 let rename_start =
19112 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
19113 let rename_end = rename_start + rename_buffer_range.len();
19114 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
19115 let mut old_highlight_id = None;
19116 let old_name: Arc<str> = buffer
19117 .chunks(rename_start..rename_end, true)
19118 .map(|chunk| {
19119 if old_highlight_id.is_none() {
19120 old_highlight_id = chunk.syntax_highlight_id;
19121 }
19122 chunk.text
19123 })
19124 .collect::<String>()
19125 .into();
19126
19127 drop(buffer);
19128
19129 // Position the selection in the rename editor so that it matches the current selection.
19130 this.show_local_selections = false;
19131 let rename_editor = cx.new(|cx| {
19132 let mut editor = Editor::single_line(window, cx);
19133 editor.buffer.update(cx, |buffer, cx| {
19134 buffer.edit(
19135 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
19136 None,
19137 cx,
19138 )
19139 });
19140 let cursor_offset_in_rename_range =
19141 MultiBufferOffset(cursor_offset_in_rename_range);
19142 let cursor_offset_in_rename_range_end =
19143 MultiBufferOffset(cursor_offset_in_rename_range_end);
19144 let rename_selection_range = match cursor_offset_in_rename_range
19145 .cmp(&cursor_offset_in_rename_range_end)
19146 {
19147 Ordering::Equal => {
19148 editor.select_all(&SelectAll, window, cx);
19149 return editor;
19150 }
19151 Ordering::Less => {
19152 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
19153 }
19154 Ordering::Greater => {
19155 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
19156 }
19157 };
19158 if rename_selection_range.end.0 > old_name.len() {
19159 editor.select_all(&SelectAll, window, cx);
19160 } else {
19161 editor.change_selections(Default::default(), window, cx, |s| {
19162 s.select_ranges([rename_selection_range]);
19163 });
19164 }
19165 editor
19166 });
19167 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19168 if e == &EditorEvent::Focused {
19169 cx.emit(EditorEvent::FocusedIn)
19170 }
19171 })
19172 .detach();
19173
19174 let write_highlights =
19175 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19176 let read_highlights =
19177 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19178 let ranges = write_highlights
19179 .iter()
19180 .flat_map(|(_, ranges)| ranges.iter())
19181 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19182 .cloned()
19183 .collect();
19184
19185 this.highlight_text(
19186 HighlightKey::Rename,
19187 ranges,
19188 HighlightStyle {
19189 fade_out: Some(0.6),
19190 ..Default::default()
19191 },
19192 cx,
19193 );
19194 let rename_focus_handle = rename_editor.focus_handle(cx);
19195 window.focus(&rename_focus_handle, cx);
19196 let block_id = this.insert_blocks(
19197 [BlockProperties {
19198 style: BlockStyle::Flex,
19199 placement: BlockPlacement::Below(range.start),
19200 height: Some(1),
19201 render: Arc::new({
19202 let rename_editor = rename_editor.clone();
19203 move |cx: &mut BlockContext| {
19204 let mut text_style = cx.editor_style.text.clone();
19205 if let Some(highlight_style) = old_highlight_id
19206 .and_then(|h| cx.editor_style.syntax.get(h).cloned())
19207 {
19208 text_style = text_style.highlight(highlight_style);
19209 }
19210 div()
19211 .block_mouse_except_scroll()
19212 .pl(cx.anchor_x)
19213 .child(EditorElement::new(
19214 &rename_editor,
19215 EditorStyle {
19216 background: cx.theme().system().transparent,
19217 local_player: cx.editor_style.local_player,
19218 text: text_style,
19219 scrollbar_width: cx.editor_style.scrollbar_width,
19220 syntax: cx.editor_style.syntax.clone(),
19221 status: cx.editor_style.status.clone(),
19222 inlay_hints_style: HighlightStyle {
19223 font_weight: Some(FontWeight::BOLD),
19224 ..make_inlay_hints_style(cx.app)
19225 },
19226 edit_prediction_styles: make_suggestion_styles(
19227 cx.app,
19228 ),
19229 ..EditorStyle::default()
19230 },
19231 ))
19232 .into_any_element()
19233 }
19234 }),
19235 priority: 0,
19236 }],
19237 Some(Autoscroll::fit()),
19238 cx,
19239 )[0];
19240 this.pending_rename = Some(RenameState {
19241 range,
19242 old_name,
19243 editor: rename_editor,
19244 block_id,
19245 });
19246 })?;
19247 }
19248
19249 Ok(())
19250 }))
19251 }
19252
19253 pub fn confirm_rename(
19254 &mut self,
19255 _: &ConfirmRename,
19256 window: &mut Window,
19257 cx: &mut Context<Self>,
19258 ) -> Option<Task<Result<()>>> {
19259 let rename = self.take_rename(false, window, cx)?;
19260 let workspace = self.workspace()?.downgrade();
19261 let (buffer, start) = self
19262 .buffer
19263 .read(cx)
19264 .text_anchor_for_position(rename.range.start, cx)?;
19265 let (end_buffer, _) = self
19266 .buffer
19267 .read(cx)
19268 .text_anchor_for_position(rename.range.end, cx)?;
19269 if buffer != end_buffer {
19270 return None;
19271 }
19272
19273 let old_name = rename.old_name;
19274 let new_name = rename.editor.read(cx).text(cx);
19275
19276 let rename = self.semantics_provider.as_ref()?.perform_rename(
19277 &buffer,
19278 start,
19279 new_name.clone(),
19280 cx,
19281 )?;
19282
19283 Some(cx.spawn_in(window, async move |editor, cx| {
19284 let project_transaction = rename.await?;
19285 Self::open_project_transaction(
19286 &editor,
19287 workspace,
19288 project_transaction,
19289 format!("Rename: {} → {}", old_name, new_name),
19290 cx,
19291 )
19292 .await?;
19293
19294 editor.update(cx, |editor, cx| {
19295 editor.refresh_document_highlights(cx);
19296 })?;
19297 Ok(())
19298 }))
19299 }
19300
19301 fn take_rename(
19302 &mut self,
19303 moving_cursor: bool,
19304 window: &mut Window,
19305 cx: &mut Context<Self>,
19306 ) -> Option<RenameState> {
19307 let rename = self.pending_rename.take()?;
19308 if rename.editor.focus_handle(cx).is_focused(window) {
19309 window.focus(&self.focus_handle, cx);
19310 }
19311
19312 self.remove_blocks(
19313 [rename.block_id].into_iter().collect(),
19314 Some(Autoscroll::fit()),
19315 cx,
19316 );
19317 self.clear_highlights(HighlightKey::Rename, cx);
19318 self.show_local_selections = true;
19319
19320 if moving_cursor {
19321 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19322 editor
19323 .selections
19324 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19325 .head()
19326 });
19327
19328 // Update the selection to match the position of the selection inside
19329 // the rename editor.
19330 let snapshot = self.buffer.read(cx).read(cx);
19331 let rename_range = rename.range.to_offset(&snapshot);
19332 let cursor_in_editor = snapshot
19333 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19334 .min(rename_range.end);
19335 drop(snapshot);
19336
19337 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19338 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19339 });
19340 } else {
19341 self.refresh_document_highlights(cx);
19342 }
19343
19344 Some(rename)
19345 }
19346
19347 pub fn pending_rename(&self) -> Option<&RenameState> {
19348 self.pending_rename.as_ref()
19349 }
19350
19351 fn format(
19352 &mut self,
19353 _: &Format,
19354 window: &mut Window,
19355 cx: &mut Context<Self>,
19356 ) -> Option<Task<Result<()>>> {
19357 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19358
19359 let project = match &self.project {
19360 Some(project) => project.clone(),
19361 None => return None,
19362 };
19363
19364 Some(self.perform_format(
19365 project,
19366 FormatTrigger::Manual,
19367 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19368 window,
19369 cx,
19370 ))
19371 }
19372
19373 fn format_selections(
19374 &mut self,
19375 _: &FormatSelections,
19376 window: &mut Window,
19377 cx: &mut Context<Self>,
19378 ) -> Option<Task<Result<()>>> {
19379 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19380
19381 let project = match &self.project {
19382 Some(project) => project.clone(),
19383 None => return None,
19384 };
19385
19386 let ranges = self
19387 .selections
19388 .all_adjusted(&self.display_snapshot(cx))
19389 .into_iter()
19390 .map(|selection| selection.range())
19391 .collect_vec();
19392
19393 Some(self.perform_format(
19394 project,
19395 FormatTrigger::Manual,
19396 FormatTarget::Ranges(ranges),
19397 window,
19398 cx,
19399 ))
19400 }
19401
19402 fn perform_format(
19403 &mut self,
19404 project: Entity<Project>,
19405 trigger: FormatTrigger,
19406 target: FormatTarget,
19407 window: &mut Window,
19408 cx: &mut Context<Self>,
19409 ) -> Task<Result<()>> {
19410 let buffer = self.buffer.clone();
19411 let (buffers, target) = match target {
19412 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19413 FormatTarget::Ranges(selection_ranges) => {
19414 let multi_buffer = buffer.read(cx);
19415 let snapshot = multi_buffer.read(cx);
19416 let mut buffers = HashSet::default();
19417 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19418 BTreeMap::new();
19419 for selection_range in selection_ranges {
19420 for (buffer, buffer_range, _) in
19421 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19422 {
19423 let buffer_id = buffer.remote_id();
19424 let start = buffer.anchor_before(buffer_range.start);
19425 let end = buffer.anchor_after(buffer_range.end);
19426 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19427 buffer_id_to_ranges
19428 .entry(buffer_id)
19429 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19430 .or_insert_with(|| vec![start..end]);
19431 }
19432 }
19433 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19434 }
19435 };
19436
19437 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19438 let selections_prev = transaction_id_prev
19439 .and_then(|transaction_id_prev| {
19440 // default to selections as they were after the last edit, if we have them,
19441 // instead of how they are now.
19442 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19443 // will take you back to where you made the last edit, instead of staying where you scrolled
19444 self.selection_history
19445 .transaction(transaction_id_prev)
19446 .map(|t| t.0.clone())
19447 })
19448 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19449
19450 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19451 let format = project.update(cx, |project, cx| {
19452 project.format(buffers, target, true, trigger, cx)
19453 });
19454
19455 cx.spawn_in(window, async move |editor, cx| {
19456 let transaction = futures::select_biased! {
19457 transaction = format.log_err().fuse() => transaction,
19458 () = timeout => {
19459 log::warn!("timed out waiting for formatting");
19460 None
19461 }
19462 };
19463
19464 buffer.update(cx, |buffer, cx| {
19465 if let Some(transaction) = transaction
19466 && !buffer.is_singleton()
19467 {
19468 buffer.push_transaction(&transaction.0, cx);
19469 }
19470 cx.notify();
19471 });
19472
19473 if let Some(transaction_id_now) =
19474 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19475 {
19476 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19477 if has_new_transaction {
19478 editor
19479 .update(cx, |editor, _| {
19480 editor
19481 .selection_history
19482 .insert_transaction(transaction_id_now, selections_prev);
19483 })
19484 .ok();
19485 }
19486 }
19487
19488 Ok(())
19489 })
19490 }
19491
19492 fn organize_imports(
19493 &mut self,
19494 _: &OrganizeImports,
19495 window: &mut Window,
19496 cx: &mut Context<Self>,
19497 ) -> Option<Task<Result<()>>> {
19498 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19499 let project = match &self.project {
19500 Some(project) => project.clone(),
19501 None => return None,
19502 };
19503 Some(self.perform_code_action_kind(
19504 project,
19505 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19506 window,
19507 cx,
19508 ))
19509 }
19510
19511 fn perform_code_action_kind(
19512 &mut self,
19513 project: Entity<Project>,
19514 kind: CodeActionKind,
19515 window: &mut Window,
19516 cx: &mut Context<Self>,
19517 ) -> Task<Result<()>> {
19518 let buffer = self.buffer.clone();
19519 let buffers = buffer.read(cx).all_buffers();
19520 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19521 let apply_action = project.update(cx, |project, cx| {
19522 project.apply_code_action_kind(buffers, kind, true, cx)
19523 });
19524 cx.spawn_in(window, async move |_, cx| {
19525 let transaction = futures::select_biased! {
19526 () = timeout => {
19527 log::warn!("timed out waiting for executing code action");
19528 None
19529 }
19530 transaction = apply_action.log_err().fuse() => transaction,
19531 };
19532 buffer.update(cx, |buffer, cx| {
19533 // check if we need this
19534 if let Some(transaction) = transaction
19535 && !buffer.is_singleton()
19536 {
19537 buffer.push_transaction(&transaction.0, cx);
19538 }
19539 cx.notify();
19540 });
19541 Ok(())
19542 })
19543 }
19544
19545 pub fn restart_language_server(
19546 &mut self,
19547 _: &RestartLanguageServer,
19548 _: &mut Window,
19549 cx: &mut Context<Self>,
19550 ) {
19551 if let Some(project) = self.project.clone() {
19552 self.buffer.update(cx, |multi_buffer, cx| {
19553 project.update(cx, |project, cx| {
19554 project.restart_language_servers_for_buffers(
19555 multi_buffer.all_buffers().into_iter().collect(),
19556 HashSet::default(),
19557 cx,
19558 );
19559 });
19560 })
19561 }
19562 }
19563
19564 pub fn stop_language_server(
19565 &mut self,
19566 _: &StopLanguageServer,
19567 _: &mut Window,
19568 cx: &mut Context<Self>,
19569 ) {
19570 if let Some(project) = self.project.clone() {
19571 self.buffer.update(cx, |multi_buffer, cx| {
19572 project.update(cx, |project, cx| {
19573 project.stop_language_servers_for_buffers(
19574 multi_buffer.all_buffers().into_iter().collect(),
19575 HashSet::default(),
19576 cx,
19577 );
19578 });
19579 });
19580 }
19581 }
19582
19583 fn cancel_language_server_work(
19584 workspace: &mut Workspace,
19585 _: &actions::CancelLanguageServerWork,
19586 _: &mut Window,
19587 cx: &mut Context<Workspace>,
19588 ) {
19589 let project = workspace.project();
19590 let buffers = workspace
19591 .active_item(cx)
19592 .and_then(|item| item.act_as::<Editor>(cx))
19593 .map_or(HashSet::default(), |editor| {
19594 editor.read(cx).buffer.read(cx).all_buffers()
19595 });
19596 project.update(cx, |project, cx| {
19597 project.cancel_language_server_work_for_buffers(buffers, cx);
19598 });
19599 }
19600
19601 fn show_character_palette(
19602 &mut self,
19603 _: &ShowCharacterPalette,
19604 window: &mut Window,
19605 _: &mut Context<Self>,
19606 ) {
19607 window.show_character_palette();
19608 }
19609
19610 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19611 if !self.diagnostics_enabled() {
19612 return;
19613 }
19614
19615 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19616 let buffer = self.buffer.read(cx).snapshot(cx);
19617 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19618 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19619 let is_valid = buffer
19620 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19621 .any(|entry| {
19622 entry.diagnostic.is_primary
19623 && !entry.range.is_empty()
19624 && entry.range.start == primary_range_start
19625 && entry.diagnostic.message == active_diagnostics.active_message
19626 });
19627
19628 if !is_valid {
19629 self.dismiss_diagnostics(cx);
19630 }
19631 }
19632 }
19633
19634 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19635 match &self.active_diagnostics {
19636 ActiveDiagnostic::Group(group) => Some(group),
19637 _ => None,
19638 }
19639 }
19640
19641 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19642 if !self.diagnostics_enabled() {
19643 return;
19644 }
19645 self.dismiss_diagnostics(cx);
19646 self.active_diagnostics = ActiveDiagnostic::All;
19647 }
19648
19649 fn activate_diagnostics(
19650 &mut self,
19651 buffer_id: BufferId,
19652 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19653 window: &mut Window,
19654 cx: &mut Context<Self>,
19655 ) {
19656 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19657 return;
19658 }
19659 self.dismiss_diagnostics(cx);
19660 let snapshot = self.snapshot(window, cx);
19661 let buffer = self.buffer.read(cx).snapshot(cx);
19662 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19663 return;
19664 };
19665
19666 let diagnostic_group = buffer
19667 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19668 .collect::<Vec<_>>();
19669
19670 let language_registry = self
19671 .project()
19672 .map(|project| project.read(cx).languages().clone());
19673
19674 let blocks = renderer.render_group(
19675 diagnostic_group,
19676 buffer_id,
19677 snapshot,
19678 cx.weak_entity(),
19679 language_registry,
19680 cx,
19681 );
19682
19683 let blocks = self.display_map.update(cx, |display_map, cx| {
19684 display_map.insert_blocks(blocks, cx).into_iter().collect()
19685 });
19686 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19687 active_range: buffer.anchor_before(diagnostic.range.start)
19688 ..buffer.anchor_after(diagnostic.range.end),
19689 active_message: diagnostic.diagnostic.message.clone(),
19690 group_id: diagnostic.diagnostic.group_id,
19691 blocks,
19692 });
19693 cx.notify();
19694 }
19695
19696 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19697 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19698 return;
19699 };
19700
19701 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19702 if let ActiveDiagnostic::Group(group) = prev {
19703 self.display_map.update(cx, |display_map, cx| {
19704 display_map.remove_blocks(group.blocks, cx);
19705 });
19706 cx.notify();
19707 }
19708 }
19709
19710 /// Disable inline diagnostics rendering for this editor.
19711 pub fn disable_inline_diagnostics(&mut self) {
19712 self.inline_diagnostics_enabled = false;
19713 self.inline_diagnostics_update = Task::ready(());
19714 self.inline_diagnostics.clear();
19715 }
19716
19717 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19718 self.diagnostics_enabled = false;
19719 self.dismiss_diagnostics(cx);
19720 self.inline_diagnostics_update = Task::ready(());
19721 self.inline_diagnostics.clear();
19722 }
19723
19724 pub fn disable_word_completions(&mut self) {
19725 self.word_completions_enabled = false;
19726 }
19727
19728 pub fn diagnostics_enabled(&self) -> bool {
19729 self.diagnostics_enabled && self.lsp_data_enabled()
19730 }
19731
19732 pub fn inline_diagnostics_enabled(&self) -> bool {
19733 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19734 }
19735
19736 pub fn show_inline_diagnostics(&self) -> bool {
19737 self.show_inline_diagnostics
19738 }
19739
19740 pub fn toggle_inline_diagnostics(
19741 &mut self,
19742 _: &ToggleInlineDiagnostics,
19743 window: &mut Window,
19744 cx: &mut Context<Editor>,
19745 ) {
19746 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19747 self.refresh_inline_diagnostics(false, window, cx);
19748 }
19749
19750 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19751 self.diagnostics_max_severity = severity;
19752 self.display_map.update(cx, |display_map, _| {
19753 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19754 });
19755 }
19756
19757 pub fn toggle_diagnostics(
19758 &mut self,
19759 _: &ToggleDiagnostics,
19760 window: &mut Window,
19761 cx: &mut Context<Editor>,
19762 ) {
19763 if !self.diagnostics_enabled() {
19764 return;
19765 }
19766
19767 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19768 EditorSettings::get_global(cx)
19769 .diagnostics_max_severity
19770 .filter(|severity| severity != &DiagnosticSeverity::Off)
19771 .unwrap_or(DiagnosticSeverity::Hint)
19772 } else {
19773 DiagnosticSeverity::Off
19774 };
19775 self.set_max_diagnostics_severity(new_severity, cx);
19776 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19777 self.active_diagnostics = ActiveDiagnostic::None;
19778 self.inline_diagnostics_update = Task::ready(());
19779 self.inline_diagnostics.clear();
19780 } else {
19781 self.refresh_inline_diagnostics(false, window, cx);
19782 }
19783
19784 cx.notify();
19785 }
19786
19787 pub fn toggle_minimap(
19788 &mut self,
19789 _: &ToggleMinimap,
19790 window: &mut Window,
19791 cx: &mut Context<Editor>,
19792 ) {
19793 if self.supports_minimap(cx) {
19794 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19795 }
19796 }
19797
19798 fn refresh_inline_diagnostics(
19799 &mut self,
19800 debounce: bool,
19801 window: &mut Window,
19802 cx: &mut Context<Self>,
19803 ) {
19804 let max_severity = ProjectSettings::get_global(cx)
19805 .diagnostics
19806 .inline
19807 .max_severity
19808 .unwrap_or(self.diagnostics_max_severity);
19809
19810 if !self.inline_diagnostics_enabled()
19811 || !self.diagnostics_enabled()
19812 || !self.show_inline_diagnostics
19813 || max_severity == DiagnosticSeverity::Off
19814 {
19815 self.inline_diagnostics_update = Task::ready(());
19816 self.inline_diagnostics.clear();
19817 return;
19818 }
19819
19820 let debounce_ms = ProjectSettings::get_global(cx)
19821 .diagnostics
19822 .inline
19823 .update_debounce_ms;
19824 let debounce = if debounce && debounce_ms > 0 {
19825 Some(Duration::from_millis(debounce_ms))
19826 } else {
19827 None
19828 };
19829 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19830 if let Some(debounce) = debounce {
19831 cx.background_executor().timer(debounce).await;
19832 }
19833 let Some(snapshot) = editor.upgrade().map(|editor| {
19834 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19835 }) else {
19836 return;
19837 };
19838
19839 let new_inline_diagnostics = cx
19840 .background_spawn(async move {
19841 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19842 for diagnostic_entry in
19843 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19844 {
19845 let message = diagnostic_entry
19846 .diagnostic
19847 .message
19848 .split_once('\n')
19849 .map(|(line, _)| line)
19850 .map(SharedString::new)
19851 .unwrap_or_else(|| {
19852 SharedString::new(&*diagnostic_entry.diagnostic.message)
19853 });
19854 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19855 let (Ok(i) | Err(i)) = inline_diagnostics
19856 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19857 inline_diagnostics.insert(
19858 i,
19859 (
19860 start_anchor,
19861 InlineDiagnostic {
19862 message,
19863 group_id: diagnostic_entry.diagnostic.group_id,
19864 start: diagnostic_entry.range.start.to_point(&snapshot),
19865 is_primary: diagnostic_entry.diagnostic.is_primary,
19866 severity: diagnostic_entry.diagnostic.severity,
19867 },
19868 ),
19869 );
19870 }
19871 inline_diagnostics
19872 })
19873 .await;
19874
19875 editor
19876 .update(cx, |editor, cx| {
19877 editor.inline_diagnostics = new_inline_diagnostics;
19878 cx.notify();
19879 })
19880 .ok();
19881 });
19882 }
19883
19884 fn pull_diagnostics(
19885 &mut self,
19886 buffer_id: BufferId,
19887 _window: &Window,
19888 cx: &mut Context<Self>,
19889 ) -> Option<()> {
19890 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19891 // skip any LSP updates for it.
19892
19893 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19894 return None;
19895 }
19896 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19897 .diagnostics
19898 .lsp_pull_diagnostics;
19899 if !pull_diagnostics_settings.enabled {
19900 return None;
19901 }
19902 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19903 let project = self.project()?.downgrade();
19904 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19905
19906 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19907 cx.background_executor().timer(debounce).await;
19908 if let Ok(task) = project.update(cx, |project, cx| {
19909 project.lsp_store().update(cx, |lsp_store, cx| {
19910 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19911 })
19912 }) {
19913 task.await.log_err();
19914 }
19915 project
19916 .update(cx, |project, cx| {
19917 project.lsp_store().update(cx, |lsp_store, cx| {
19918 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19919 })
19920 })
19921 .log_err();
19922 });
19923
19924 Some(())
19925 }
19926
19927 pub fn set_selections_from_remote(
19928 &mut self,
19929 selections: Vec<Selection<Anchor>>,
19930 pending_selection: Option<Selection<Anchor>>,
19931 window: &mut Window,
19932 cx: &mut Context<Self>,
19933 ) {
19934 let old_cursor_position = self.selections.newest_anchor().head();
19935 self.selections
19936 .change_with(&self.display_snapshot(cx), |s| {
19937 s.select_anchors(selections);
19938 if let Some(pending_selection) = pending_selection {
19939 s.set_pending(pending_selection, SelectMode::Character);
19940 } else {
19941 s.clear_pending();
19942 }
19943 });
19944 self.selections_did_change(
19945 false,
19946 &old_cursor_position,
19947 SelectionEffects::default(),
19948 window,
19949 cx,
19950 );
19951 }
19952
19953 pub fn transact(
19954 &mut self,
19955 window: &mut Window,
19956 cx: &mut Context<Self>,
19957 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19958 ) -> Option<TransactionId> {
19959 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19960 this.start_transaction_at(Instant::now(), window, cx);
19961 update(this, window, cx);
19962 this.end_transaction_at(Instant::now(), cx)
19963 })
19964 }
19965
19966 pub fn start_transaction_at(
19967 &mut self,
19968 now: Instant,
19969 window: &mut Window,
19970 cx: &mut Context<Self>,
19971 ) -> Option<TransactionId> {
19972 self.end_selection(window, cx);
19973 if let Some(tx_id) = self
19974 .buffer
19975 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19976 {
19977 self.selection_history
19978 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19979 cx.emit(EditorEvent::TransactionBegun {
19980 transaction_id: tx_id,
19981 });
19982 Some(tx_id)
19983 } else {
19984 None
19985 }
19986 }
19987
19988 pub fn end_transaction_at(
19989 &mut self,
19990 now: Instant,
19991 cx: &mut Context<Self>,
19992 ) -> Option<TransactionId> {
19993 if let Some(transaction_id) = self
19994 .buffer
19995 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19996 {
19997 if let Some((_, end_selections)) =
19998 self.selection_history.transaction_mut(transaction_id)
19999 {
20000 *end_selections = Some(self.selections.disjoint_anchors_arc());
20001 } else {
20002 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
20003 }
20004
20005 cx.emit(EditorEvent::Edited { transaction_id });
20006 Some(transaction_id)
20007 } else {
20008 None
20009 }
20010 }
20011
20012 pub fn modify_transaction_selection_history(
20013 &mut self,
20014 transaction_id: TransactionId,
20015 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
20016 ) -> bool {
20017 self.selection_history
20018 .transaction_mut(transaction_id)
20019 .map(modify)
20020 .is_some()
20021 }
20022
20023 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
20024 if self.selection_mark_mode {
20025 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20026 s.move_with(&mut |_, sel| {
20027 sel.collapse_to(sel.head(), SelectionGoal::None);
20028 });
20029 })
20030 }
20031 self.selection_mark_mode = true;
20032 cx.notify();
20033 }
20034
20035 pub fn swap_selection_ends(
20036 &mut self,
20037 _: &actions::SwapSelectionEnds,
20038 window: &mut Window,
20039 cx: &mut Context<Self>,
20040 ) {
20041 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20042 s.move_with(&mut |_, sel| {
20043 if sel.start != sel.end {
20044 sel.reversed = !sel.reversed
20045 }
20046 });
20047 });
20048 self.request_autoscroll(Autoscroll::newest(), cx);
20049 cx.notify();
20050 }
20051
20052 pub fn toggle_focus(
20053 workspace: &mut Workspace,
20054 _: &actions::ToggleFocus,
20055 window: &mut Window,
20056 cx: &mut Context<Workspace>,
20057 ) {
20058 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
20059 return;
20060 };
20061 workspace.activate_item(&item, true, true, window, cx);
20062 }
20063
20064 pub fn toggle_fold(
20065 &mut self,
20066 _: &actions::ToggleFold,
20067 window: &mut Window,
20068 cx: &mut Context<Self>,
20069 ) {
20070 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20071 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20072 let selection = self.selections.newest::<Point>(&display_map);
20073
20074 let range = if selection.is_empty() {
20075 let point = selection.head().to_display_point(&display_map);
20076 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20077 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20078 .to_point(&display_map);
20079 start..end
20080 } else {
20081 selection.range()
20082 };
20083 if display_map.folds_in_range(range).next().is_some() {
20084 self.unfold_lines(&Default::default(), window, cx)
20085 } else {
20086 self.fold(&Default::default(), window, cx)
20087 }
20088 } else {
20089 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20090 let buffer_ids: HashSet<_> = self
20091 .selections
20092 .disjoint_anchor_ranges()
20093 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20094 .collect();
20095
20096 let should_unfold = buffer_ids
20097 .iter()
20098 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20099
20100 for buffer_id in buffer_ids {
20101 if should_unfold {
20102 self.unfold_buffer(buffer_id, cx);
20103 } else {
20104 self.fold_buffer(buffer_id, cx);
20105 }
20106 }
20107 }
20108 }
20109
20110 pub fn toggle_fold_recursive(
20111 &mut self,
20112 _: &actions::ToggleFoldRecursive,
20113 window: &mut Window,
20114 cx: &mut Context<Self>,
20115 ) {
20116 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20117
20118 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20119 let range = if selection.is_empty() {
20120 let point = selection.head().to_display_point(&display_map);
20121 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20122 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20123 .to_point(&display_map);
20124 start..end
20125 } else {
20126 selection.range()
20127 };
20128 if display_map.folds_in_range(range).next().is_some() {
20129 self.unfold_recursive(&Default::default(), window, cx)
20130 } else {
20131 self.fold_recursive(&Default::default(), window, cx)
20132 }
20133 }
20134
20135 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
20136 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20137 let mut to_fold = Vec::new();
20138 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20139 let selections = self.selections.all_adjusted(&display_map);
20140
20141 for selection in selections {
20142 let range = selection.range().sorted();
20143 let buffer_start_row = range.start.row;
20144
20145 if range.start.row != range.end.row {
20146 let mut found = false;
20147 let mut row = range.start.row;
20148 while row <= range.end.row {
20149 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20150 {
20151 found = true;
20152 row = crease.range().end.row + 1;
20153 to_fold.push(crease);
20154 } else {
20155 row += 1
20156 }
20157 }
20158 if found {
20159 continue;
20160 }
20161 }
20162
20163 for row in (0..=range.start.row).rev() {
20164 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20165 && crease.range().end.row >= buffer_start_row
20166 {
20167 to_fold.push(crease);
20168 if row <= range.start.row {
20169 break;
20170 }
20171 }
20172 }
20173 }
20174
20175 self.fold_creases(to_fold, true, window, cx);
20176 } else {
20177 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20178 let buffer_ids = self
20179 .selections
20180 .disjoint_anchor_ranges()
20181 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20182 .collect::<HashSet<_>>();
20183 for buffer_id in buffer_ids {
20184 self.fold_buffer(buffer_id, cx);
20185 }
20186 }
20187 }
20188
20189 pub fn toggle_fold_all(
20190 &mut self,
20191 _: &actions::ToggleFoldAll,
20192 window: &mut Window,
20193 cx: &mut Context<Self>,
20194 ) {
20195 let has_folds = if self.buffer.read(cx).is_singleton() {
20196 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20197 let has_folds = display_map
20198 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20199 .next()
20200 .is_some();
20201 has_folds
20202 } else {
20203 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
20204 let has_folds = buffer_ids
20205 .iter()
20206 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20207 has_folds
20208 };
20209
20210 if has_folds {
20211 self.unfold_all(&actions::UnfoldAll, window, cx);
20212 } else {
20213 self.fold_all(&actions::FoldAll, window, cx);
20214 }
20215 }
20216
20217 fn fold_at_level(
20218 &mut self,
20219 fold_at: &FoldAtLevel,
20220 window: &mut Window,
20221 cx: &mut Context<Self>,
20222 ) {
20223 if !self.buffer.read(cx).is_singleton() {
20224 return;
20225 }
20226
20227 let fold_at_level = fold_at.0;
20228 let snapshot = self.buffer.read(cx).snapshot(cx);
20229 let mut to_fold = Vec::new();
20230 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20231
20232 let row_ranges_to_keep: Vec<Range<u32>> = self
20233 .selections
20234 .all::<Point>(&self.display_snapshot(cx))
20235 .into_iter()
20236 .map(|sel| sel.start.row..sel.end.row)
20237 .collect();
20238
20239 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20240 while start_row < end_row {
20241 match self
20242 .snapshot(window, cx)
20243 .crease_for_buffer_row(MultiBufferRow(start_row))
20244 {
20245 Some(crease) => {
20246 let nested_start_row = crease.range().start.row + 1;
20247 let nested_end_row = crease.range().end.row;
20248
20249 if current_level < fold_at_level {
20250 stack.push((nested_start_row, nested_end_row, current_level + 1));
20251 } else if current_level == fold_at_level {
20252 // Fold iff there is no selection completely contained within the fold region
20253 if !row_ranges_to_keep.iter().any(|selection| {
20254 selection.end >= nested_start_row
20255 && selection.start <= nested_end_row
20256 }) {
20257 to_fold.push(crease);
20258 }
20259 }
20260
20261 start_row = nested_end_row + 1;
20262 }
20263 None => start_row += 1,
20264 }
20265 }
20266 }
20267
20268 self.fold_creases(to_fold, true, window, cx);
20269 }
20270
20271 pub fn fold_at_level_1(
20272 &mut self,
20273 _: &actions::FoldAtLevel1,
20274 window: &mut Window,
20275 cx: &mut Context<Self>,
20276 ) {
20277 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20278 }
20279
20280 pub fn fold_at_level_2(
20281 &mut self,
20282 _: &actions::FoldAtLevel2,
20283 window: &mut Window,
20284 cx: &mut Context<Self>,
20285 ) {
20286 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20287 }
20288
20289 pub fn fold_at_level_3(
20290 &mut self,
20291 _: &actions::FoldAtLevel3,
20292 window: &mut Window,
20293 cx: &mut Context<Self>,
20294 ) {
20295 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20296 }
20297
20298 pub fn fold_at_level_4(
20299 &mut self,
20300 _: &actions::FoldAtLevel4,
20301 window: &mut Window,
20302 cx: &mut Context<Self>,
20303 ) {
20304 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20305 }
20306
20307 pub fn fold_at_level_5(
20308 &mut self,
20309 _: &actions::FoldAtLevel5,
20310 window: &mut Window,
20311 cx: &mut Context<Self>,
20312 ) {
20313 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20314 }
20315
20316 pub fn fold_at_level_6(
20317 &mut self,
20318 _: &actions::FoldAtLevel6,
20319 window: &mut Window,
20320 cx: &mut Context<Self>,
20321 ) {
20322 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20323 }
20324
20325 pub fn fold_at_level_7(
20326 &mut self,
20327 _: &actions::FoldAtLevel7,
20328 window: &mut Window,
20329 cx: &mut Context<Self>,
20330 ) {
20331 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20332 }
20333
20334 pub fn fold_at_level_8(
20335 &mut self,
20336 _: &actions::FoldAtLevel8,
20337 window: &mut Window,
20338 cx: &mut Context<Self>,
20339 ) {
20340 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20341 }
20342
20343 pub fn fold_at_level_9(
20344 &mut self,
20345 _: &actions::FoldAtLevel9,
20346 window: &mut Window,
20347 cx: &mut Context<Self>,
20348 ) {
20349 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20350 }
20351
20352 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20353 if self.buffer.read(cx).is_singleton() {
20354 let mut fold_ranges = Vec::new();
20355 let snapshot = self.buffer.read(cx).snapshot(cx);
20356
20357 for row in 0..snapshot.max_row().0 {
20358 if let Some(foldable_range) = self
20359 .snapshot(window, cx)
20360 .crease_for_buffer_row(MultiBufferRow(row))
20361 {
20362 fold_ranges.push(foldable_range);
20363 }
20364 }
20365
20366 self.fold_creases(fold_ranges, true, window, cx);
20367 } else {
20368 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20369 editor
20370 .update_in(cx, |editor, _, cx| {
20371 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20372 editor.fold_buffer(buffer_id, cx);
20373 }
20374 })
20375 .ok();
20376 });
20377 }
20378 }
20379
20380 pub fn fold_function_bodies(
20381 &mut self,
20382 _: &actions::FoldFunctionBodies,
20383 window: &mut Window,
20384 cx: &mut Context<Self>,
20385 ) {
20386 let snapshot = self.buffer.read(cx).snapshot(cx);
20387
20388 let ranges = snapshot
20389 .text_object_ranges(
20390 MultiBufferOffset(0)..snapshot.len(),
20391 TreeSitterOptions::default(),
20392 )
20393 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20394 .collect::<Vec<_>>();
20395
20396 let creases = ranges
20397 .into_iter()
20398 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20399 .collect();
20400
20401 self.fold_creases(creases, true, window, cx);
20402 }
20403
20404 pub fn fold_recursive(
20405 &mut self,
20406 _: &actions::FoldRecursive,
20407 window: &mut Window,
20408 cx: &mut Context<Self>,
20409 ) {
20410 let mut to_fold = Vec::new();
20411 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20412 let selections = self.selections.all_adjusted(&display_map);
20413
20414 for selection in selections {
20415 let range = selection.range().sorted();
20416 let buffer_start_row = range.start.row;
20417
20418 if range.start.row != range.end.row {
20419 let mut found = false;
20420 for row in range.start.row..=range.end.row {
20421 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20422 found = true;
20423 to_fold.push(crease);
20424 }
20425 }
20426 if found {
20427 continue;
20428 }
20429 }
20430
20431 for row in (0..=range.start.row).rev() {
20432 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20433 if crease.range().end.row >= buffer_start_row {
20434 to_fold.push(crease);
20435 } else {
20436 break;
20437 }
20438 }
20439 }
20440 }
20441
20442 self.fold_creases(to_fold, true, window, cx);
20443 }
20444
20445 pub fn fold_at(
20446 &mut self,
20447 buffer_row: MultiBufferRow,
20448 window: &mut Window,
20449 cx: &mut Context<Self>,
20450 ) {
20451 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20452
20453 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20454 let autoscroll = self
20455 .selections
20456 .all::<Point>(&display_map)
20457 .iter()
20458 .any(|selection| crease.range().overlaps(&selection.range()));
20459
20460 self.fold_creases(vec![crease], autoscroll, window, cx);
20461 }
20462 }
20463
20464 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20465 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20466 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20467 let buffer = display_map.buffer_snapshot();
20468 let selections = self.selections.all::<Point>(&display_map);
20469 let ranges = selections
20470 .iter()
20471 .map(|s| {
20472 let range = s.display_range(&display_map).sorted();
20473 let mut start = range.start.to_point(&display_map);
20474 let mut end = range.end.to_point(&display_map);
20475 start.column = 0;
20476 end.column = buffer.line_len(MultiBufferRow(end.row));
20477 start..end
20478 })
20479 .collect::<Vec<_>>();
20480
20481 self.unfold_ranges(&ranges, true, true, cx);
20482 } else {
20483 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20484 let buffer_ids = self
20485 .selections
20486 .disjoint_anchor_ranges()
20487 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20488 .collect::<HashSet<_>>();
20489 for buffer_id in buffer_ids {
20490 self.unfold_buffer(buffer_id, cx);
20491 }
20492 }
20493 }
20494
20495 pub fn unfold_recursive(
20496 &mut self,
20497 _: &UnfoldRecursive,
20498 _window: &mut Window,
20499 cx: &mut Context<Self>,
20500 ) {
20501 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20502 let selections = self.selections.all::<Point>(&display_map);
20503 let ranges = selections
20504 .iter()
20505 .map(|s| {
20506 let mut range = s.display_range(&display_map).sorted();
20507 *range.start.column_mut() = 0;
20508 *range.end.column_mut() = display_map.line_len(range.end.row());
20509 let start = range.start.to_point(&display_map);
20510 let end = range.end.to_point(&display_map);
20511 start..end
20512 })
20513 .collect::<Vec<_>>();
20514
20515 self.unfold_ranges(&ranges, true, true, cx);
20516 }
20517
20518 pub fn unfold_at(
20519 &mut self,
20520 buffer_row: MultiBufferRow,
20521 _window: &mut Window,
20522 cx: &mut Context<Self>,
20523 ) {
20524 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20525
20526 let intersection_range = Point::new(buffer_row.0, 0)
20527 ..Point::new(
20528 buffer_row.0,
20529 display_map.buffer_snapshot().line_len(buffer_row),
20530 );
20531
20532 let autoscroll = self
20533 .selections
20534 .all::<Point>(&display_map)
20535 .iter()
20536 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20537
20538 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20539 }
20540
20541 pub fn unfold_all(
20542 &mut self,
20543 _: &actions::UnfoldAll,
20544 _window: &mut Window,
20545 cx: &mut Context<Self>,
20546 ) {
20547 if self.buffer.read(cx).is_singleton() {
20548 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20549 self.unfold_ranges(
20550 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20551 true,
20552 true,
20553 cx,
20554 );
20555 } else {
20556 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20557 editor
20558 .update(cx, |editor, cx| {
20559 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20560 editor.unfold_buffer(buffer_id, cx);
20561 }
20562 })
20563 .ok();
20564 });
20565 }
20566 }
20567
20568 pub fn fold_selected_ranges(
20569 &mut self,
20570 _: &FoldSelectedRanges,
20571 window: &mut Window,
20572 cx: &mut Context<Self>,
20573 ) {
20574 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20575 let selections = self.selections.all_adjusted(&display_map);
20576 let ranges = selections
20577 .into_iter()
20578 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20579 .collect::<Vec<_>>();
20580 self.fold_creases(ranges, true, window, cx);
20581 }
20582
20583 pub fn fold_ranges<T: ToOffset + Clone>(
20584 &mut self,
20585 ranges: Vec<Range<T>>,
20586 auto_scroll: bool,
20587 window: &mut Window,
20588 cx: &mut Context<Self>,
20589 ) {
20590 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20591 let ranges = ranges
20592 .into_iter()
20593 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20594 .collect::<Vec<_>>();
20595 self.fold_creases(ranges, auto_scroll, window, cx);
20596 }
20597
20598 pub fn fold_creases<T: ToOffset + Clone>(
20599 &mut self,
20600 creases: Vec<Crease<T>>,
20601 auto_scroll: bool,
20602 window: &mut Window,
20603 cx: &mut Context<Self>,
20604 ) {
20605 if creases.is_empty() {
20606 return;
20607 }
20608
20609 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20610
20611 if auto_scroll {
20612 self.request_autoscroll(Autoscroll::fit(), cx);
20613 }
20614
20615 cx.notify();
20616
20617 self.scrollbar_marker_state.dirty = true;
20618 self.update_data_on_scroll(window, cx);
20619 self.folds_did_change(cx);
20620 }
20621
20622 /// Removes any folds whose ranges intersect any of the given ranges.
20623 pub fn unfold_ranges<T: ToOffset + Clone>(
20624 &mut self,
20625 ranges: &[Range<T>],
20626 inclusive: bool,
20627 auto_scroll: bool,
20628 cx: &mut Context<Self>,
20629 ) {
20630 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20631 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20632 });
20633 self.folds_did_change(cx);
20634 }
20635
20636 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20637 self.fold_buffers([buffer_id], cx);
20638 }
20639
20640 pub fn fold_buffers(
20641 &mut self,
20642 buffer_ids: impl IntoIterator<Item = BufferId>,
20643 cx: &mut Context<Self>,
20644 ) {
20645 if self.buffer().read(cx).is_singleton() {
20646 return;
20647 }
20648
20649 let ids_to_fold: Vec<BufferId> = buffer_ids
20650 .into_iter()
20651 .filter(|id| !self.is_buffer_folded(*id, cx))
20652 .collect();
20653
20654 if ids_to_fold.is_empty() {
20655 return;
20656 }
20657
20658 let mut all_folded_excerpt_ids = Vec::new();
20659 for buffer_id in &ids_to_fold {
20660 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20661 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id));
20662 }
20663
20664 self.display_map.update(cx, |display_map, cx| {
20665 display_map.fold_buffers(ids_to_fold.clone(), cx)
20666 });
20667
20668 let snapshot = self.display_snapshot(cx);
20669 self.selections.change_with(&snapshot, |selections| {
20670 for buffer_id in ids_to_fold {
20671 selections.remove_selections_from_buffer(buffer_id);
20672 }
20673 });
20674
20675 cx.emit(EditorEvent::BufferFoldToggled {
20676 ids: all_folded_excerpt_ids,
20677 folded: true,
20678 });
20679 cx.notify();
20680 }
20681
20682 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20683 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20684 return;
20685 }
20686 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20687 self.display_map.update(cx, |display_map, cx| {
20688 display_map.unfold_buffers([buffer_id], cx);
20689 });
20690 cx.emit(EditorEvent::BufferFoldToggled {
20691 ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(),
20692 folded: false,
20693 });
20694 cx.notify();
20695 }
20696
20697 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20698 self.display_map.read(cx).is_buffer_folded(buffer)
20699 }
20700
20701 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20702 if self.buffer().read(cx).is_singleton() {
20703 return false;
20704 }
20705 !self.folded_buffers(cx).is_empty()
20706 }
20707
20708 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20709 self.display_map.read(cx).folded_buffers()
20710 }
20711
20712 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20713 self.display_map.update(cx, |display_map, cx| {
20714 display_map.disable_header_for_buffer(buffer_id, cx);
20715 });
20716 cx.notify();
20717 }
20718
20719 /// Removes any folds with the given ranges.
20720 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20721 &mut self,
20722 ranges: &[Range<T>],
20723 type_id: TypeId,
20724 auto_scroll: bool,
20725 cx: &mut Context<Self>,
20726 ) {
20727 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20728 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20729 });
20730 self.folds_did_change(cx);
20731 }
20732
20733 fn remove_folds_with<T: ToOffset + Clone>(
20734 &mut self,
20735 ranges: &[Range<T>],
20736 auto_scroll: bool,
20737 cx: &mut Context<Self>,
20738 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20739 ) {
20740 if ranges.is_empty() {
20741 return;
20742 }
20743
20744 let mut buffers_affected = HashSet::default();
20745 let multi_buffer = self.buffer().read(cx);
20746 for range in ranges {
20747 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20748 buffers_affected.insert(buffer.read(cx).remote_id());
20749 };
20750 }
20751
20752 self.display_map.update(cx, update);
20753
20754 if auto_scroll {
20755 self.request_autoscroll(Autoscroll::fit(), cx);
20756 }
20757
20758 cx.notify();
20759 self.scrollbar_marker_state.dirty = true;
20760 self.active_indent_guides_state.dirty = true;
20761 }
20762
20763 pub fn update_renderer_widths(
20764 &mut self,
20765 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20766 cx: &mut Context<Self>,
20767 ) -> bool {
20768 self.display_map
20769 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20770 }
20771
20772 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20773 self.display_map.read(cx).fold_placeholder.clone()
20774 }
20775
20776 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20777 self.buffer.update(cx, |buffer, cx| {
20778 buffer.set_all_diff_hunks_expanded(cx);
20779 });
20780 }
20781
20782 pub fn expand_all_diff_hunks(
20783 &mut self,
20784 _: &ExpandAllDiffHunks,
20785 _window: &mut Window,
20786 cx: &mut Context<Self>,
20787 ) {
20788 self.buffer.update(cx, |buffer, cx| {
20789 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20790 });
20791 }
20792
20793 pub fn collapse_all_diff_hunks(
20794 &mut self,
20795 _: &CollapseAllDiffHunks,
20796 _window: &mut Window,
20797 cx: &mut Context<Self>,
20798 ) {
20799 self.buffer.update(cx, |buffer, cx| {
20800 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20801 });
20802 }
20803
20804 pub fn toggle_selected_diff_hunks(
20805 &mut self,
20806 _: &ToggleSelectedDiffHunks,
20807 _window: &mut Window,
20808 cx: &mut Context<Self>,
20809 ) {
20810 let ranges: Vec<_> = self
20811 .selections
20812 .disjoint_anchors()
20813 .iter()
20814 .map(|s| s.range())
20815 .collect();
20816 self.toggle_diff_hunks_in_ranges(ranges, cx);
20817 }
20818
20819 pub fn diff_hunks_in_ranges<'a>(
20820 &'a self,
20821 ranges: &'a [Range<Anchor>],
20822 buffer: &'a MultiBufferSnapshot,
20823 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20824 ranges.iter().flat_map(move |range| {
20825 let end_excerpt_id = range.end.excerpt_id;
20826 let range = range.to_point(buffer);
20827 let mut peek_end = range.end;
20828 if range.end.row < buffer.max_row().0 {
20829 peek_end = Point::new(range.end.row + 1, 0);
20830 }
20831 buffer
20832 .diff_hunks_in_range(range.start..peek_end)
20833 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20834 })
20835 }
20836
20837 pub fn has_stageable_diff_hunks_in_ranges(
20838 &self,
20839 ranges: &[Range<Anchor>],
20840 snapshot: &MultiBufferSnapshot,
20841 ) -> bool {
20842 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20843 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20844 }
20845
20846 pub fn toggle_staged_selected_diff_hunks(
20847 &mut self,
20848 _: &::git::ToggleStaged,
20849 _: &mut Window,
20850 cx: &mut Context<Self>,
20851 ) {
20852 let snapshot = self.buffer.read(cx).snapshot(cx);
20853 let ranges: Vec<_> = self
20854 .selections
20855 .disjoint_anchors()
20856 .iter()
20857 .map(|s| s.range())
20858 .collect();
20859 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20860 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20861 }
20862
20863 pub fn set_render_diff_hunk_controls(
20864 &mut self,
20865 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20866 cx: &mut Context<Self>,
20867 ) {
20868 self.render_diff_hunk_controls = render_diff_hunk_controls;
20869 cx.notify();
20870 }
20871
20872 pub fn stage_and_next(
20873 &mut self,
20874 _: &::git::StageAndNext,
20875 window: &mut Window,
20876 cx: &mut Context<Self>,
20877 ) {
20878 self.do_stage_or_unstage_and_next(true, window, cx);
20879 }
20880
20881 pub fn unstage_and_next(
20882 &mut self,
20883 _: &::git::UnstageAndNext,
20884 window: &mut Window,
20885 cx: &mut Context<Self>,
20886 ) {
20887 self.do_stage_or_unstage_and_next(false, window, cx);
20888 }
20889
20890 pub fn stage_or_unstage_diff_hunks(
20891 &mut self,
20892 stage: bool,
20893 ranges: Vec<Range<Anchor>>,
20894 cx: &mut Context<Self>,
20895 ) {
20896 if self.delegate_stage_and_restore {
20897 let snapshot = self.buffer.read(cx).snapshot(cx);
20898 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20899 if !hunks.is_empty() {
20900 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20901 }
20902 return;
20903 }
20904 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20905 cx.spawn(async move |this, cx| {
20906 task.await?;
20907 this.update(cx, |this, cx| {
20908 let snapshot = this.buffer.read(cx).snapshot(cx);
20909 let chunk_by = this
20910 .diff_hunks_in_ranges(&ranges, &snapshot)
20911 .chunk_by(|hunk| hunk.buffer_id);
20912 for (buffer_id, hunks) in &chunk_by {
20913 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20914 }
20915 })
20916 })
20917 .detach_and_log_err(cx);
20918 }
20919
20920 fn save_buffers_for_ranges_if_needed(
20921 &mut self,
20922 ranges: &[Range<Anchor>],
20923 cx: &mut Context<Editor>,
20924 ) -> Task<Result<()>> {
20925 let multibuffer = self.buffer.read(cx);
20926 let snapshot = multibuffer.read(cx);
20927 let buffer_ids: HashSet<_> = ranges
20928 .iter()
20929 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20930 .collect();
20931 drop(snapshot);
20932
20933 let mut buffers = HashSet::default();
20934 for buffer_id in buffer_ids {
20935 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20936 let buffer = buffer_entity.read(cx);
20937 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20938 {
20939 buffers.insert(buffer_entity);
20940 }
20941 }
20942 }
20943
20944 if let Some(project) = &self.project {
20945 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20946 } else {
20947 Task::ready(Ok(()))
20948 }
20949 }
20950
20951 fn do_stage_or_unstage_and_next(
20952 &mut self,
20953 stage: bool,
20954 window: &mut Window,
20955 cx: &mut Context<Self>,
20956 ) {
20957 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20958
20959 if ranges.iter().any(|range| range.start != range.end) {
20960 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20961 return;
20962 }
20963
20964 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20965
20966 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20967 let wrap_around = !all_diff_hunks_expanded;
20968 let snapshot = self.snapshot(window, cx);
20969 let position = self
20970 .selections
20971 .newest::<Point>(&snapshot.display_snapshot)
20972 .head();
20973
20974 self.go_to_hunk_before_or_after_position(
20975 &snapshot,
20976 position,
20977 Direction::Next,
20978 wrap_around,
20979 window,
20980 cx,
20981 );
20982 }
20983
20984 pub(crate) fn do_stage_or_unstage(
20985 &self,
20986 stage: bool,
20987 buffer_id: BufferId,
20988 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20989 cx: &mut App,
20990 ) -> Option<()> {
20991 let project = self.project()?;
20992 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20993 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20994 let buffer_snapshot = buffer.read(cx).snapshot();
20995 let file_exists = buffer_snapshot
20996 .file()
20997 .is_some_and(|file| file.disk_state().exists());
20998 diff.update(cx, |diff, cx| {
20999 diff.stage_or_unstage_hunks(
21000 stage,
21001 &hunks
21002 .map(|hunk| buffer_diff::DiffHunk {
21003 buffer_range: hunk.buffer_range,
21004 // We don't need to pass in word diffs here because they're only used for rendering and
21005 // this function changes internal state
21006 base_word_diffs: Vec::default(),
21007 buffer_word_diffs: Vec::default(),
21008 diff_base_byte_range: hunk.diff_base_byte_range.start.0
21009 ..hunk.diff_base_byte_range.end.0,
21010 secondary_status: hunk.status.secondary,
21011 range: Point::zero()..Point::zero(), // unused
21012 })
21013 .collect::<Vec<_>>(),
21014 &buffer_snapshot,
21015 file_exists,
21016 cx,
21017 )
21018 });
21019 None
21020 }
21021
21022 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
21023 let ranges: Vec<_> = self
21024 .selections
21025 .disjoint_anchors()
21026 .iter()
21027 .map(|s| s.range())
21028 .collect();
21029 self.buffer
21030 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
21031 }
21032
21033 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
21034 self.buffer.update(cx, |buffer, cx| {
21035 let ranges = vec![Anchor::min()..Anchor::max()];
21036 if !buffer.all_diff_hunks_expanded()
21037 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
21038 {
21039 buffer.collapse_diff_hunks(ranges, cx);
21040 true
21041 } else {
21042 false
21043 }
21044 })
21045 }
21046
21047 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
21048 if self.buffer.read(cx).all_diff_hunks_expanded() {
21049 return true;
21050 }
21051 let ranges = vec![Anchor::min()..Anchor::max()];
21052 self.buffer
21053 .read(cx)
21054 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
21055 }
21056
21057 fn toggle_diff_hunks_in_ranges(
21058 &mut self,
21059 ranges: Vec<Range<Anchor>>,
21060 cx: &mut Context<Editor>,
21061 ) {
21062 self.buffer.update(cx, |buffer, cx| {
21063 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
21064 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
21065 })
21066 }
21067
21068 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
21069 self.buffer.update(cx, |buffer, cx| {
21070 buffer.toggle_single_diff_hunk(range, cx);
21071 })
21072 }
21073
21074 pub(crate) fn apply_all_diff_hunks(
21075 &mut self,
21076 _: &ApplyAllDiffHunks,
21077 window: &mut Window,
21078 cx: &mut Context<Self>,
21079 ) {
21080 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21081
21082 let buffers = self.buffer.read(cx).all_buffers();
21083 for branch_buffer in buffers {
21084 branch_buffer.update(cx, |branch_buffer, cx| {
21085 branch_buffer.merge_into_base(Vec::new(), cx);
21086 });
21087 }
21088
21089 if let Some(project) = self.project.clone() {
21090 self.save(
21091 SaveOptions {
21092 format: true,
21093 autosave: false,
21094 },
21095 project,
21096 window,
21097 cx,
21098 )
21099 .detach_and_log_err(cx);
21100 }
21101 }
21102
21103 pub(crate) fn apply_selected_diff_hunks(
21104 &mut self,
21105 _: &ApplyDiffHunk,
21106 window: &mut Window,
21107 cx: &mut Context<Self>,
21108 ) {
21109 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21110 let snapshot = self.snapshot(window, cx);
21111 let hunks = snapshot.hunks_for_ranges(
21112 self.selections
21113 .all(&snapshot.display_snapshot)
21114 .into_iter()
21115 .map(|selection| selection.range()),
21116 );
21117 let mut ranges_by_buffer = HashMap::default();
21118 self.transact(window, cx, |editor, _window, cx| {
21119 for hunk in hunks {
21120 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
21121 ranges_by_buffer
21122 .entry(buffer.clone())
21123 .or_insert_with(Vec::new)
21124 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
21125 }
21126 }
21127
21128 for (buffer, ranges) in ranges_by_buffer {
21129 buffer.update(cx, |buffer, cx| {
21130 buffer.merge_into_base(ranges, cx);
21131 });
21132 }
21133 });
21134
21135 if let Some(project) = self.project.clone() {
21136 self.save(
21137 SaveOptions {
21138 format: true,
21139 autosave: false,
21140 },
21141 project,
21142 window,
21143 cx,
21144 )
21145 .detach_and_log_err(cx);
21146 }
21147 }
21148
21149 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
21150 if hovered != self.gutter_hovered {
21151 self.gutter_hovered = hovered;
21152 cx.notify();
21153 }
21154 }
21155
21156 pub fn insert_blocks(
21157 &mut self,
21158 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
21159 autoscroll: Option<Autoscroll>,
21160 cx: &mut Context<Self>,
21161 ) -> Vec<CustomBlockId> {
21162 let blocks = self
21163 .display_map
21164 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
21165 if let Some(autoscroll) = autoscroll {
21166 self.request_autoscroll(autoscroll, cx);
21167 }
21168 cx.notify();
21169 blocks
21170 }
21171
21172 pub fn resize_blocks(
21173 &mut self,
21174 heights: HashMap<CustomBlockId, u32>,
21175 autoscroll: Option<Autoscroll>,
21176 cx: &mut Context<Self>,
21177 ) {
21178 self.display_map
21179 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21180 if let Some(autoscroll) = autoscroll {
21181 self.request_autoscroll(autoscroll, cx);
21182 }
21183 cx.notify();
21184 }
21185
21186 pub fn replace_blocks(
21187 &mut self,
21188 renderers: HashMap<CustomBlockId, RenderBlock>,
21189 autoscroll: Option<Autoscroll>,
21190 cx: &mut Context<Self>,
21191 ) {
21192 self.display_map
21193 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21194 if let Some(autoscroll) = autoscroll {
21195 self.request_autoscroll(autoscroll, cx);
21196 }
21197 cx.notify();
21198 }
21199
21200 pub fn remove_blocks(
21201 &mut self,
21202 block_ids: HashSet<CustomBlockId>,
21203 autoscroll: Option<Autoscroll>,
21204 cx: &mut Context<Self>,
21205 ) {
21206 self.display_map.update(cx, |display_map, cx| {
21207 display_map.remove_blocks(block_ids, cx)
21208 });
21209 if let Some(autoscroll) = autoscroll {
21210 self.request_autoscroll(autoscroll, cx);
21211 }
21212 cx.notify();
21213 }
21214
21215 pub fn row_for_block(
21216 &self,
21217 block_id: CustomBlockId,
21218 cx: &mut Context<Self>,
21219 ) -> Option<DisplayRow> {
21220 self.display_map
21221 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21222 }
21223
21224 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21225 self.focused_block = Some(focused_block);
21226 }
21227
21228 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21229 self.focused_block.take()
21230 }
21231
21232 pub fn insert_creases(
21233 &mut self,
21234 creases: impl IntoIterator<Item = Crease<Anchor>>,
21235 cx: &mut Context<Self>,
21236 ) -> Vec<CreaseId> {
21237 self.display_map
21238 .update(cx, |map, cx| map.insert_creases(creases, cx))
21239 }
21240
21241 pub fn remove_creases(
21242 &mut self,
21243 ids: impl IntoIterator<Item = CreaseId>,
21244 cx: &mut Context<Self>,
21245 ) -> Vec<(CreaseId, Range<Anchor>)> {
21246 self.display_map
21247 .update(cx, |map, cx| map.remove_creases(ids, cx))
21248 }
21249
21250 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21251 self.display_map
21252 .update(cx, |map, cx| map.snapshot(cx))
21253 .longest_row()
21254 }
21255
21256 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21257 self.display_map
21258 .update(cx, |map, cx| map.snapshot(cx))
21259 .max_point()
21260 }
21261
21262 pub fn text(&self, cx: &App) -> String {
21263 self.buffer.read(cx).read(cx).text()
21264 }
21265
21266 pub fn is_empty(&self, cx: &App) -> bool {
21267 self.buffer.read(cx).read(cx).is_empty()
21268 }
21269
21270 pub fn text_option(&self, cx: &App) -> Option<String> {
21271 let text = self.text(cx);
21272 let text = text.trim();
21273
21274 if text.is_empty() {
21275 return None;
21276 }
21277
21278 Some(text.to_string())
21279 }
21280
21281 pub fn set_text(
21282 &mut self,
21283 text: impl Into<Arc<str>>,
21284 window: &mut Window,
21285 cx: &mut Context<Self>,
21286 ) {
21287 self.transact(window, cx, |this, _, cx| {
21288 this.buffer
21289 .read(cx)
21290 .as_singleton()
21291 .expect("you can only call set_text on editors for singleton buffers")
21292 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21293 });
21294 }
21295
21296 pub fn display_text(&self, cx: &mut App) -> String {
21297 self.display_map
21298 .update(cx, |map, cx| map.snapshot(cx))
21299 .text()
21300 }
21301
21302 fn create_minimap(
21303 &self,
21304 minimap_settings: MinimapSettings,
21305 window: &mut Window,
21306 cx: &mut Context<Self>,
21307 ) -> Option<Entity<Self>> {
21308 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21309 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21310 }
21311
21312 fn initialize_new_minimap(
21313 &self,
21314 minimap_settings: MinimapSettings,
21315 window: &mut Window,
21316 cx: &mut Context<Self>,
21317 ) -> Entity<Self> {
21318 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21319 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21320
21321 let mut minimap = Editor::new_internal(
21322 EditorMode::Minimap {
21323 parent: cx.weak_entity(),
21324 },
21325 self.buffer.clone(),
21326 None,
21327 Some(self.display_map.clone()),
21328 window,
21329 cx,
21330 );
21331 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21332 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21333 minimap.scroll_manager.clone_state(
21334 &self.scroll_manager,
21335 &my_snapshot,
21336 &minimap_snapshot,
21337 cx,
21338 );
21339 minimap.set_text_style_refinement(TextStyleRefinement {
21340 font_size: Some(MINIMAP_FONT_SIZE),
21341 font_weight: Some(MINIMAP_FONT_WEIGHT),
21342 font_family: Some(MINIMAP_FONT_FAMILY),
21343 ..Default::default()
21344 });
21345 minimap.update_minimap_configuration(minimap_settings, cx);
21346 cx.new(|_| minimap)
21347 }
21348
21349 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21350 let current_line_highlight = minimap_settings
21351 .current_line_highlight
21352 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21353 self.set_current_line_highlight(Some(current_line_highlight));
21354 }
21355
21356 pub fn minimap(&self) -> Option<&Entity<Self>> {
21357 self.minimap
21358 .as_ref()
21359 .filter(|_| self.minimap_visibility.visible())
21360 }
21361
21362 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21363 let mut wrap_guides = smallvec![];
21364
21365 if self.show_wrap_guides == Some(false) {
21366 return wrap_guides;
21367 }
21368
21369 let settings = self.buffer.read(cx).language_settings(cx);
21370 if settings.show_wrap_guides {
21371 match self.soft_wrap_mode(cx) {
21372 SoftWrap::Column(soft_wrap) => {
21373 wrap_guides.push((soft_wrap as usize, true));
21374 }
21375 SoftWrap::Bounded(soft_wrap) => {
21376 wrap_guides.push((soft_wrap as usize, true));
21377 }
21378 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21379 }
21380 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21381 }
21382
21383 wrap_guides
21384 }
21385
21386 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21387 let settings = self.buffer.read(cx).language_settings(cx);
21388 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21389 match mode {
21390 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21391 SoftWrap::None
21392 }
21393 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21394 language_settings::SoftWrap::PreferredLineLength => {
21395 SoftWrap::Column(settings.preferred_line_length)
21396 }
21397 language_settings::SoftWrap::Bounded => {
21398 SoftWrap::Bounded(settings.preferred_line_length)
21399 }
21400 }
21401 }
21402
21403 pub fn set_soft_wrap_mode(
21404 &mut self,
21405 mode: language_settings::SoftWrap,
21406 cx: &mut Context<Self>,
21407 ) {
21408 self.soft_wrap_mode_override = Some(mode);
21409 cx.notify();
21410 }
21411
21412 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21413 self.hard_wrap = hard_wrap;
21414 cx.notify();
21415 }
21416
21417 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21418 self.text_style_refinement = Some(style);
21419 }
21420
21421 /// called by the Element so we know what style we were most recently rendered with.
21422 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21423 // We intentionally do not inform the display map about the minimap style
21424 // so that wrapping is not recalculated and stays consistent for the editor
21425 // and its linked minimap.
21426 if !self.mode.is_minimap() {
21427 let font = style.text.font();
21428 let font_size = style.text.font_size.to_pixels(window.rem_size());
21429 let display_map = self
21430 .placeholder_display_map
21431 .as_ref()
21432 .filter(|_| self.is_empty(cx))
21433 .unwrap_or(&self.display_map);
21434
21435 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21436 }
21437 self.style = Some(style);
21438 }
21439
21440 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21441 if self.style.is_none() {
21442 self.style = Some(self.create_style(cx));
21443 }
21444 self.style.as_ref().unwrap()
21445 }
21446
21447 // Called by the element. This method is not designed to be called outside of the editor
21448 // element's layout code because it does not notify when rewrapping is computed synchronously.
21449 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21450 if self.is_empty(cx) {
21451 self.placeholder_display_map
21452 .as_ref()
21453 .map_or(false, |display_map| {
21454 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21455 })
21456 } else {
21457 self.display_map
21458 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21459 }
21460 }
21461
21462 pub fn set_soft_wrap(&mut self) {
21463 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21464 }
21465
21466 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21467 if self.soft_wrap_mode_override.is_some() {
21468 self.soft_wrap_mode_override.take();
21469 } else {
21470 let soft_wrap = match self.soft_wrap_mode(cx) {
21471 SoftWrap::GitDiff => return,
21472 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21473 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21474 language_settings::SoftWrap::None
21475 }
21476 };
21477 self.soft_wrap_mode_override = Some(soft_wrap);
21478 }
21479 cx.notify();
21480 }
21481
21482 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21483 let Some(workspace) = self.workspace() else {
21484 return;
21485 };
21486 let fs = workspace.read(cx).app_state().fs.clone();
21487 let current_show = TabBarSettings::get_global(cx).show;
21488 update_settings_file(fs, cx, move |setting, _| {
21489 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21490 });
21491 }
21492
21493 pub fn toggle_indent_guides(
21494 &mut self,
21495 _: &ToggleIndentGuides,
21496 _: &mut Window,
21497 cx: &mut Context<Self>,
21498 ) {
21499 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21500 self.buffer
21501 .read(cx)
21502 .language_settings(cx)
21503 .indent_guides
21504 .enabled
21505 });
21506 self.show_indent_guides = Some(!currently_enabled);
21507 cx.notify();
21508 }
21509
21510 fn should_show_indent_guides(&self) -> Option<bool> {
21511 self.show_indent_guides
21512 }
21513
21514 pub fn disable_indent_guides_for_buffer(
21515 &mut self,
21516 buffer_id: BufferId,
21517 cx: &mut Context<Self>,
21518 ) {
21519 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21520 cx.notify();
21521 }
21522
21523 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21524 self.buffers_with_disabled_indent_guides
21525 .contains(&buffer_id)
21526 }
21527
21528 pub fn toggle_line_numbers(
21529 &mut self,
21530 _: &ToggleLineNumbers,
21531 _: &mut Window,
21532 cx: &mut Context<Self>,
21533 ) {
21534 let mut editor_settings = EditorSettings::get_global(cx).clone();
21535 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21536 EditorSettings::override_global(editor_settings, cx);
21537 }
21538
21539 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21540 if let Some(show_line_numbers) = self.show_line_numbers {
21541 return show_line_numbers;
21542 }
21543 EditorSettings::get_global(cx).gutter.line_numbers
21544 }
21545
21546 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21547 match (
21548 self.use_relative_line_numbers,
21549 EditorSettings::get_global(cx).relative_line_numbers,
21550 ) {
21551 (None, setting) => setting,
21552 (Some(false), _) => RelativeLineNumbers::Disabled,
21553 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21554 (Some(true), _) => RelativeLineNumbers::Enabled,
21555 }
21556 }
21557
21558 pub fn toggle_relative_line_numbers(
21559 &mut self,
21560 _: &ToggleRelativeLineNumbers,
21561 _: &mut Window,
21562 cx: &mut Context<Self>,
21563 ) {
21564 let is_relative = self.relative_line_numbers(cx);
21565 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21566 }
21567
21568 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21569 self.use_relative_line_numbers = is_relative;
21570 cx.notify();
21571 }
21572
21573 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21574 self.show_gutter = show_gutter;
21575 cx.notify();
21576 }
21577
21578 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21579 self.show_scrollbars = ScrollbarAxes {
21580 horizontal: show,
21581 vertical: show,
21582 };
21583 cx.notify();
21584 }
21585
21586 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21587 self.show_scrollbars.vertical = show;
21588 cx.notify();
21589 }
21590
21591 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21592 self.show_scrollbars.horizontal = show;
21593 cx.notify();
21594 }
21595
21596 pub fn set_minimap_visibility(
21597 &mut self,
21598 minimap_visibility: MinimapVisibility,
21599 window: &mut Window,
21600 cx: &mut Context<Self>,
21601 ) {
21602 if self.minimap_visibility != minimap_visibility {
21603 if minimap_visibility.visible() && self.minimap.is_none() {
21604 let minimap_settings = EditorSettings::get_global(cx).minimap;
21605 self.minimap =
21606 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21607 }
21608 self.minimap_visibility = minimap_visibility;
21609 cx.notify();
21610 }
21611 }
21612
21613 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21614 self.set_show_scrollbars(false, cx);
21615 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21616 }
21617
21618 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21619 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21620 }
21621
21622 /// Normally the text in full mode and auto height editors is padded on the
21623 /// left side by roughly half a character width for improved hit testing.
21624 ///
21625 /// Use this method to disable this for cases where this is not wanted (e.g.
21626 /// if you want to align the editor text with some other text above or below)
21627 /// or if you want to add this padding to single-line editors.
21628 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21629 self.offset_content = offset_content;
21630 cx.notify();
21631 }
21632
21633 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21634 self.show_line_numbers = Some(show_line_numbers);
21635 cx.notify();
21636 }
21637
21638 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21639 self.disable_expand_excerpt_buttons = true;
21640 cx.notify();
21641 }
21642
21643 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21644 self.number_deleted_lines = number;
21645 cx.notify();
21646 }
21647
21648 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21649 self.delegate_expand_excerpts = delegate;
21650 }
21651
21652 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21653 self.delegate_stage_and_restore = delegate;
21654 }
21655
21656 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21657 self.delegate_open_excerpts = delegate;
21658 }
21659
21660 pub fn set_on_local_selections_changed(
21661 &mut self,
21662 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21663 ) {
21664 self.on_local_selections_changed = callback;
21665 }
21666
21667 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21668 self.suppress_selection_callback = suppress;
21669 }
21670
21671 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21672 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21673 cx.notify();
21674 }
21675
21676 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21677 self.show_code_actions = Some(show_code_actions);
21678 cx.notify();
21679 }
21680
21681 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21682 self.show_runnables = Some(show_runnables);
21683 cx.notify();
21684 }
21685
21686 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21687 self.show_breakpoints = Some(show_breakpoints);
21688 cx.notify();
21689 }
21690
21691 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21692 self.show_diff_review_button = show;
21693 cx.notify();
21694 }
21695
21696 pub fn show_diff_review_button(&self) -> bool {
21697 self.show_diff_review_button
21698 }
21699
21700 pub fn render_diff_review_button(
21701 &self,
21702 display_row: DisplayRow,
21703 width: Pixels,
21704 cx: &mut Context<Self>,
21705 ) -> impl IntoElement {
21706 let text_color = cx.theme().colors().text;
21707 let icon_color = cx.theme().colors().icon_accent;
21708
21709 h_flex()
21710 .id("diff_review_button")
21711 .cursor_pointer()
21712 .w(width - px(1.))
21713 .h(relative(0.9))
21714 .justify_center()
21715 .rounded_sm()
21716 .border_1()
21717 .border_color(text_color.opacity(0.1))
21718 .bg(text_color.opacity(0.15))
21719 .hover(|s| {
21720 s.bg(icon_color.opacity(0.4))
21721 .border_color(icon_color.opacity(0.5))
21722 })
21723 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21724 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21725 .on_mouse_down(
21726 gpui::MouseButton::Left,
21727 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21728 editor.start_diff_review_drag(display_row, window, cx);
21729 }),
21730 )
21731 }
21732
21733 pub fn start_diff_review_drag(
21734 &mut self,
21735 display_row: DisplayRow,
21736 window: &mut Window,
21737 cx: &mut Context<Self>,
21738 ) {
21739 let snapshot = self.snapshot(window, cx);
21740 let point = snapshot
21741 .display_snapshot
21742 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21743 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21744 self.diff_review_drag_state = Some(DiffReviewDragState {
21745 start_anchor: anchor,
21746 current_anchor: anchor,
21747 });
21748 cx.notify();
21749 }
21750
21751 pub fn update_diff_review_drag(
21752 &mut self,
21753 display_row: DisplayRow,
21754 window: &mut Window,
21755 cx: &mut Context<Self>,
21756 ) {
21757 if self.diff_review_drag_state.is_none() {
21758 return;
21759 }
21760 let snapshot = self.snapshot(window, cx);
21761 let point = snapshot
21762 .display_snapshot
21763 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21764 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21765 if let Some(drag_state) = &mut self.diff_review_drag_state {
21766 drag_state.current_anchor = anchor;
21767 cx.notify();
21768 }
21769 }
21770
21771 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21772 if let Some(drag_state) = self.diff_review_drag_state.take() {
21773 let snapshot = self.snapshot(window, cx);
21774 let range = drag_state.row_range(&snapshot.display_snapshot);
21775 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21776 }
21777 cx.notify();
21778 }
21779
21780 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21781 self.diff_review_drag_state = None;
21782 cx.notify();
21783 }
21784
21785 /// Calculates the appropriate block height for the diff review overlay.
21786 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21787 /// and 2 lines per comment when expanded.
21788 fn calculate_overlay_height(
21789 &self,
21790 hunk_key: &DiffHunkKey,
21791 comments_expanded: bool,
21792 snapshot: &MultiBufferSnapshot,
21793 ) -> u32 {
21794 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21795 let base_height: u32 = 2; // Input row with avatar and buttons
21796
21797 if comment_count == 0 {
21798 base_height
21799 } else if comments_expanded {
21800 // Header (1 line) + 2 lines per comment
21801 base_height + 1 + (comment_count as u32 * 2)
21802 } else {
21803 // Just header when collapsed
21804 base_height + 1
21805 }
21806 }
21807
21808 pub fn show_diff_review_overlay(
21809 &mut self,
21810 display_range: Range<DisplayRow>,
21811 window: &mut Window,
21812 cx: &mut Context<Self>,
21813 ) {
21814 let Range { start, end } = display_range.sorted();
21815
21816 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21817 let editor_snapshot = self.snapshot(window, cx);
21818
21819 // Convert display rows to multibuffer points
21820 let start_point = editor_snapshot
21821 .display_snapshot
21822 .display_point_to_point(start.as_display_point(), Bias::Left);
21823 let end_point = editor_snapshot
21824 .display_snapshot
21825 .display_point_to_point(end.as_display_point(), Bias::Left);
21826 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21827
21828 // Create anchor range for the selected lines (start of first line to end of last line)
21829 let line_end = Point::new(
21830 end_point.row,
21831 buffer_snapshot.line_len(end_multi_buffer_row),
21832 );
21833 let anchor_range =
21834 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21835
21836 // Compute the hunk key for this display row
21837 let file_path = buffer_snapshot
21838 .file_at(start_point)
21839 .map(|file: &Arc<dyn language::File>| file.path().clone())
21840 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21841 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21842 let new_hunk_key = DiffHunkKey {
21843 file_path,
21844 hunk_start_anchor,
21845 };
21846
21847 // Check if we already have an overlay for this hunk
21848 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21849 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21850 }) {
21851 // Just focus the existing overlay's prompt editor
21852 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21853 window.focus(&focus_handle, cx);
21854 return;
21855 }
21856
21857 // Dismiss overlays that have no comments for their hunks
21858 self.dismiss_overlays_without_comments(cx);
21859
21860 // Get the current user's avatar URI from the project's user_store
21861 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21862 let user_store = project.read(cx).user_store();
21863 user_store
21864 .read(cx)
21865 .current_user()
21866 .map(|user| user.avatar_uri.clone())
21867 });
21868
21869 // Create anchor at the end of the last row so the block appears immediately below it
21870 // Use multibuffer coordinates for anchor creation
21871 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21872 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21873
21874 // Use the hunk key we already computed
21875 let hunk_key = new_hunk_key;
21876
21877 // Create the prompt editor for the review input
21878 let prompt_editor = cx.new(|cx| {
21879 let mut editor = Editor::single_line(window, cx);
21880 editor.set_placeholder_text("Add a review comment...", window, cx);
21881 editor
21882 });
21883
21884 // Register the Newline action on the prompt editor to submit the review
21885 let parent_editor = cx.entity().downgrade();
21886 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21887 prompt_editor.register_action({
21888 let parent_editor = parent_editor.clone();
21889 move |_: &crate::actions::Newline, window, cx| {
21890 if let Some(editor) = parent_editor.upgrade() {
21891 editor.update(cx, |editor, cx| {
21892 editor.submit_diff_review_comment(window, cx);
21893 });
21894 }
21895 }
21896 })
21897 });
21898
21899 // Calculate initial height based on existing comments for this hunk
21900 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21901
21902 // Create the overlay block
21903 let prompt_editor_for_render = prompt_editor.clone();
21904 let hunk_key_for_render = hunk_key.clone();
21905 let editor_handle = cx.entity().downgrade();
21906 let block = BlockProperties {
21907 style: BlockStyle::Sticky,
21908 placement: BlockPlacement::Below(anchor),
21909 height: Some(initial_height),
21910 render: Arc::new(move |cx| {
21911 Self::render_diff_review_overlay(
21912 &prompt_editor_for_render,
21913 &hunk_key_for_render,
21914 &editor_handle,
21915 cx,
21916 )
21917 }),
21918 priority: 0,
21919 };
21920
21921 let block_ids = self.insert_blocks([block], None, cx);
21922 let Some(block_id) = block_ids.into_iter().next() else {
21923 log::error!("Failed to insert diff review overlay block");
21924 return;
21925 };
21926
21927 self.diff_review_overlays.push(DiffReviewOverlay {
21928 anchor_range,
21929 block_id,
21930 prompt_editor: prompt_editor.clone(),
21931 hunk_key,
21932 comments_expanded: true,
21933 inline_edit_editors: HashMap::default(),
21934 inline_edit_subscriptions: HashMap::default(),
21935 user_avatar_uri,
21936 _subscription: subscription,
21937 });
21938
21939 // Focus the prompt editor
21940 let focus_handle = prompt_editor.focus_handle(cx);
21941 window.focus(&focus_handle, cx);
21942
21943 cx.notify();
21944 }
21945
21946 /// Dismisses all diff review overlays.
21947 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21948 if self.diff_review_overlays.is_empty() {
21949 return;
21950 }
21951 let block_ids: HashSet<_> = self
21952 .diff_review_overlays
21953 .drain(..)
21954 .map(|overlay| overlay.block_id)
21955 .collect();
21956 self.remove_blocks(block_ids, None, cx);
21957 cx.notify();
21958 }
21959
21960 /// Dismisses overlays that have no comments stored for their hunks.
21961 /// Keeps overlays that have at least one comment.
21962 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21963 let snapshot = self.buffer.read(cx).snapshot(cx);
21964
21965 // First, compute which overlays have comments (to avoid borrow issues with retain)
21966 let overlays_with_comments: Vec<bool> = self
21967 .diff_review_overlays
21968 .iter()
21969 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21970 .collect();
21971
21972 // Now collect block IDs to remove and retain overlays
21973 let mut block_ids_to_remove = HashSet::default();
21974 let mut index = 0;
21975 self.diff_review_overlays.retain(|overlay| {
21976 let has_comments = overlays_with_comments[index];
21977 index += 1;
21978 if !has_comments {
21979 block_ids_to_remove.insert(overlay.block_id);
21980 }
21981 has_comments
21982 });
21983
21984 if !block_ids_to_remove.is_empty() {
21985 self.remove_blocks(block_ids_to_remove, None, cx);
21986 cx.notify();
21987 }
21988 }
21989
21990 /// Refreshes the diff review overlay block to update its height and render function.
21991 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21992 fn refresh_diff_review_overlay_height(
21993 &mut self,
21994 hunk_key: &DiffHunkKey,
21995 _window: &mut Window,
21996 cx: &mut Context<Self>,
21997 ) {
21998 // Extract all needed data from overlay first to avoid borrow conflicts
21999 let snapshot = self.buffer.read(cx).snapshot(cx);
22000 let (comments_expanded, block_id, prompt_editor) = {
22001 let Some(overlay) = self
22002 .diff_review_overlays
22003 .iter()
22004 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22005 else {
22006 return;
22007 };
22008
22009 (
22010 overlay.comments_expanded,
22011 overlay.block_id,
22012 overlay.prompt_editor.clone(),
22013 )
22014 };
22015
22016 // Calculate new height
22017 let snapshot = self.buffer.read(cx).snapshot(cx);
22018 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
22019
22020 // Update the block height using resize_blocks (avoids flicker)
22021 let mut heights = HashMap::default();
22022 heights.insert(block_id, new_height);
22023 self.resize_blocks(heights, None, cx);
22024
22025 // Update the render function using replace_blocks (avoids flicker)
22026 let hunk_key_for_render = hunk_key.clone();
22027 let editor_handle = cx.entity().downgrade();
22028 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
22029 Arc::new(move |cx| {
22030 Self::render_diff_review_overlay(
22031 &prompt_editor,
22032 &hunk_key_for_render,
22033 &editor_handle,
22034 cx,
22035 )
22036 });
22037
22038 let mut renderers = HashMap::default();
22039 renderers.insert(block_id, render);
22040 self.replace_blocks(renderers, None, cx);
22041 }
22042
22043 /// Action handler for SubmitDiffReviewComment.
22044 pub fn submit_diff_review_comment_action(
22045 &mut self,
22046 _: &SubmitDiffReviewComment,
22047 window: &mut Window,
22048 cx: &mut Context<Self>,
22049 ) {
22050 self.submit_diff_review_comment(window, cx);
22051 }
22052
22053 /// Stores the diff review comment locally.
22054 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
22055 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22056 // Find the overlay that currently has focus
22057 let overlay_index = self
22058 .diff_review_overlays
22059 .iter()
22060 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
22061 let Some(overlay_index) = overlay_index else {
22062 return;
22063 };
22064 let overlay = &self.diff_review_overlays[overlay_index];
22065
22066 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
22067 if comment_text.is_empty() {
22068 return;
22069 }
22070
22071 let anchor_range = overlay.anchor_range.clone();
22072 let hunk_key = overlay.hunk_key.clone();
22073
22074 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
22075
22076 // Clear the prompt editor but keep the overlay open
22077 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
22078 overlay.prompt_editor.update(cx, |editor, cx| {
22079 editor.clear(window, cx);
22080 });
22081 }
22082
22083 // Refresh the overlay to update the block height for the new comment
22084 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22085
22086 cx.notify();
22087 }
22088
22089 /// Returns the prompt editor for the diff review overlay, if one is active.
22090 /// This is primarily used for testing.
22091 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
22092 self.diff_review_overlays
22093 .first()
22094 .map(|overlay| &overlay.prompt_editor)
22095 }
22096
22097 /// Returns the line range for the first diff review overlay, if one is active.
22098 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
22099 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
22100 let overlay = self.diff_review_overlays.first()?;
22101 let snapshot = self.buffer.read(cx).snapshot(cx);
22102 let start_point = overlay.anchor_range.start.to_point(&snapshot);
22103 let end_point = overlay.anchor_range.end.to_point(&snapshot);
22104 let start_row = snapshot
22105 .point_to_buffer_point(start_point)
22106 .map(|(_, p, _)| p.row)
22107 .unwrap_or(start_point.row);
22108 let end_row = snapshot
22109 .point_to_buffer_point(end_point)
22110 .map(|(_, p, _)| p.row)
22111 .unwrap_or(end_point.row);
22112 Some((start_row, end_row))
22113 }
22114
22115 /// Sets whether the comments section is expanded in the diff review overlay.
22116 /// This is primarily used for testing.
22117 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
22118 for overlay in &mut self.diff_review_overlays {
22119 overlay.comments_expanded = expanded;
22120 }
22121 cx.notify();
22122 }
22123
22124 /// Compares two DiffHunkKeys for equality by resolving their anchors.
22125 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
22126 a.file_path == b.file_path
22127 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
22128 }
22129
22130 /// Returns comments for a specific hunk, ordered by creation time.
22131 pub fn comments_for_hunk<'a>(
22132 &'a self,
22133 key: &DiffHunkKey,
22134 snapshot: &MultiBufferSnapshot,
22135 ) -> &'a [StoredReviewComment] {
22136 let key_point = key.hunk_start_anchor.to_point(snapshot);
22137 self.stored_review_comments
22138 .iter()
22139 .find(|(k, _)| {
22140 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22141 })
22142 .map(|(_, comments)| comments.as_slice())
22143 .unwrap_or(&[])
22144 }
22145
22146 /// Returns the total count of stored review comments across all hunks.
22147 pub fn total_review_comment_count(&self) -> usize {
22148 self.stored_review_comments
22149 .iter()
22150 .map(|(_, v)| v.len())
22151 .sum()
22152 }
22153
22154 /// Returns the count of comments for a specific hunk.
22155 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
22156 let key_point = key.hunk_start_anchor.to_point(snapshot);
22157 self.stored_review_comments
22158 .iter()
22159 .find(|(k, _)| {
22160 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22161 })
22162 .map(|(_, v)| v.len())
22163 .unwrap_or(0)
22164 }
22165
22166 /// Adds a new review comment to a specific hunk.
22167 pub fn add_review_comment(
22168 &mut self,
22169 hunk_key: DiffHunkKey,
22170 comment: String,
22171 anchor_range: Range<Anchor>,
22172 cx: &mut Context<Self>,
22173 ) -> usize {
22174 let id = self.next_review_comment_id;
22175 self.next_review_comment_id += 1;
22176
22177 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22178
22179 let snapshot = self.buffer.read(cx).snapshot(cx);
22180 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22181
22182 // Find existing entry for this hunk or add a new one
22183 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22184 k.file_path == hunk_key.file_path
22185 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22186 }) {
22187 comments.push(stored_comment);
22188 } else {
22189 self.stored_review_comments
22190 .push((hunk_key, vec![stored_comment]));
22191 }
22192
22193 cx.emit(EditorEvent::ReviewCommentsChanged {
22194 total_count: self.total_review_comment_count(),
22195 });
22196 cx.notify();
22197 id
22198 }
22199
22200 /// Removes a review comment by ID from any hunk.
22201 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22202 for (_, comments) in self.stored_review_comments.iter_mut() {
22203 if let Some(index) = comments.iter().position(|c| c.id == id) {
22204 comments.remove(index);
22205 cx.emit(EditorEvent::ReviewCommentsChanged {
22206 total_count: self.total_review_comment_count(),
22207 });
22208 cx.notify();
22209 return true;
22210 }
22211 }
22212 false
22213 }
22214
22215 /// Updates a review comment's text by ID.
22216 pub fn update_review_comment(
22217 &mut self,
22218 id: usize,
22219 new_comment: String,
22220 cx: &mut Context<Self>,
22221 ) -> bool {
22222 for (_, comments) in self.stored_review_comments.iter_mut() {
22223 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22224 comment.comment = new_comment;
22225 comment.is_editing = false;
22226 cx.emit(EditorEvent::ReviewCommentsChanged {
22227 total_count: self.total_review_comment_count(),
22228 });
22229 cx.notify();
22230 return true;
22231 }
22232 }
22233 false
22234 }
22235
22236 /// Sets a comment's editing state.
22237 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22238 for (_, comments) in self.stored_review_comments.iter_mut() {
22239 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22240 comment.is_editing = is_editing;
22241 cx.notify();
22242 return;
22243 }
22244 }
22245 }
22246
22247 /// Takes all stored comments from all hunks, clearing the storage.
22248 /// Returns a Vec of (hunk_key, comments) pairs.
22249 pub fn take_all_review_comments(
22250 &mut self,
22251 cx: &mut Context<Self>,
22252 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22253 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22254 self.dismiss_all_diff_review_overlays(cx);
22255 let comments = std::mem::take(&mut self.stored_review_comments);
22256 // Reset the ID counter since all comments have been taken
22257 self.next_review_comment_id = 0;
22258 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22259 cx.notify();
22260 comments
22261 }
22262
22263 /// Removes review comments whose anchors are no longer valid or whose
22264 /// associated diff hunks no longer exist.
22265 ///
22266 /// This should be called when the buffer changes to prevent orphaned comments
22267 /// from accumulating.
22268 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22269 let snapshot = self.buffer.read(cx).snapshot(cx);
22270 let original_count = self.total_review_comment_count();
22271
22272 // Remove comments with invalid hunk anchors
22273 self.stored_review_comments
22274 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22275
22276 // Also clean up individual comments with invalid anchor ranges
22277 for (_, comments) in &mut self.stored_review_comments {
22278 comments.retain(|comment| {
22279 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22280 });
22281 }
22282
22283 // Remove empty hunk entries
22284 self.stored_review_comments
22285 .retain(|(_, comments)| !comments.is_empty());
22286
22287 let new_count = self.total_review_comment_count();
22288 if new_count != original_count {
22289 cx.emit(EditorEvent::ReviewCommentsChanged {
22290 total_count: new_count,
22291 });
22292 cx.notify();
22293 }
22294 }
22295
22296 /// Toggles the expanded state of the comments section in the overlay.
22297 pub fn toggle_review_comments_expanded(
22298 &mut self,
22299 _: &ToggleReviewCommentsExpanded,
22300 window: &mut Window,
22301 cx: &mut Context<Self>,
22302 ) {
22303 // Find the overlay that currently has focus, or use the first one
22304 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22305 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22306 overlay.comments_expanded = !overlay.comments_expanded;
22307 Some(overlay.hunk_key.clone())
22308 } else {
22309 None
22310 }
22311 });
22312
22313 // If no focused overlay found, toggle the first one
22314 let hunk_key = overlay_info.or_else(|| {
22315 self.diff_review_overlays.first_mut().map(|overlay| {
22316 overlay.comments_expanded = !overlay.comments_expanded;
22317 overlay.hunk_key.clone()
22318 })
22319 });
22320
22321 if let Some(hunk_key) = hunk_key {
22322 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22323 cx.notify();
22324 }
22325 }
22326
22327 /// Handles the EditReviewComment action - sets a comment into editing mode.
22328 pub fn edit_review_comment(
22329 &mut self,
22330 action: &EditReviewComment,
22331 window: &mut Window,
22332 cx: &mut Context<Self>,
22333 ) {
22334 let comment_id = action.id;
22335
22336 // Set the comment to editing mode
22337 self.set_comment_editing(comment_id, true, cx);
22338
22339 // Find the overlay that contains this comment and create an inline editor if needed
22340 // First, find which hunk this comment belongs to
22341 let hunk_key = self
22342 .stored_review_comments
22343 .iter()
22344 .find_map(|(key, comments)| {
22345 if comments.iter().any(|c| c.id == comment_id) {
22346 Some(key.clone())
22347 } else {
22348 None
22349 }
22350 });
22351
22352 let snapshot = self.buffer.read(cx).snapshot(cx);
22353 if let Some(hunk_key) = hunk_key {
22354 if let Some(overlay) = self
22355 .diff_review_overlays
22356 .iter_mut()
22357 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22358 {
22359 if let std::collections::hash_map::Entry::Vacant(entry) =
22360 overlay.inline_edit_editors.entry(comment_id)
22361 {
22362 // Find the comment text
22363 let comment_text = self
22364 .stored_review_comments
22365 .iter()
22366 .flat_map(|(_, comments)| comments)
22367 .find(|c| c.id == comment_id)
22368 .map(|c| c.comment.clone())
22369 .unwrap_or_default();
22370
22371 // Create inline editor
22372 let parent_editor = cx.entity().downgrade();
22373 let inline_editor = cx.new(|cx| {
22374 let mut editor = Editor::single_line(window, cx);
22375 editor.set_text(&*comment_text, window, cx);
22376 // Select all text for easy replacement
22377 editor.select_all(&crate::actions::SelectAll, window, cx);
22378 editor
22379 });
22380
22381 // Register the Newline action to confirm the edit
22382 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22383 inline_editor.register_action({
22384 let parent_editor = parent_editor.clone();
22385 move |_: &crate::actions::Newline, window, cx| {
22386 if let Some(editor) = parent_editor.upgrade() {
22387 editor.update(cx, |editor, cx| {
22388 editor.confirm_edit_review_comment(comment_id, window, cx);
22389 });
22390 }
22391 }
22392 })
22393 });
22394
22395 // Store the subscription to keep the action handler alive
22396 overlay
22397 .inline_edit_subscriptions
22398 .insert(comment_id, subscription);
22399
22400 // Focus the inline editor
22401 let focus_handle = inline_editor.focus_handle(cx);
22402 window.focus(&focus_handle, cx);
22403
22404 entry.insert(inline_editor);
22405 }
22406 }
22407 }
22408
22409 cx.notify();
22410 }
22411
22412 /// Confirms an inline edit of a review comment.
22413 pub fn confirm_edit_review_comment(
22414 &mut self,
22415 comment_id: usize,
22416 _window: &mut Window,
22417 cx: &mut Context<Self>,
22418 ) {
22419 // Get the new text from the inline editor
22420 // Find the overlay containing this comment's inline editor
22421 let snapshot = self.buffer.read(cx).snapshot(cx);
22422 let hunk_key = self
22423 .stored_review_comments
22424 .iter()
22425 .find_map(|(key, comments)| {
22426 if comments.iter().any(|c| c.id == comment_id) {
22427 Some(key.clone())
22428 } else {
22429 None
22430 }
22431 });
22432
22433 let new_text = hunk_key
22434 .as_ref()
22435 .and_then(|hunk_key| {
22436 self.diff_review_overlays
22437 .iter()
22438 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22439 })
22440 .as_ref()
22441 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22442 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22443
22444 if let Some(new_text) = new_text {
22445 if !new_text.is_empty() {
22446 self.update_review_comment(comment_id, new_text, cx);
22447 }
22448 }
22449
22450 // Remove the inline editor and its subscription
22451 if let Some(hunk_key) = hunk_key {
22452 if let Some(overlay) = self
22453 .diff_review_overlays
22454 .iter_mut()
22455 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22456 {
22457 overlay.inline_edit_editors.remove(&comment_id);
22458 overlay.inline_edit_subscriptions.remove(&comment_id);
22459 }
22460 }
22461
22462 // Clear editing state
22463 self.set_comment_editing(comment_id, false, cx);
22464 }
22465
22466 /// Cancels an inline edit of a review comment.
22467 pub fn cancel_edit_review_comment(
22468 &mut self,
22469 comment_id: usize,
22470 _window: &mut Window,
22471 cx: &mut Context<Self>,
22472 ) {
22473 // Find which hunk this comment belongs to
22474 let hunk_key = self
22475 .stored_review_comments
22476 .iter()
22477 .find_map(|(key, comments)| {
22478 if comments.iter().any(|c| c.id == comment_id) {
22479 Some(key.clone())
22480 } else {
22481 None
22482 }
22483 });
22484
22485 // Remove the inline editor and its subscription
22486 if let Some(hunk_key) = hunk_key {
22487 let snapshot = self.buffer.read(cx).snapshot(cx);
22488 if let Some(overlay) = self
22489 .diff_review_overlays
22490 .iter_mut()
22491 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22492 {
22493 overlay.inline_edit_editors.remove(&comment_id);
22494 overlay.inline_edit_subscriptions.remove(&comment_id);
22495 }
22496 }
22497
22498 // Clear editing state
22499 self.set_comment_editing(comment_id, false, cx);
22500 }
22501
22502 /// Action handler for ConfirmEditReviewComment.
22503 pub fn confirm_edit_review_comment_action(
22504 &mut self,
22505 action: &ConfirmEditReviewComment,
22506 window: &mut Window,
22507 cx: &mut Context<Self>,
22508 ) {
22509 self.confirm_edit_review_comment(action.id, window, cx);
22510 }
22511
22512 /// Action handler for CancelEditReviewComment.
22513 pub fn cancel_edit_review_comment_action(
22514 &mut self,
22515 action: &CancelEditReviewComment,
22516 window: &mut Window,
22517 cx: &mut Context<Self>,
22518 ) {
22519 self.cancel_edit_review_comment(action.id, window, cx);
22520 }
22521
22522 /// Handles the DeleteReviewComment action - removes a comment.
22523 pub fn delete_review_comment(
22524 &mut self,
22525 action: &DeleteReviewComment,
22526 window: &mut Window,
22527 cx: &mut Context<Self>,
22528 ) {
22529 // Get the hunk key before removing the comment
22530 // Find the hunk key from the comment itself
22531 let comment_id = action.id;
22532 let hunk_key = self
22533 .stored_review_comments
22534 .iter()
22535 .find_map(|(key, comments)| {
22536 if comments.iter().any(|c| c.id == comment_id) {
22537 Some(key.clone())
22538 } else {
22539 None
22540 }
22541 });
22542
22543 // Also get it from the overlay for refresh purposes
22544 let overlay_hunk_key = self
22545 .diff_review_overlays
22546 .first()
22547 .map(|o| o.hunk_key.clone());
22548
22549 self.remove_review_comment(action.id, cx);
22550
22551 // Refresh the overlay height after removing a comment
22552 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22553 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22554 }
22555 }
22556
22557 fn render_diff_review_overlay(
22558 prompt_editor: &Entity<Editor>,
22559 hunk_key: &DiffHunkKey,
22560 editor_handle: &WeakEntity<Editor>,
22561 cx: &mut BlockContext,
22562 ) -> AnyElement {
22563 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22564 if ranges.is_empty() {
22565 return None;
22566 }
22567 let formatted: Vec<String> = ranges
22568 .iter()
22569 .map(|(start, end)| {
22570 let start_line = start + 1;
22571 let end_line = end + 1;
22572 if start_line == end_line {
22573 format!("Line {start_line}")
22574 } else {
22575 format!("Lines {start_line}-{end_line}")
22576 }
22577 })
22578 .collect();
22579 // Don't show label for single line in single excerpt
22580 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22581 return None;
22582 }
22583 Some(formatted.join(" ⋯ "))
22584 }
22585
22586 let theme = cx.theme();
22587 let colors = theme.colors();
22588
22589 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22590 editor_handle
22591 .upgrade()
22592 .map(|editor| {
22593 let editor = editor.read(cx);
22594 let snapshot = editor.buffer().read(cx).snapshot(cx);
22595 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22596 let (expanded, editors, avatar_uri, line_ranges) = editor
22597 .diff_review_overlays
22598 .iter()
22599 .find(|overlay| {
22600 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22601 })
22602 .map(|o| {
22603 let start_point = o.anchor_range.start.to_point(&snapshot);
22604 let end_point = o.anchor_range.end.to_point(&snapshot);
22605 // Get line ranges per excerpt to detect discontinuities
22606 let buffer_ranges =
22607 snapshot.range_to_buffer_ranges(start_point..end_point);
22608 let ranges: Vec<(u32, u32)> = buffer_ranges
22609 .iter()
22610 .map(|(buffer, range, _)| {
22611 let start = buffer.offset_to_point(range.start.0).row;
22612 let end = buffer.offset_to_point(range.end.0).row;
22613 (start, end)
22614 })
22615 .collect();
22616 (
22617 o.comments_expanded,
22618 o.inline_edit_editors.clone(),
22619 o.user_avatar_uri.clone(),
22620 if ranges.is_empty() {
22621 None
22622 } else {
22623 Some(ranges)
22624 },
22625 )
22626 })
22627 .unwrap_or((true, HashMap::default(), None, None));
22628 (comments, expanded, editors, avatar_uri, line_ranges)
22629 })
22630 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22631
22632 let comment_count = comments.len();
22633 let avatar_size = px(20.);
22634 let action_icon_size = IconSize::XSmall;
22635
22636 v_flex()
22637 .w_full()
22638 .bg(colors.editor_background)
22639 .border_b_1()
22640 .border_color(colors.border)
22641 .px_2()
22642 .pb_2()
22643 .gap_2()
22644 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22645 .when_some(line_ranges, |el, ranges| {
22646 let label = format_line_ranges(&ranges);
22647 if let Some(label) = label {
22648 el.child(
22649 h_flex()
22650 .w_full()
22651 .px_2()
22652 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22653 )
22654 } else {
22655 el
22656 }
22657 })
22658 // Top row: editable input with user's avatar
22659 .child(
22660 h_flex()
22661 .w_full()
22662 .items_center()
22663 .gap_2()
22664 .px_2()
22665 .py_1p5()
22666 .rounded_md()
22667 .bg(colors.surface_background)
22668 .child(
22669 div()
22670 .size(avatar_size)
22671 .flex_shrink_0()
22672 .rounded_full()
22673 .overflow_hidden()
22674 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22675 Avatar::new(avatar_uri.clone())
22676 .size(avatar_size)
22677 .into_any_element()
22678 } else {
22679 Icon::new(IconName::Person)
22680 .size(IconSize::Small)
22681 .color(ui::Color::Muted)
22682 .into_any_element()
22683 }),
22684 )
22685 .child(
22686 div()
22687 .flex_1()
22688 .border_1()
22689 .border_color(colors.border)
22690 .rounded_md()
22691 .bg(colors.editor_background)
22692 .px_2()
22693 .py_1()
22694 .child(prompt_editor.clone()),
22695 )
22696 .child(
22697 h_flex()
22698 .flex_shrink_0()
22699 .gap_1()
22700 .child(
22701 IconButton::new("diff-review-close", IconName::Close)
22702 .icon_color(ui::Color::Muted)
22703 .icon_size(action_icon_size)
22704 .tooltip(Tooltip::text("Close"))
22705 .on_click(|_, window, cx| {
22706 window
22707 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22708 }),
22709 )
22710 .child(
22711 IconButton::new("diff-review-add", IconName::Return)
22712 .icon_color(ui::Color::Muted)
22713 .icon_size(action_icon_size)
22714 .tooltip(Tooltip::text("Add comment"))
22715 .on_click(|_, window, cx| {
22716 window.dispatch_action(
22717 Box::new(crate::actions::SubmitDiffReviewComment),
22718 cx,
22719 );
22720 }),
22721 ),
22722 ),
22723 )
22724 // Expandable comments section (only shown when there are comments)
22725 .when(comment_count > 0, |el| {
22726 el.child(Self::render_comments_section(
22727 comments,
22728 comments_expanded,
22729 inline_editors,
22730 user_avatar_uri,
22731 avatar_size,
22732 action_icon_size,
22733 colors,
22734 ))
22735 })
22736 .into_any_element()
22737 }
22738
22739 fn render_comments_section(
22740 comments: Vec<StoredReviewComment>,
22741 expanded: bool,
22742 inline_editors: HashMap<usize, Entity<Editor>>,
22743 user_avatar_uri: Option<SharedUri>,
22744 avatar_size: Pixels,
22745 action_icon_size: IconSize,
22746 colors: &theme::ThemeColors,
22747 ) -> impl IntoElement {
22748 let comment_count = comments.len();
22749
22750 v_flex()
22751 .w_full()
22752 .gap_1()
22753 // Header with expand/collapse toggle
22754 .child(
22755 h_flex()
22756 .id("review-comments-header")
22757 .w_full()
22758 .items_center()
22759 .gap_1()
22760 .px_2()
22761 .py_1()
22762 .cursor_pointer()
22763 .rounded_md()
22764 .hover(|style| style.bg(colors.ghost_element_hover))
22765 .on_click(|_, window: &mut Window, cx| {
22766 window.dispatch_action(
22767 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22768 cx,
22769 );
22770 })
22771 .child(
22772 Icon::new(if expanded {
22773 IconName::ChevronDown
22774 } else {
22775 IconName::ChevronRight
22776 })
22777 .size(IconSize::Small)
22778 .color(ui::Color::Muted),
22779 )
22780 .child(
22781 Label::new(format!(
22782 "{} Comment{}",
22783 comment_count,
22784 if comment_count == 1 { "" } else { "s" }
22785 ))
22786 .size(LabelSize::Small)
22787 .color(Color::Muted),
22788 ),
22789 )
22790 // Comments list (when expanded)
22791 .when(expanded, |el| {
22792 el.children(comments.into_iter().map(|comment| {
22793 let inline_editor = inline_editors.get(&comment.id).cloned();
22794 Self::render_comment_row(
22795 comment,
22796 inline_editor,
22797 user_avatar_uri.clone(),
22798 avatar_size,
22799 action_icon_size,
22800 colors,
22801 )
22802 }))
22803 })
22804 }
22805
22806 fn render_comment_row(
22807 comment: StoredReviewComment,
22808 inline_editor: Option<Entity<Editor>>,
22809 user_avatar_uri: Option<SharedUri>,
22810 avatar_size: Pixels,
22811 action_icon_size: IconSize,
22812 colors: &theme::ThemeColors,
22813 ) -> impl IntoElement {
22814 let comment_id = comment.id;
22815 let is_editing = inline_editor.is_some();
22816
22817 h_flex()
22818 .w_full()
22819 .items_center()
22820 .gap_2()
22821 .px_2()
22822 .py_1p5()
22823 .rounded_md()
22824 .bg(colors.surface_background)
22825 .child(
22826 div()
22827 .size(avatar_size)
22828 .flex_shrink_0()
22829 .rounded_full()
22830 .overflow_hidden()
22831 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22832 Avatar::new(avatar_uri.clone())
22833 .size(avatar_size)
22834 .into_any_element()
22835 } else {
22836 Icon::new(IconName::Person)
22837 .size(IconSize::Small)
22838 .color(ui::Color::Muted)
22839 .into_any_element()
22840 }),
22841 )
22842 .child(if let Some(editor) = inline_editor {
22843 // Inline edit mode: show an editable text field
22844 div()
22845 .flex_1()
22846 .border_1()
22847 .border_color(colors.border)
22848 .rounded_md()
22849 .bg(colors.editor_background)
22850 .px_2()
22851 .py_1()
22852 .child(editor)
22853 .into_any_element()
22854 } else {
22855 // Display mode: show the comment text
22856 div()
22857 .flex_1()
22858 .text_sm()
22859 .text_color(colors.text)
22860 .child(comment.comment)
22861 .into_any_element()
22862 })
22863 .child(if is_editing {
22864 // Editing mode: show close and confirm buttons
22865 h_flex()
22866 .gap_1()
22867 .child(
22868 IconButton::new(
22869 format!("diff-review-cancel-edit-{comment_id}"),
22870 IconName::Close,
22871 )
22872 .icon_color(ui::Color::Muted)
22873 .icon_size(action_icon_size)
22874 .tooltip(Tooltip::text("Cancel"))
22875 .on_click(move |_, window, cx| {
22876 window.dispatch_action(
22877 Box::new(crate::actions::CancelEditReviewComment {
22878 id: comment_id,
22879 }),
22880 cx,
22881 );
22882 }),
22883 )
22884 .child(
22885 IconButton::new(
22886 format!("diff-review-confirm-edit-{comment_id}"),
22887 IconName::Return,
22888 )
22889 .icon_color(ui::Color::Muted)
22890 .icon_size(action_icon_size)
22891 .tooltip(Tooltip::text("Confirm"))
22892 .on_click(move |_, window, cx| {
22893 window.dispatch_action(
22894 Box::new(crate::actions::ConfirmEditReviewComment {
22895 id: comment_id,
22896 }),
22897 cx,
22898 );
22899 }),
22900 )
22901 .into_any_element()
22902 } else {
22903 // Display mode: no action buttons for now (edit/delete not yet implemented)
22904 gpui::Empty.into_any_element()
22905 })
22906 }
22907
22908 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22909 if self.display_map.read(cx).masked != masked {
22910 self.display_map.update(cx, |map, _| map.masked = masked);
22911 }
22912 cx.notify()
22913 }
22914
22915 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22916 self.show_wrap_guides = Some(show_wrap_guides);
22917 cx.notify();
22918 }
22919
22920 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22921 self.show_indent_guides = Some(show_indent_guides);
22922 cx.notify();
22923 }
22924
22925 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22926 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22927 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22928 && let Some(dir) = file.abs_path(cx).parent()
22929 {
22930 return Some(dir.to_owned());
22931 }
22932 }
22933
22934 None
22935 }
22936
22937 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22938 self.active_excerpt(cx)?
22939 .1
22940 .read(cx)
22941 .file()
22942 .and_then(|f| f.as_local())
22943 }
22944
22945 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22946 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22947 let buffer = buffer.read(cx);
22948 if let Some(project_path) = buffer.project_path(cx) {
22949 let project = self.project()?.read(cx);
22950 project.absolute_path(&project_path, cx)
22951 } else {
22952 buffer
22953 .file()
22954 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22955 }
22956 })
22957 }
22958
22959 pub fn reveal_in_finder(
22960 &mut self,
22961 _: &RevealInFileManager,
22962 _window: &mut Window,
22963 cx: &mut Context<Self>,
22964 ) {
22965 if let Some(path) = self.target_file_abs_path(cx) {
22966 if let Some(project) = self.project() {
22967 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22968 } else {
22969 cx.reveal_path(&path);
22970 }
22971 }
22972 }
22973
22974 pub fn copy_path(
22975 &mut self,
22976 _: &zed_actions::workspace::CopyPath,
22977 _window: &mut Window,
22978 cx: &mut Context<Self>,
22979 ) {
22980 if let Some(path) = self.target_file_abs_path(cx)
22981 && let Some(path) = path.to_str()
22982 {
22983 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22984 } else {
22985 cx.propagate();
22986 }
22987 }
22988
22989 pub fn copy_relative_path(
22990 &mut self,
22991 _: &zed_actions::workspace::CopyRelativePath,
22992 _window: &mut Window,
22993 cx: &mut Context<Self>,
22994 ) {
22995 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22996 let project = self.project()?.read(cx);
22997 let path = buffer.read(cx).file()?.path();
22998 let path = path.display(project.path_style(cx));
22999 Some(path)
23000 }) {
23001 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
23002 } else {
23003 cx.propagate();
23004 }
23005 }
23006
23007 /// Returns the project path for the editor's buffer, if any buffer is
23008 /// opened in the editor.
23009 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
23010 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
23011 buffer.read(cx).project_path(cx)
23012 } else {
23013 None
23014 }
23015 }
23016
23017 // Returns true if the editor handled a go-to-line request
23018 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
23019 maybe!({
23020 let breakpoint_store = self.breakpoint_store.as_ref()?;
23021
23022 let (active_stack_frame, debug_line_pane_id) = {
23023 let store = breakpoint_store.read(cx);
23024 let active_stack_frame = store.active_position().cloned();
23025 let debug_line_pane_id = store.active_debug_line_pane_id();
23026 (active_stack_frame, debug_line_pane_id)
23027 };
23028
23029 let Some(active_stack_frame) = active_stack_frame else {
23030 self.clear_row_highlights::<ActiveDebugLine>();
23031 return None;
23032 };
23033
23034 if let Some(debug_line_pane_id) = debug_line_pane_id {
23035 if let Some(workspace) = self
23036 .workspace
23037 .as_ref()
23038 .and_then(|(workspace, _)| workspace.upgrade())
23039 {
23040 let editor_pane_id = workspace
23041 .read(cx)
23042 .pane_for_item_id(cx.entity_id())
23043 .map(|pane| pane.entity_id());
23044
23045 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
23046 self.clear_row_highlights::<ActiveDebugLine>();
23047 return None;
23048 }
23049 }
23050 }
23051
23052 let position = active_stack_frame.position;
23053 let buffer_id = position.buffer_id?;
23054 let snapshot = self
23055 .project
23056 .as_ref()?
23057 .read(cx)
23058 .buffer_for_id(buffer_id, cx)?
23059 .read(cx)
23060 .snapshot();
23061
23062 let mut handled = false;
23063 for (id, _, ExcerptRange { context, .. }) in
23064 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
23065 {
23066 if context.start.cmp(&position, &snapshot).is_ge()
23067 || context.end.cmp(&position, &snapshot).is_lt()
23068 {
23069 continue;
23070 }
23071 let snapshot = self.buffer.read(cx).snapshot(cx);
23072 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
23073
23074 handled = true;
23075 self.clear_row_highlights::<ActiveDebugLine>();
23076
23077 self.go_to_line::<ActiveDebugLine>(
23078 multibuffer_anchor,
23079 Some(cx.theme().colors().editor_debugger_active_line_background),
23080 window,
23081 cx,
23082 );
23083
23084 cx.notify();
23085 }
23086
23087 handled.then_some(())
23088 })
23089 .is_some()
23090 }
23091
23092 pub fn copy_file_name_without_extension(
23093 &mut self,
23094 _: &CopyFileNameWithoutExtension,
23095 _: &mut Window,
23096 cx: &mut Context<Self>,
23097 ) {
23098 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23099 let file = buffer.read(cx).file()?;
23100 file.path().file_stem()
23101 }) {
23102 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
23103 }
23104 }
23105
23106 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
23107 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23108 let file = buffer.read(cx).file()?;
23109 Some(file.file_name(cx))
23110 }) {
23111 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
23112 }
23113 }
23114
23115 pub fn toggle_git_blame(
23116 &mut self,
23117 _: &::git::Blame,
23118 window: &mut Window,
23119 cx: &mut Context<Self>,
23120 ) {
23121 self.show_git_blame_gutter = !self.show_git_blame_gutter;
23122
23123 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
23124 self.start_git_blame(true, window, cx);
23125 }
23126
23127 cx.notify();
23128 }
23129
23130 pub fn toggle_git_blame_inline(
23131 &mut self,
23132 _: &ToggleGitBlameInline,
23133 window: &mut Window,
23134 cx: &mut Context<Self>,
23135 ) {
23136 self.toggle_git_blame_inline_internal(true, window, cx);
23137 cx.notify();
23138 }
23139
23140 pub fn open_git_blame_commit(
23141 &mut self,
23142 _: &OpenGitBlameCommit,
23143 window: &mut Window,
23144 cx: &mut Context<Self>,
23145 ) {
23146 self.open_git_blame_commit_internal(window, cx);
23147 }
23148
23149 fn open_git_blame_commit_internal(
23150 &mut self,
23151 window: &mut Window,
23152 cx: &mut Context<Self>,
23153 ) -> Option<()> {
23154 let blame = self.blame.as_ref()?;
23155 let snapshot = self.snapshot(window, cx);
23156 let cursor = self
23157 .selections
23158 .newest::<Point>(&snapshot.display_snapshot)
23159 .head();
23160 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
23161 let (_, blame_entry) = blame
23162 .update(cx, |blame, cx| {
23163 blame
23164 .blame_for_rows(
23165 &[RowInfo {
23166 buffer_id: Some(buffer.remote_id()),
23167 buffer_row: Some(point.row),
23168 ..Default::default()
23169 }],
23170 cx,
23171 )
23172 .next()
23173 })
23174 .flatten()?;
23175 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23176 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23177 let workspace = self.workspace()?.downgrade();
23178 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23179 None
23180 }
23181
23182 pub fn git_blame_inline_enabled(&self) -> bool {
23183 self.git_blame_inline_enabled
23184 }
23185
23186 pub fn toggle_selection_menu(
23187 &mut self,
23188 _: &ToggleSelectionMenu,
23189 _: &mut Window,
23190 cx: &mut Context<Self>,
23191 ) {
23192 self.show_selection_menu = self
23193 .show_selection_menu
23194 .map(|show_selections_menu| !show_selections_menu)
23195 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23196
23197 cx.notify();
23198 }
23199
23200 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23201 self.show_selection_menu
23202 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23203 }
23204
23205 fn start_git_blame(
23206 &mut self,
23207 user_triggered: bool,
23208 window: &mut Window,
23209 cx: &mut Context<Self>,
23210 ) {
23211 if let Some(project) = self.project() {
23212 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23213 && buffer.read(cx).file().is_none()
23214 {
23215 return;
23216 }
23217
23218 let focused = self.focus_handle(cx).contains_focused(window, cx);
23219
23220 let project = project.clone();
23221 let blame = cx
23222 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23223 self.blame_subscription =
23224 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23225 self.blame = Some(blame);
23226 }
23227 }
23228
23229 fn toggle_git_blame_inline_internal(
23230 &mut self,
23231 user_triggered: bool,
23232 window: &mut Window,
23233 cx: &mut Context<Self>,
23234 ) {
23235 if self.git_blame_inline_enabled {
23236 self.git_blame_inline_enabled = false;
23237 self.show_git_blame_inline = false;
23238 self.show_git_blame_inline_delay_task.take();
23239 } else {
23240 self.git_blame_inline_enabled = true;
23241 self.start_git_blame_inline(user_triggered, window, cx);
23242 }
23243
23244 cx.notify();
23245 }
23246
23247 fn start_git_blame_inline(
23248 &mut self,
23249 user_triggered: bool,
23250 window: &mut Window,
23251 cx: &mut Context<Self>,
23252 ) {
23253 self.start_git_blame(user_triggered, window, cx);
23254
23255 if ProjectSettings::get_global(cx)
23256 .git
23257 .inline_blame_delay()
23258 .is_some()
23259 {
23260 self.start_inline_blame_timer(window, cx);
23261 } else {
23262 self.show_git_blame_inline = true
23263 }
23264 }
23265
23266 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23267 self.blame.as_ref()
23268 }
23269
23270 pub fn show_git_blame_gutter(&self) -> bool {
23271 self.show_git_blame_gutter
23272 }
23273
23274 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23275 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23276 }
23277
23278 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23279 self.show_git_blame_inline
23280 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23281 && !self.newest_selection_head_on_empty_line(cx)
23282 && self.has_blame_entries(cx)
23283 }
23284
23285 fn has_blame_entries(&self, cx: &App) -> bool {
23286 self.blame()
23287 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23288 }
23289
23290 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23291 let cursor_anchor = self.selections.newest_anchor().head();
23292
23293 let snapshot = self.buffer.read(cx).snapshot(cx);
23294 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23295
23296 snapshot.line_len(buffer_row) == 0
23297 }
23298
23299 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23300 let buffer_and_selection = maybe!({
23301 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23302 let selection_range = selection.range();
23303
23304 let multi_buffer = self.buffer().read(cx);
23305 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23306 let buffer_ranges = multi_buffer_snapshot
23307 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23308
23309 let (buffer, range, _) = if selection.reversed {
23310 buffer_ranges.first()
23311 } else {
23312 buffer_ranges.last()
23313 }?;
23314
23315 let buffer_range = range.to_point(buffer);
23316
23317 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23318 return Some((
23319 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23320 buffer_range.start.row..buffer_range.end.row,
23321 ));
23322 };
23323
23324 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23325 let start =
23326 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23327 let end =
23328 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23329
23330 Some((
23331 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23332 start.row..end.row,
23333 ))
23334 });
23335
23336 let Some((buffer, selection)) = buffer_and_selection else {
23337 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23338 };
23339
23340 let Some(project) = self.project() else {
23341 return Task::ready(Err(anyhow!("editor does not have project")));
23342 };
23343
23344 project.update(cx, |project, cx| {
23345 project.get_permalink_to_line(&buffer, selection, cx)
23346 })
23347 }
23348
23349 pub fn copy_permalink_to_line(
23350 &mut self,
23351 _: &CopyPermalinkToLine,
23352 window: &mut Window,
23353 cx: &mut Context<Self>,
23354 ) {
23355 let permalink_task = self.get_permalink_to_line(cx);
23356 let workspace = self.workspace();
23357
23358 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23359 Ok(permalink) => {
23360 cx.update(|_, cx| {
23361 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23362 })
23363 .ok();
23364 }
23365 Err(err) => {
23366 let message = format!("Failed to copy permalink: {err}");
23367
23368 anyhow::Result::<()>::Err(err).log_err();
23369
23370 if let Some(workspace) = workspace {
23371 workspace
23372 .update_in(cx, |workspace, _, cx| {
23373 struct CopyPermalinkToLine;
23374
23375 workspace.show_toast(
23376 Toast::new(
23377 NotificationId::unique::<CopyPermalinkToLine>(),
23378 message,
23379 ),
23380 cx,
23381 )
23382 })
23383 .ok();
23384 }
23385 }
23386 })
23387 .detach();
23388 }
23389
23390 pub fn copy_file_location(
23391 &mut self,
23392 _: &CopyFileLocation,
23393 _: &mut Window,
23394 cx: &mut Context<Self>,
23395 ) {
23396 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23397
23398 let start_line = selection.start.row + 1;
23399 let end_line = selection.end.row + 1;
23400
23401 let end_line = if selection.end.column == 0 && end_line > start_line {
23402 end_line - 1
23403 } else {
23404 end_line
23405 };
23406
23407 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23408 let project = self.project()?.read(cx);
23409 let file = buffer.read(cx).file()?;
23410 let path = file.path().display(project.path_style(cx));
23411
23412 let location = if start_line == end_line {
23413 format!("{path}:{start_line}")
23414 } else {
23415 format!("{path}:{start_line}-{end_line}")
23416 };
23417 Some(location)
23418 }) {
23419 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23420 }
23421 }
23422
23423 pub fn open_permalink_to_line(
23424 &mut self,
23425 _: &OpenPermalinkToLine,
23426 window: &mut Window,
23427 cx: &mut Context<Self>,
23428 ) {
23429 let permalink_task = self.get_permalink_to_line(cx);
23430 let workspace = self.workspace();
23431
23432 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23433 Ok(permalink) => {
23434 cx.update(|_, cx| {
23435 cx.open_url(permalink.as_ref());
23436 })
23437 .ok();
23438 }
23439 Err(err) => {
23440 let message = format!("Failed to open permalink: {err}");
23441
23442 anyhow::Result::<()>::Err(err).log_err();
23443
23444 if let Some(workspace) = workspace {
23445 workspace.update(cx, |workspace, cx| {
23446 struct OpenPermalinkToLine;
23447
23448 workspace.show_toast(
23449 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23450 cx,
23451 )
23452 });
23453 }
23454 }
23455 })
23456 .detach();
23457 }
23458
23459 pub fn insert_uuid_v4(
23460 &mut self,
23461 _: &InsertUuidV4,
23462 window: &mut Window,
23463 cx: &mut Context<Self>,
23464 ) {
23465 self.insert_uuid(UuidVersion::V4, window, cx);
23466 }
23467
23468 pub fn insert_uuid_v7(
23469 &mut self,
23470 _: &InsertUuidV7,
23471 window: &mut Window,
23472 cx: &mut Context<Self>,
23473 ) {
23474 self.insert_uuid(UuidVersion::V7, window, cx);
23475 }
23476
23477 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23478 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23479 self.transact(window, cx, |this, window, cx| {
23480 let edits = this
23481 .selections
23482 .all::<Point>(&this.display_snapshot(cx))
23483 .into_iter()
23484 .map(|selection| {
23485 let uuid = match version {
23486 UuidVersion::V4 => uuid::Uuid::new_v4(),
23487 UuidVersion::V7 => uuid::Uuid::now_v7(),
23488 };
23489
23490 (selection.range(), uuid.to_string())
23491 });
23492 this.edit(edits, cx);
23493 this.refresh_edit_prediction(true, false, window, cx);
23494 });
23495 }
23496
23497 pub fn open_selections_in_multibuffer(
23498 &mut self,
23499 _: &OpenSelectionsInMultibuffer,
23500 window: &mut Window,
23501 cx: &mut Context<Self>,
23502 ) {
23503 let multibuffer = self.buffer.read(cx);
23504
23505 let Some(buffer) = multibuffer.as_singleton() else {
23506 return;
23507 };
23508
23509 let Some(workspace) = self.workspace() else {
23510 return;
23511 };
23512
23513 let title = multibuffer.title(cx).to_string();
23514
23515 let locations = self
23516 .selections
23517 .all_anchors(&self.display_snapshot(cx))
23518 .iter()
23519 .map(|selection| {
23520 (
23521 buffer.clone(),
23522 (selection.start.text_anchor..selection.end.text_anchor)
23523 .to_point(buffer.read(cx)),
23524 )
23525 })
23526 .into_group_map();
23527
23528 cx.spawn_in(window, async move |_, cx| {
23529 workspace.update_in(cx, |workspace, window, cx| {
23530 Self::open_locations_in_multibuffer(
23531 workspace,
23532 locations,
23533 format!("Selections for '{title}'"),
23534 false,
23535 false,
23536 MultibufferSelectionMode::All,
23537 window,
23538 cx,
23539 );
23540 })
23541 })
23542 .detach();
23543 }
23544
23545 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23546 /// last highlight added will be used.
23547 ///
23548 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23549 pub fn highlight_rows<T: 'static>(
23550 &mut self,
23551 range: Range<Anchor>,
23552 color: Hsla,
23553 options: RowHighlightOptions,
23554 cx: &mut Context<Self>,
23555 ) {
23556 let snapshot = self.buffer().read(cx).snapshot(cx);
23557 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23558 let ix = row_highlights.binary_search_by(|highlight| {
23559 Ordering::Equal
23560 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23561 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23562 });
23563
23564 if let Err(mut ix) = ix {
23565 let index = post_inc(&mut self.highlight_order);
23566
23567 // If this range intersects with the preceding highlight, then merge it with
23568 // the preceding highlight. Otherwise insert a new highlight.
23569 let mut merged = false;
23570 if ix > 0 {
23571 let prev_highlight = &mut row_highlights[ix - 1];
23572 if prev_highlight
23573 .range
23574 .end
23575 .cmp(&range.start, &snapshot)
23576 .is_ge()
23577 {
23578 ix -= 1;
23579 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23580 prev_highlight.range.end = range.end;
23581 }
23582 merged = true;
23583 prev_highlight.index = index;
23584 prev_highlight.color = color;
23585 prev_highlight.options = options;
23586 }
23587 }
23588
23589 if !merged {
23590 row_highlights.insert(
23591 ix,
23592 RowHighlight {
23593 range,
23594 index,
23595 color,
23596 options,
23597 type_id: TypeId::of::<T>(),
23598 },
23599 );
23600 }
23601
23602 // If any of the following highlights intersect with this one, merge them.
23603 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23604 let highlight = &row_highlights[ix];
23605 if next_highlight
23606 .range
23607 .start
23608 .cmp(&highlight.range.end, &snapshot)
23609 .is_le()
23610 {
23611 if next_highlight
23612 .range
23613 .end
23614 .cmp(&highlight.range.end, &snapshot)
23615 .is_gt()
23616 {
23617 row_highlights[ix].range.end = next_highlight.range.end;
23618 }
23619 row_highlights.remove(ix + 1);
23620 } else {
23621 break;
23622 }
23623 }
23624 }
23625 }
23626
23627 /// Remove any highlighted row ranges of the given type that intersect the
23628 /// given ranges.
23629 pub fn remove_highlighted_rows<T: 'static>(
23630 &mut self,
23631 ranges_to_remove: Vec<Range<Anchor>>,
23632 cx: &mut Context<Self>,
23633 ) {
23634 let snapshot = self.buffer().read(cx).snapshot(cx);
23635 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23636 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23637 row_highlights.retain(|highlight| {
23638 while let Some(range_to_remove) = ranges_to_remove.peek() {
23639 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23640 Ordering::Less | Ordering::Equal => {
23641 ranges_to_remove.next();
23642 }
23643 Ordering::Greater => {
23644 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23645 Ordering::Less | Ordering::Equal => {
23646 return false;
23647 }
23648 Ordering::Greater => break,
23649 }
23650 }
23651 }
23652 }
23653
23654 true
23655 })
23656 }
23657
23658 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23659 pub fn clear_row_highlights<T: 'static>(&mut self) {
23660 self.highlighted_rows.remove(&TypeId::of::<T>());
23661 }
23662
23663 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23664 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23665 self.highlighted_rows
23666 .get(&TypeId::of::<T>())
23667 .map_or(&[] as &[_], |vec| vec.as_slice())
23668 .iter()
23669 .map(|highlight| (highlight.range.clone(), highlight.color))
23670 }
23671
23672 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23673 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23674 /// Allows to ignore certain kinds of highlights.
23675 pub fn highlighted_display_rows(
23676 &self,
23677 window: &mut Window,
23678 cx: &mut App,
23679 ) -> BTreeMap<DisplayRow, LineHighlight> {
23680 let snapshot = self.snapshot(window, cx);
23681 let mut used_highlight_orders = HashMap::default();
23682 self.highlighted_rows
23683 .iter()
23684 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23685 .fold(
23686 BTreeMap::<DisplayRow, LineHighlight>::new(),
23687 |mut unique_rows, highlight| {
23688 let start = highlight.range.start.to_display_point(&snapshot);
23689 let end = highlight.range.end.to_display_point(&snapshot);
23690 let start_row = start.row().0;
23691 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23692 {
23693 end.row().0.saturating_sub(1)
23694 } else {
23695 end.row().0
23696 };
23697 for row in start_row..=end_row {
23698 let used_index =
23699 used_highlight_orders.entry(row).or_insert(highlight.index);
23700 if highlight.index >= *used_index {
23701 *used_index = highlight.index;
23702 unique_rows.insert(
23703 DisplayRow(row),
23704 LineHighlight {
23705 include_gutter: highlight.options.include_gutter,
23706 border: None,
23707 background: highlight.color.into(),
23708 type_id: Some(highlight.type_id),
23709 },
23710 );
23711 }
23712 }
23713 unique_rows
23714 },
23715 )
23716 }
23717
23718 pub fn highlighted_display_row_for_autoscroll(
23719 &self,
23720 snapshot: &DisplaySnapshot,
23721 ) -> Option<DisplayRow> {
23722 self.highlighted_rows
23723 .values()
23724 .flat_map(|highlighted_rows| highlighted_rows.iter())
23725 .filter_map(|highlight| {
23726 if highlight.options.autoscroll {
23727 Some(highlight.range.start.to_display_point(snapshot).row())
23728 } else {
23729 None
23730 }
23731 })
23732 .min()
23733 }
23734
23735 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23736 self.highlight_background(
23737 HighlightKey::SearchWithinRange,
23738 ranges,
23739 |_, colors| colors.colors().editor_document_highlight_read_background,
23740 cx,
23741 )
23742 }
23743
23744 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23745 self.breadcrumb_header = Some(new_header);
23746 }
23747
23748 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23749 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23750 }
23751
23752 pub fn highlight_background(
23753 &mut self,
23754 key: HighlightKey,
23755 ranges: &[Range<Anchor>],
23756 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23757 cx: &mut Context<Self>,
23758 ) {
23759 self.background_highlights
23760 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23761 self.scrollbar_marker_state.dirty = true;
23762 cx.notify();
23763 }
23764
23765 pub fn clear_background_highlights(
23766 &mut self,
23767 key: HighlightKey,
23768 cx: &mut Context<Self>,
23769 ) -> Option<BackgroundHighlight> {
23770 let text_highlights = self.background_highlights.remove(&key)?;
23771 if !text_highlights.1.is_empty() {
23772 self.scrollbar_marker_state.dirty = true;
23773 cx.notify();
23774 }
23775 Some(text_highlights)
23776 }
23777
23778 pub fn highlight_gutter<T: 'static>(
23779 &mut self,
23780 ranges: impl Into<Vec<Range<Anchor>>>,
23781 color_fetcher: fn(&App) -> Hsla,
23782 cx: &mut Context<Self>,
23783 ) {
23784 self.gutter_highlights
23785 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23786 cx.notify();
23787 }
23788
23789 pub fn clear_gutter_highlights<T: 'static>(
23790 &mut self,
23791 cx: &mut Context<Self>,
23792 ) -> Option<GutterHighlight> {
23793 cx.notify();
23794 self.gutter_highlights.remove(&TypeId::of::<T>())
23795 }
23796
23797 pub fn insert_gutter_highlight<T: 'static>(
23798 &mut self,
23799 range: Range<Anchor>,
23800 color_fetcher: fn(&App) -> Hsla,
23801 cx: &mut Context<Self>,
23802 ) {
23803 let snapshot = self.buffer().read(cx).snapshot(cx);
23804 let mut highlights = self
23805 .gutter_highlights
23806 .remove(&TypeId::of::<T>())
23807 .map(|(_, highlights)| highlights)
23808 .unwrap_or_default();
23809 let ix = highlights.binary_search_by(|highlight| {
23810 Ordering::Equal
23811 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23812 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23813 });
23814 if let Err(ix) = ix {
23815 highlights.insert(ix, range);
23816 }
23817 self.gutter_highlights
23818 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23819 }
23820
23821 pub fn remove_gutter_highlights<T: 'static>(
23822 &mut self,
23823 ranges_to_remove: Vec<Range<Anchor>>,
23824 cx: &mut Context<Self>,
23825 ) {
23826 let snapshot = self.buffer().read(cx).snapshot(cx);
23827 let Some((color_fetcher, mut gutter_highlights)) =
23828 self.gutter_highlights.remove(&TypeId::of::<T>())
23829 else {
23830 return;
23831 };
23832 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23833 gutter_highlights.retain(|highlight| {
23834 while let Some(range_to_remove) = ranges_to_remove.peek() {
23835 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23836 Ordering::Less | Ordering::Equal => {
23837 ranges_to_remove.next();
23838 }
23839 Ordering::Greater => {
23840 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23841 Ordering::Less | Ordering::Equal => {
23842 return false;
23843 }
23844 Ordering::Greater => break,
23845 }
23846 }
23847 }
23848 }
23849
23850 true
23851 });
23852 self.gutter_highlights
23853 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23854 }
23855
23856 #[cfg(any(test, feature = "test-support"))]
23857 pub fn all_text_highlights(
23858 &self,
23859 window: &mut Window,
23860 cx: &mut Context<Self>,
23861 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23862 let snapshot = self.snapshot(window, cx);
23863 self.display_map.update(cx, |display_map, _| {
23864 display_map
23865 .all_text_highlights()
23866 .map(|(_, highlight)| {
23867 let (style, ranges) = highlight.as_ref();
23868 (
23869 *style,
23870 ranges
23871 .iter()
23872 .map(|range| range.clone().to_display_points(&snapshot))
23873 .collect(),
23874 )
23875 })
23876 .collect()
23877 })
23878 }
23879
23880 #[cfg(any(test, feature = "test-support"))]
23881 pub fn all_text_background_highlights(
23882 &self,
23883 window: &mut Window,
23884 cx: &mut Context<Self>,
23885 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23886 let snapshot = self.snapshot(window, cx);
23887 let buffer = &snapshot.buffer_snapshot();
23888 let start = buffer.anchor_before(MultiBufferOffset(0));
23889 let end = buffer.anchor_after(buffer.len());
23890 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23891 }
23892
23893 #[cfg(any(test, feature = "test-support"))]
23894 pub fn sorted_background_highlights_in_range(
23895 &self,
23896 search_range: Range<Anchor>,
23897 display_snapshot: &DisplaySnapshot,
23898 theme: &Theme,
23899 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23900 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23901 res.sort_by(|a, b| {
23902 a.0.start
23903 .cmp(&b.0.start)
23904 .then_with(|| a.0.end.cmp(&b.0.end))
23905 .then_with(|| a.1.cmp(&b.1))
23906 });
23907 res
23908 }
23909
23910 #[cfg(any(test, feature = "test-support"))]
23911 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23912 let snapshot = self.buffer().read(cx).snapshot(cx);
23913
23914 let highlights = self
23915 .background_highlights
23916 .get(&HighlightKey::BufferSearchHighlights);
23917
23918 if let Some((_color, ranges)) = highlights {
23919 ranges
23920 .iter()
23921 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23922 .collect_vec()
23923 } else {
23924 vec![]
23925 }
23926 }
23927
23928 fn document_highlights_for_position<'a>(
23929 &'a self,
23930 position: Anchor,
23931 buffer: &'a MultiBufferSnapshot,
23932 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23933 let read_highlights = self
23934 .background_highlights
23935 .get(&HighlightKey::DocumentHighlightRead)
23936 .map(|h| &h.1);
23937 let write_highlights = self
23938 .background_highlights
23939 .get(&HighlightKey::DocumentHighlightWrite)
23940 .map(|h| &h.1);
23941 let left_position = position.bias_left(buffer);
23942 let right_position = position.bias_right(buffer);
23943 read_highlights
23944 .into_iter()
23945 .chain(write_highlights)
23946 .flat_map(move |ranges| {
23947 let start_ix = match ranges.binary_search_by(|probe| {
23948 let cmp = probe.end.cmp(&left_position, buffer);
23949 if cmp.is_ge() {
23950 Ordering::Greater
23951 } else {
23952 Ordering::Less
23953 }
23954 }) {
23955 Ok(i) | Err(i) => i,
23956 };
23957
23958 ranges[start_ix..]
23959 .iter()
23960 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23961 })
23962 }
23963
23964 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23965 self.background_highlights
23966 .get(&key)
23967 .is_some_and(|(_, highlights)| !highlights.is_empty())
23968 }
23969
23970 /// Returns all background highlights for a given range.
23971 ///
23972 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23973 pub fn background_highlights_in_range(
23974 &self,
23975 search_range: Range<Anchor>,
23976 display_snapshot: &DisplaySnapshot,
23977 theme: &Theme,
23978 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23979 let mut results = Vec::new();
23980 for (color_fetcher, ranges) in self.background_highlights.values() {
23981 let start_ix = match ranges.binary_search_by(|probe| {
23982 let cmp = probe
23983 .end
23984 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23985 if cmp.is_gt() {
23986 Ordering::Greater
23987 } else {
23988 Ordering::Less
23989 }
23990 }) {
23991 Ok(i) | Err(i) => i,
23992 };
23993 for (index, range) in ranges[start_ix..].iter().enumerate() {
23994 if range
23995 .start
23996 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23997 .is_ge()
23998 {
23999 break;
24000 }
24001
24002 let color = color_fetcher(&(start_ix + index), theme);
24003 let start = range.start.to_display_point(display_snapshot);
24004 let end = range.end.to_display_point(display_snapshot);
24005 results.push((start..end, color))
24006 }
24007 }
24008 results
24009 }
24010
24011 pub fn gutter_highlights_in_range(
24012 &self,
24013 search_range: Range<Anchor>,
24014 display_snapshot: &DisplaySnapshot,
24015 cx: &App,
24016 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
24017 let mut results = Vec::new();
24018 for (color_fetcher, ranges) in self.gutter_highlights.values() {
24019 let color = color_fetcher(cx);
24020 let start_ix = match ranges.binary_search_by(|probe| {
24021 let cmp = probe
24022 .end
24023 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
24024 if cmp.is_gt() {
24025 Ordering::Greater
24026 } else {
24027 Ordering::Less
24028 }
24029 }) {
24030 Ok(i) | Err(i) => i,
24031 };
24032 for range in &ranges[start_ix..] {
24033 if range
24034 .start
24035 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
24036 .is_ge()
24037 {
24038 break;
24039 }
24040
24041 let start = range.start.to_display_point(display_snapshot);
24042 let end = range.end.to_display_point(display_snapshot);
24043 results.push((start..end, color))
24044 }
24045 }
24046 results
24047 }
24048
24049 /// Get the text ranges corresponding to the redaction query
24050 pub fn redacted_ranges(
24051 &self,
24052 search_range: Range<Anchor>,
24053 display_snapshot: &DisplaySnapshot,
24054 cx: &App,
24055 ) -> Vec<Range<DisplayPoint>> {
24056 display_snapshot
24057 .buffer_snapshot()
24058 .redacted_ranges(search_range, |file| {
24059 if let Some(file) = file {
24060 file.is_private()
24061 && EditorSettings::get(
24062 Some(SettingsLocation {
24063 worktree_id: file.worktree_id(cx),
24064 path: file.path().as_ref(),
24065 }),
24066 cx,
24067 )
24068 .redact_private_values
24069 } else {
24070 false
24071 }
24072 })
24073 .map(|range| {
24074 range.start.to_display_point(display_snapshot)
24075 ..range.end.to_display_point(display_snapshot)
24076 })
24077 .collect()
24078 }
24079
24080 pub fn highlight_text_key(
24081 &mut self,
24082 key: HighlightKey,
24083 ranges: Vec<Range<Anchor>>,
24084 style: HighlightStyle,
24085 merge: bool,
24086 cx: &mut Context<Self>,
24087 ) {
24088 self.display_map.update(cx, |map, cx| {
24089 map.highlight_text(key, ranges, style, merge, cx);
24090 });
24091 cx.notify();
24092 }
24093
24094 pub fn highlight_text(
24095 &mut self,
24096 key: HighlightKey,
24097 ranges: Vec<Range<Anchor>>,
24098 style: HighlightStyle,
24099 cx: &mut Context<Self>,
24100 ) {
24101 self.display_map.update(cx, |map, cx| {
24102 map.highlight_text(key, ranges, style, false, cx)
24103 });
24104 cx.notify();
24105 }
24106
24107 pub fn text_highlights<'a>(
24108 &'a self,
24109 key: HighlightKey,
24110 cx: &'a App,
24111 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
24112 self.display_map.read(cx).text_highlights(key)
24113 }
24114
24115 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
24116 let cleared = self
24117 .display_map
24118 .update(cx, |map, _| map.clear_highlights(key));
24119 if cleared {
24120 cx.notify();
24121 }
24122 }
24123
24124 pub fn clear_highlights_with(
24125 &mut self,
24126 f: &mut dyn FnMut(&HighlightKey) -> bool,
24127 cx: &mut Context<Self>,
24128 ) {
24129 let cleared = self
24130 .display_map
24131 .update(cx, |map, _| map.clear_highlights_with(f));
24132 if cleared {
24133 cx.notify();
24134 }
24135 }
24136
24137 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
24138 (self.read_only(cx) || self.blink_manager.read(cx).visible())
24139 && self.focus_handle.is_focused(window)
24140 }
24141
24142 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
24143 self.show_cursor_when_unfocused = is_enabled;
24144 cx.notify();
24145 }
24146
24147 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
24148 cx.notify();
24149 }
24150
24151 fn on_debug_session_event(
24152 &mut self,
24153 _session: Entity<Session>,
24154 event: &SessionEvent,
24155 cx: &mut Context<Self>,
24156 ) {
24157 if let SessionEvent::InvalidateInlineValue = event {
24158 self.refresh_inline_values(cx);
24159 }
24160 }
24161
24162 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
24163 let Some(semantics) = self.semantics_provider.clone() else {
24164 return;
24165 };
24166
24167 if !self.inline_value_cache.enabled {
24168 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24169 self.splice_inlays(&inlays, Vec::new(), cx);
24170 return;
24171 }
24172
24173 let current_execution_position = self
24174 .highlighted_rows
24175 .get(&TypeId::of::<ActiveDebugLine>())
24176 .and_then(|lines| lines.last().map(|line| line.range.end));
24177
24178 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24179 let inline_values = editor
24180 .update(cx, |editor, cx| {
24181 let Some(current_execution_position) = current_execution_position else {
24182 return Some(Task::ready(Ok(Vec::new())));
24183 };
24184
24185 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
24186 let snapshot = buffer.snapshot(cx);
24187
24188 let excerpt = snapshot.excerpt_containing(
24189 current_execution_position..current_execution_position,
24190 )?;
24191
24192 editor.buffer.read(cx).buffer(excerpt.buffer_id())
24193 })?;
24194
24195 if current_execution_position
24196 .text_anchor
24197 .buffer_id
24198 .is_some_and(|id| id != buffer.read(cx).remote_id())
24199 {
24200 return Some(Task::ready(Ok(Vec::new())));
24201 }
24202
24203 let range =
24204 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
24205
24206 semantics.inline_values(buffer, range, cx)
24207 })
24208 .ok()
24209 .flatten()?
24210 .await
24211 .context("refreshing debugger inlays")
24212 .log_err()?;
24213
24214 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24215
24216 for (buffer_id, inline_value) in inline_values
24217 .into_iter()
24218 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
24219 {
24220 buffer_inline_values
24221 .entry(buffer_id)
24222 .or_default()
24223 .push(inline_value);
24224 }
24225
24226 editor
24227 .update(cx, |editor, cx| {
24228 let snapshot = editor.buffer.read(cx).snapshot(cx);
24229 let mut new_inlays = Vec::default();
24230
24231 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
24232 let buffer_id = buffer_snapshot.remote_id();
24233 buffer_inline_values
24234 .get(&buffer_id)
24235 .into_iter()
24236 .flatten()
24237 .for_each(|hint| {
24238 let inlay = Inlay::debugger(
24239 post_inc(&mut editor.next_inlay_id),
24240 Anchor::in_buffer(excerpt_id, hint.position),
24241 hint.text(),
24242 );
24243 if !inlay.text().chars().contains(&'\n') {
24244 new_inlays.push(inlay);
24245 }
24246 });
24247 }
24248
24249 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24250 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24251
24252 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24253 })
24254 .ok()?;
24255 Some(())
24256 });
24257 }
24258
24259 fn on_buffer_event(
24260 &mut self,
24261 multibuffer: &Entity<MultiBuffer>,
24262 event: &multi_buffer::Event,
24263 window: &mut Window,
24264 cx: &mut Context<Self>,
24265 ) {
24266 match event {
24267 multi_buffer::Event::Edited {
24268 edited_buffer,
24269 is_local,
24270 } => {
24271 self.scrollbar_marker_state.dirty = true;
24272 self.active_indent_guides_state.dirty = true;
24273 self.refresh_active_diagnostics(cx);
24274 self.refresh_code_actions(window, cx);
24275 self.refresh_single_line_folds(window, cx);
24276 let snapshot = self.snapshot(window, cx);
24277 self.refresh_matching_bracket_highlights(&snapshot, cx);
24278 self.refresh_outline_symbols_at_cursor(cx);
24279 self.refresh_sticky_headers(&snapshot, cx);
24280 if *is_local && self.has_active_edit_prediction() {
24281 self.update_visible_edit_prediction(window, cx);
24282 }
24283
24284 // Clean up orphaned review comments after edits
24285 self.cleanup_orphaned_review_comments(cx);
24286
24287 if let Some(buffer) = edited_buffer {
24288 if buffer.read(cx).file().is_none() {
24289 cx.emit(EditorEvent::TitleChanged);
24290 }
24291
24292 if self.project.is_some() {
24293 let buffer_id = buffer.read(cx).remote_id();
24294 self.register_buffer(buffer_id, cx);
24295 self.update_lsp_data(Some(buffer_id), window, cx);
24296 self.refresh_inlay_hints(
24297 InlayHintRefreshReason::BufferEdited(buffer_id),
24298 cx,
24299 );
24300 }
24301 }
24302
24303 cx.emit(EditorEvent::BufferEdited);
24304 cx.emit(SearchEvent::MatchesInvalidated);
24305
24306 let Some(project) = &self.project else { return };
24307 let (telemetry, is_via_ssh) = {
24308 let project = project.read(cx);
24309 let telemetry = project.client().telemetry().clone();
24310 let is_via_ssh = project.is_via_remote_server();
24311 (telemetry, is_via_ssh)
24312 };
24313 telemetry.log_edit_event("editor", is_via_ssh);
24314 }
24315 multi_buffer::Event::ExcerptsAdded {
24316 buffer,
24317 predecessor,
24318 excerpts,
24319 } => {
24320 let buffer_id = buffer.read(cx).remote_id();
24321 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24322 && let Some(project) = &self.project
24323 {
24324 update_uncommitted_diff_for_buffer(
24325 cx.entity(),
24326 project,
24327 [buffer.clone()],
24328 self.buffer.clone(),
24329 cx,
24330 )
24331 .detach();
24332 }
24333 self.semantic_token_state
24334 .invalidate_buffer(&buffer.read(cx).remote_id());
24335 self.update_lsp_data(Some(buffer_id), window, cx);
24336 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24337 self.refresh_runnables(None, window, cx);
24338 self.colorize_brackets(false, cx);
24339 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24340 cx.emit(EditorEvent::ExcerptsAdded {
24341 buffer: buffer.clone(),
24342 predecessor: *predecessor,
24343 excerpts: excerpts.clone(),
24344 });
24345 }
24346 multi_buffer::Event::ExcerptsRemoved {
24347 ids,
24348 removed_buffer_ids,
24349 } => {
24350 if let Some(inlay_hints) = &mut self.inlay_hints {
24351 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24352 }
24353 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24354 for buffer_id in removed_buffer_ids {
24355 self.registered_buffers.remove(buffer_id);
24356 self.clear_runnables(Some(*buffer_id));
24357 self.semantic_token_state.invalidate_buffer(buffer_id);
24358 self.display_map.update(cx, |display_map, cx| {
24359 display_map.invalidate_semantic_highlights(*buffer_id);
24360 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24361 });
24362 }
24363
24364 self.display_map.update(cx, |display_map, cx| {
24365 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24366 });
24367
24368 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24369 cx.emit(EditorEvent::ExcerptsRemoved {
24370 ids: ids.clone(),
24371 removed_buffer_ids: removed_buffer_ids.clone(),
24372 });
24373 }
24374 multi_buffer::Event::ExcerptsEdited {
24375 excerpt_ids,
24376 buffer_ids,
24377 } => {
24378 self.display_map.update(cx, |map, cx| {
24379 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24380 });
24381 cx.emit(EditorEvent::ExcerptsEdited {
24382 ids: excerpt_ids.clone(),
24383 });
24384 }
24385 multi_buffer::Event::ExcerptsExpanded { ids } => {
24386 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24387 self.refresh_document_highlights(cx);
24388 let snapshot = multibuffer.read(cx).snapshot(cx);
24389 for id in ids {
24390 self.bracket_fetched_tree_sitter_chunks.remove(id);
24391 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24392 self.semantic_token_state
24393 .invalidate_buffer(&buffer.remote_id());
24394 }
24395 }
24396 self.colorize_brackets(false, cx);
24397 self.update_lsp_data(None, window, cx);
24398 self.refresh_runnables(None, window, cx);
24399 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24400 }
24401 multi_buffer::Event::Reparsed(buffer_id) => {
24402 self.refresh_runnables(Some(*buffer_id), window, cx);
24403 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24404 self.colorize_brackets(true, cx);
24405 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24406
24407 cx.emit(EditorEvent::Reparsed(*buffer_id));
24408 }
24409 multi_buffer::Event::DiffHunksToggled => {
24410 self.refresh_runnables(None, window, cx);
24411 }
24412 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24413 if !is_fresh_language {
24414 self.registered_buffers.remove(&buffer_id);
24415 }
24416 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24417 cx.emit(EditorEvent::Reparsed(*buffer_id));
24418 self.update_edit_prediction_settings(cx);
24419 cx.notify();
24420 }
24421 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24422 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24423 multi_buffer::Event::FileHandleChanged
24424 | multi_buffer::Event::Reloaded
24425 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24426 multi_buffer::Event::DiagnosticsUpdated => {
24427 self.update_diagnostics_state(window, cx);
24428 }
24429 _ => {}
24430 };
24431 }
24432
24433 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24434 if !self.diagnostics_enabled() {
24435 return;
24436 }
24437 self.refresh_active_diagnostics(cx);
24438 self.refresh_inline_diagnostics(true, window, cx);
24439 self.scrollbar_marker_state.dirty = true;
24440 cx.notify();
24441 }
24442
24443 pub fn start_temporary_diff_override(&mut self) {
24444 self.load_diff_task.take();
24445 self.temporary_diff_override = true;
24446 }
24447
24448 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24449 self.temporary_diff_override = false;
24450 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24451 self.buffer.update(cx, |buffer, cx| {
24452 buffer.set_all_diff_hunks_collapsed(cx);
24453 });
24454
24455 if let Some(project) = self.project.clone() {
24456 self.load_diff_task = Some(
24457 update_uncommitted_diff_for_buffer(
24458 cx.entity(),
24459 &project,
24460 self.buffer.read(cx).all_buffers(),
24461 self.buffer.clone(),
24462 cx,
24463 )
24464 .shared(),
24465 );
24466 }
24467 }
24468
24469 fn on_display_map_changed(
24470 &mut self,
24471 _: Entity<DisplayMap>,
24472 _: &mut Window,
24473 cx: &mut Context<Self>,
24474 ) {
24475 cx.notify();
24476 }
24477
24478 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24479 if !self.mode.is_full() {
24480 return None;
24481 }
24482
24483 let theme_settings = theme_settings::ThemeSettings::get_global(cx);
24484 let theme = cx.theme();
24485 let accent_colors = theme.accents().clone();
24486
24487 let accent_overrides = theme_settings
24488 .theme_overrides
24489 .get(theme.name.as_ref())
24490 .map(|theme_style| &theme_style.accents)
24491 .into_iter()
24492 .flatten()
24493 .chain(
24494 theme_settings
24495 .experimental_theme_overrides
24496 .as_ref()
24497 .map(|overrides| &overrides.accents)
24498 .into_iter()
24499 .flatten(),
24500 )
24501 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24502 .collect();
24503
24504 Some(AccentData {
24505 colors: accent_colors,
24506 overrides: accent_overrides,
24507 })
24508 }
24509
24510 fn fetch_applicable_language_settings(
24511 &self,
24512 cx: &App,
24513 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24514 if !self.mode.is_full() {
24515 return HashMap::default();
24516 }
24517
24518 self.buffer().read(cx).all_buffers().into_iter().fold(
24519 HashMap::default(),
24520 |mut acc, buffer| {
24521 let buffer = buffer.read(cx);
24522 let language = buffer.language().map(|language| language.name());
24523 if let hash_map::Entry::Vacant(v) = acc.entry(language) {
24524 v.insert(LanguageSettings::for_buffer(&buffer, cx).into_owned());
24525 }
24526 acc
24527 },
24528 )
24529 }
24530
24531 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24532 let new_language_settings = self.fetch_applicable_language_settings(cx);
24533 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24534 self.applicable_language_settings = new_language_settings;
24535
24536 let new_accents = self.fetch_accent_data(cx);
24537 let accents_changed = new_accents != self.accent_data;
24538 self.accent_data = new_accents;
24539
24540 if self.diagnostics_enabled() {
24541 let new_severity = EditorSettings::get_global(cx)
24542 .diagnostics_max_severity
24543 .unwrap_or(DiagnosticSeverity::Hint);
24544 self.set_max_diagnostics_severity(new_severity, cx);
24545 }
24546 self.refresh_runnables(None, window, cx);
24547 self.update_edit_prediction_settings(cx);
24548 self.refresh_edit_prediction(true, false, window, cx);
24549 self.refresh_inline_values(cx);
24550
24551 let old_cursor_shape = self.cursor_shape;
24552 let old_show_breadcrumbs = self.show_breadcrumbs;
24553
24554 {
24555 let editor_settings = EditorSettings::get_global(cx);
24556 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24557 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24558 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24559 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24560 }
24561
24562 if old_cursor_shape != self.cursor_shape {
24563 cx.emit(EditorEvent::CursorShapeChanged);
24564 }
24565
24566 if old_show_breadcrumbs != self.show_breadcrumbs {
24567 cx.emit(EditorEvent::BreadcrumbsChanged);
24568 }
24569
24570 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24571 let project_settings = ProjectSettings::get_global(cx);
24572 (
24573 project_settings.session.restore_unsaved_buffers,
24574 project_settings.diagnostics.inline.enabled,
24575 project_settings.git.inline_blame.enabled,
24576 )
24577 };
24578 self.buffer_serialization = self
24579 .should_serialize_buffer()
24580 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24581
24582 if self.mode.is_full() {
24583 if self.show_inline_diagnostics != show_inline_diagnostics {
24584 self.show_inline_diagnostics = show_inline_diagnostics;
24585 self.refresh_inline_diagnostics(false, window, cx);
24586 }
24587
24588 if self.git_blame_inline_enabled != inline_blame_enabled {
24589 self.toggle_git_blame_inline_internal(false, window, cx);
24590 }
24591
24592 let minimap_settings = EditorSettings::get_global(cx).minimap;
24593 if self.minimap_visibility != MinimapVisibility::Disabled {
24594 if self.minimap_visibility.settings_visibility()
24595 != minimap_settings.minimap_enabled()
24596 {
24597 self.set_minimap_visibility(
24598 MinimapVisibility::for_mode(self.mode(), cx),
24599 window,
24600 cx,
24601 );
24602 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24603 minimap_entity.update(cx, |minimap_editor, cx| {
24604 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24605 })
24606 }
24607 }
24608
24609 if language_settings_changed || accents_changed {
24610 self.colorize_brackets(true, cx);
24611 }
24612
24613 if language_settings_changed {
24614 self.clear_disabled_lsp_folding_ranges(window, cx);
24615 self.refresh_document_symbols(None, cx);
24616 }
24617
24618 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24619 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24620 }) {
24621 if !inlay_splice.is_empty() {
24622 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24623 }
24624 self.refresh_document_colors(None, window, cx);
24625 }
24626
24627 self.refresh_inlay_hints(
24628 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24629 self.selections.newest_anchor().head(),
24630 &self.buffer.read(cx).snapshot(cx),
24631 cx,
24632 )),
24633 cx,
24634 );
24635
24636 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24637 .global_lsp_settings
24638 .semantic_token_rules
24639 .clone();
24640 let semantic_token_rules_changed = self
24641 .semantic_token_state
24642 .update_rules(new_semantic_token_rules);
24643 if language_settings_changed || semantic_token_rules_changed {
24644 self.invalidate_semantic_tokens(None);
24645 self.refresh_semantic_tokens(None, None, cx);
24646 }
24647 }
24648
24649 cx.notify();
24650 }
24651
24652 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24653 if !self.mode.is_full() {
24654 return;
24655 }
24656
24657 let new_accents = self.fetch_accent_data(cx);
24658 if new_accents != self.accent_data {
24659 self.accent_data = new_accents;
24660 self.colorize_brackets(true, cx);
24661 }
24662
24663 self.invalidate_semantic_tokens(None);
24664 self.refresh_semantic_tokens(None, None, cx);
24665 }
24666
24667 pub fn set_searchable(&mut self, searchable: bool) {
24668 self.searchable = searchable;
24669 }
24670
24671 pub fn searchable(&self) -> bool {
24672 self.searchable
24673 }
24674
24675 pub fn open_excerpts_in_split(
24676 &mut self,
24677 _: &OpenExcerptsSplit,
24678 window: &mut Window,
24679 cx: &mut Context<Self>,
24680 ) {
24681 self.open_excerpts_common(None, true, window, cx)
24682 }
24683
24684 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24685 self.open_excerpts_common(None, false, window, cx)
24686 }
24687
24688 pub(crate) fn open_excerpts_common(
24689 &mut self,
24690 jump_data: Option<JumpData>,
24691 split: bool,
24692 window: &mut Window,
24693 cx: &mut Context<Self>,
24694 ) {
24695 if self.buffer.read(cx).is_singleton() {
24696 cx.propagate();
24697 return;
24698 }
24699
24700 let mut new_selections_by_buffer = HashMap::default();
24701 match &jump_data {
24702 Some(JumpData::MultiBufferPoint {
24703 excerpt_id,
24704 position,
24705 anchor,
24706 line_offset_from_top,
24707 }) => {
24708 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24709 if let Some(buffer) = multi_buffer_snapshot
24710 .buffer_id_for_excerpt(*excerpt_id)
24711 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24712 {
24713 let buffer_snapshot = buffer.read(cx).snapshot();
24714 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24715 language::ToPoint::to_point(anchor, &buffer_snapshot)
24716 } else {
24717 buffer_snapshot.clip_point(*position, Bias::Left)
24718 };
24719 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24720 new_selections_by_buffer.insert(
24721 buffer,
24722 (
24723 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24724 Some(*line_offset_from_top),
24725 ),
24726 );
24727 }
24728 }
24729 Some(JumpData::MultiBufferRow {
24730 row,
24731 line_offset_from_top,
24732 }) => {
24733 let point = MultiBufferPoint::new(row.0, 0);
24734 if let Some((buffer, buffer_point, _)) =
24735 self.buffer.read(cx).point_to_buffer_point(point, cx)
24736 {
24737 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24738 new_selections_by_buffer
24739 .entry(buffer)
24740 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24741 .0
24742 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24743 }
24744 }
24745 None => {
24746 let selections = self
24747 .selections
24748 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24749 let multi_buffer = self.buffer.read(cx);
24750 for selection in selections {
24751 for (snapshot, range, _, anchor) in multi_buffer
24752 .snapshot(cx)
24753 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24754 {
24755 if let Some(anchor) = anchor {
24756 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24757 else {
24758 continue;
24759 };
24760 let offset = text::ToOffset::to_offset(
24761 &anchor.text_anchor,
24762 &buffer_handle.read(cx).snapshot(),
24763 );
24764 let range = BufferOffset(offset)..BufferOffset(offset);
24765 new_selections_by_buffer
24766 .entry(buffer_handle)
24767 .or_insert((Vec::new(), None))
24768 .0
24769 .push(range)
24770 } else {
24771 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24772 else {
24773 continue;
24774 };
24775 new_selections_by_buffer
24776 .entry(buffer_handle)
24777 .or_insert((Vec::new(), None))
24778 .0
24779 .push(range)
24780 }
24781 }
24782 }
24783 }
24784 }
24785
24786 if self.delegate_open_excerpts {
24787 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24788 .into_iter()
24789 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24790 .collect();
24791 if !selections_by_buffer.is_empty() {
24792 cx.emit(EditorEvent::OpenExcerptsRequested {
24793 selections_by_buffer,
24794 split,
24795 });
24796 }
24797 return;
24798 }
24799
24800 let Some(workspace) = self.workspace() else {
24801 cx.propagate();
24802 return;
24803 };
24804
24805 new_selections_by_buffer
24806 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24807
24808 if new_selections_by_buffer.is_empty() {
24809 return;
24810 }
24811
24812 Self::open_buffers_in_workspace(
24813 workspace.downgrade(),
24814 new_selections_by_buffer,
24815 split,
24816 window,
24817 cx,
24818 );
24819 }
24820
24821 pub(crate) fn open_buffers_in_workspace(
24822 workspace: WeakEntity<Workspace>,
24823 new_selections_by_buffer: HashMap<
24824 Entity<language::Buffer>,
24825 (Vec<Range<BufferOffset>>, Option<u32>),
24826 >,
24827 split: bool,
24828 window: &mut Window,
24829 cx: &mut App,
24830 ) {
24831 // We defer the pane interaction because we ourselves are a workspace item
24832 // and activating a new item causes the pane to call a method on us reentrantly,
24833 // which panics if we're on the stack.
24834 window.defer(cx, move |window, cx| {
24835 workspace
24836 .update(cx, |workspace, cx| {
24837 let pane = if split {
24838 workspace.adjacent_pane(window, cx)
24839 } else {
24840 workspace.active_pane().clone()
24841 };
24842
24843 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24844 let buffer_read = buffer.read(cx);
24845 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24846 (true, project::File::from_dyn(Some(file)).is_some())
24847 } else {
24848 (false, false)
24849 };
24850
24851 // If project file is none workspace.open_project_item will fail to open the excerpt
24852 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24853 // so we check if there's a tab match in that case first
24854 let editor = (!has_file || !is_project_file)
24855 .then(|| {
24856 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24857 // so `workspace.open_project_item` will never find them, always opening a new editor.
24858 // Instead, we try to activate the existing editor in the pane first.
24859 let (editor, pane_item_index, pane_item_id) =
24860 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24861 let editor = item.downcast::<Editor>()?;
24862 let singleton_buffer =
24863 editor.read(cx).buffer().read(cx).as_singleton()?;
24864 if singleton_buffer == buffer {
24865 Some((editor, i, item.item_id()))
24866 } else {
24867 None
24868 }
24869 })?;
24870 pane.update(cx, |pane, cx| {
24871 pane.activate_item(pane_item_index, true, true, window, cx);
24872 if !PreviewTabsSettings::get_global(cx)
24873 .enable_preview_from_multibuffer
24874 {
24875 pane.unpreview_item_if_preview(pane_item_id);
24876 }
24877 });
24878 Some(editor)
24879 })
24880 .flatten()
24881 .unwrap_or_else(|| {
24882 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24883 .enable_keep_preview_on_code_navigation;
24884 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24885 .enable_preview_from_multibuffer;
24886 workspace.open_project_item::<Self>(
24887 pane.clone(),
24888 buffer,
24889 true,
24890 true,
24891 keep_old_preview,
24892 allow_new_preview,
24893 window,
24894 cx,
24895 )
24896 });
24897
24898 editor.update(cx, |editor, cx| {
24899 if has_file && !is_project_file {
24900 editor.set_read_only(true);
24901 }
24902 let autoscroll = match scroll_offset {
24903 Some(scroll_offset) => {
24904 Autoscroll::top_relative(scroll_offset as usize)
24905 }
24906 None => Autoscroll::newest(),
24907 };
24908 let nav_history = editor.nav_history.take();
24909 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24910 let Some((excerpt_id, _, buffer_snapshot)) =
24911 multibuffer_snapshot.as_singleton()
24912 else {
24913 return;
24914 };
24915 editor.change_selections(
24916 SelectionEffects::scroll(autoscroll),
24917 window,
24918 cx,
24919 |s| {
24920 s.select_ranges(ranges.into_iter().map(|range| {
24921 let range = buffer_snapshot.anchor_before(range.start)
24922 ..buffer_snapshot.anchor_after(range.end);
24923 multibuffer_snapshot
24924 .anchor_range_in_excerpt(excerpt_id, range)
24925 .unwrap()
24926 }));
24927 },
24928 );
24929 editor.nav_history = nav_history;
24930 });
24931 }
24932 })
24933 .ok();
24934 });
24935 }
24936
24937 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24938 let snapshot = self.buffer.read(cx).read(cx);
24939 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24940 Some(
24941 ranges
24942 .iter()
24943 .map(move |range| {
24944 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24945 })
24946 .collect(),
24947 )
24948 }
24949
24950 fn selection_replacement_ranges(
24951 &self,
24952 range: Range<MultiBufferOffsetUtf16>,
24953 cx: &mut App,
24954 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24955 let selections = self
24956 .selections
24957 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24958 let newest_selection = selections
24959 .iter()
24960 .max_by_key(|selection| selection.id)
24961 .unwrap();
24962 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24963 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24964 let snapshot = self.buffer.read(cx).read(cx);
24965 selections
24966 .into_iter()
24967 .map(|mut selection| {
24968 selection.start.0.0 =
24969 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24970 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24971 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24972 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24973 })
24974 .collect()
24975 }
24976
24977 fn report_editor_event(
24978 &self,
24979 reported_event: ReportEditorEvent,
24980 file_extension: Option<String>,
24981 cx: &App,
24982 ) {
24983 if cfg!(any(test, feature = "test-support")) {
24984 return;
24985 }
24986
24987 let Some(project) = &self.project else { return };
24988
24989 // If None, we are in a file without an extension
24990 let file = self
24991 .buffer
24992 .read(cx)
24993 .as_singleton()
24994 .and_then(|b| b.read(cx).file());
24995 let file_extension = file_extension.or(file
24996 .as_ref()
24997 .and_then(|file| Path::new(file.file_name(cx)).extension())
24998 .and_then(|e| e.to_str())
24999 .map(|a| a.to_string()));
25000
25001 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
25002 .map(|vim_mode| vim_mode.0)
25003 .unwrap_or(false);
25004
25005 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
25006 let copilot_enabled = edit_predictions_provider
25007 == language::language_settings::EditPredictionProvider::Copilot;
25008 let copilot_enabled_for_language = self
25009 .buffer
25010 .read(cx)
25011 .language_settings(cx)
25012 .show_edit_predictions;
25013
25014 let project = project.read(cx);
25015 let event_type = reported_event.event_type();
25016
25017 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
25018 telemetry::event!(
25019 event_type,
25020 type = if auto_saved {"autosave"} else {"manual"},
25021 file_extension,
25022 vim_mode,
25023 copilot_enabled,
25024 copilot_enabled_for_language,
25025 edit_predictions_provider,
25026 is_via_ssh = project.is_via_remote_server(),
25027 );
25028 } else {
25029 telemetry::event!(
25030 event_type,
25031 file_extension,
25032 vim_mode,
25033 copilot_enabled,
25034 copilot_enabled_for_language,
25035 edit_predictions_provider,
25036 is_via_ssh = project.is_via_remote_server(),
25037 );
25038 };
25039 }
25040
25041 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
25042 /// with each line being an array of {text, highlight} objects.
25043 fn copy_highlight_json(
25044 &mut self,
25045 _: &CopyHighlightJson,
25046 _: &mut Window,
25047 cx: &mut Context<Self>,
25048 ) {
25049 #[derive(Serialize)]
25050 struct Chunk<'a> {
25051 text: String,
25052 highlight: Option<&'a str>,
25053 }
25054
25055 let snapshot = self.buffer.read(cx).snapshot(cx);
25056 let mut selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
25057 let max_point = snapshot.max_point();
25058
25059 let range = if self.selections.line_mode() {
25060 selection.start = Point::new(selection.start.row, 0);
25061 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
25062 selection.goal = SelectionGoal::None;
25063 selection.range()
25064 } else if selection.is_empty() {
25065 Point::new(0, 0)..max_point
25066 } else {
25067 selection.range()
25068 };
25069
25070 let chunks = snapshot.chunks(range, true);
25071 let mut lines = Vec::new();
25072 let mut line: VecDeque<Chunk> = VecDeque::new();
25073
25074 let Some(style) = self.style.as_ref() else {
25075 return;
25076 };
25077
25078 for chunk in chunks {
25079 let highlight = chunk
25080 .syntax_highlight_id
25081 .and_then(|id| style.syntax.get_capture_name(id));
25082
25083 let mut chunk_lines = chunk.text.split('\n').peekable();
25084 while let Some(text) = chunk_lines.next() {
25085 let mut merged_with_last_token = false;
25086 if let Some(last_token) = line.back_mut()
25087 && last_token.highlight == highlight
25088 {
25089 last_token.text.push_str(text);
25090 merged_with_last_token = true;
25091 }
25092
25093 if !merged_with_last_token {
25094 line.push_back(Chunk {
25095 text: text.into(),
25096 highlight,
25097 });
25098 }
25099
25100 if chunk_lines.peek().is_some() {
25101 if line.len() > 1 && line.front().unwrap().text.is_empty() {
25102 line.pop_front();
25103 }
25104 if line.len() > 1 && line.back().unwrap().text.is_empty() {
25105 line.pop_back();
25106 }
25107
25108 lines.push(mem::take(&mut line));
25109 }
25110 }
25111 }
25112
25113 if line.iter().any(|chunk| !chunk.text.is_empty()) {
25114 lines.push(line);
25115 }
25116
25117 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
25118 return;
25119 };
25120 cx.write_to_clipboard(ClipboardItem::new_string(lines));
25121 }
25122
25123 pub fn open_context_menu(
25124 &mut self,
25125 _: &OpenContextMenu,
25126 window: &mut Window,
25127 cx: &mut Context<Self>,
25128 ) {
25129 self.request_autoscroll(Autoscroll::newest(), cx);
25130 let position = self
25131 .selections
25132 .newest_display(&self.display_snapshot(cx))
25133 .start;
25134 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
25135 }
25136
25137 pub fn replay_insert_event(
25138 &mut self,
25139 text: &str,
25140 relative_utf16_range: Option<Range<isize>>,
25141 window: &mut Window,
25142 cx: &mut Context<Self>,
25143 ) {
25144 if !self.input_enabled {
25145 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25146 return;
25147 }
25148 if let Some(relative_utf16_range) = relative_utf16_range {
25149 let selections = self
25150 .selections
25151 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25152 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25153 let new_ranges = selections.into_iter().map(|range| {
25154 let start = MultiBufferOffsetUtf16(OffsetUtf16(
25155 range
25156 .head()
25157 .0
25158 .0
25159 .saturating_add_signed(relative_utf16_range.start),
25160 ));
25161 let end = MultiBufferOffsetUtf16(OffsetUtf16(
25162 range
25163 .head()
25164 .0
25165 .0
25166 .saturating_add_signed(relative_utf16_range.end),
25167 ));
25168 start..end
25169 });
25170 s.select_ranges(new_ranges);
25171 });
25172 }
25173
25174 self.handle_input(text, window, cx);
25175 }
25176
25177 pub fn is_focused(&self, window: &Window) -> bool {
25178 self.focus_handle.is_focused(window)
25179 }
25180
25181 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25182 cx.emit(EditorEvent::Focused);
25183
25184 if let Some(descendant) = self
25185 .last_focused_descendant
25186 .take()
25187 .and_then(|descendant| descendant.upgrade())
25188 {
25189 window.focus(&descendant, cx);
25190 } else {
25191 if let Some(blame) = self.blame.as_ref() {
25192 blame.update(cx, GitBlame::focus)
25193 }
25194
25195 self.blink_manager.update(cx, BlinkManager::enable);
25196 self.show_cursor_names(window, cx);
25197 self.buffer.update(cx, |buffer, cx| {
25198 buffer.finalize_last_transaction(cx);
25199 if self.leader_id.is_none() {
25200 buffer.set_active_selections(
25201 &self.selections.disjoint_anchors_arc(),
25202 self.selections.line_mode(),
25203 self.cursor_shape,
25204 cx,
25205 );
25206 }
25207 });
25208
25209 if let Some(position_map) = self.last_position_map.clone()
25210 && !self.mouse_cursor_hidden
25211 {
25212 EditorElement::mouse_moved(
25213 self,
25214 &MouseMoveEvent {
25215 position: window.mouse_position(),
25216 pressed_button: None,
25217 modifiers: window.modifiers(),
25218 },
25219 &position_map,
25220 None,
25221 window,
25222 cx,
25223 );
25224 }
25225 }
25226 }
25227
25228 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25229 cx.emit(EditorEvent::FocusedIn)
25230 }
25231
25232 fn handle_focus_out(
25233 &mut self,
25234 event: FocusOutEvent,
25235 _window: &mut Window,
25236 cx: &mut Context<Self>,
25237 ) {
25238 if event.blurred != self.focus_handle {
25239 self.last_focused_descendant = Some(event.blurred);
25240 }
25241 self.selection_drag_state = SelectionDragState::None;
25242 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25243 }
25244
25245 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25246 self.blink_manager.update(cx, BlinkManager::disable);
25247 self.buffer
25248 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25249
25250 if let Some(blame) = self.blame.as_ref() {
25251 blame.update(cx, GitBlame::blur)
25252 }
25253 if !self.hover_state.focused(window, cx) {
25254 hide_hover(self, cx);
25255 }
25256 if !self
25257 .context_menu
25258 .borrow()
25259 .as_ref()
25260 .is_some_and(|context_menu| context_menu.focused(window, cx))
25261 {
25262 self.hide_context_menu(window, cx);
25263 }
25264 self.take_active_edit_prediction(true, cx);
25265 cx.emit(EditorEvent::Blurred);
25266 cx.notify();
25267 }
25268
25269 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25270 let mut pending: String = window
25271 .pending_input_keystrokes()
25272 .into_iter()
25273 .flatten()
25274 .filter_map(|keystroke| keystroke.key_char.clone())
25275 .collect();
25276
25277 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25278 pending = "".to_string();
25279 }
25280
25281 let existing_pending = self
25282 .text_highlights(HighlightKey::PendingInput, cx)
25283 .map(|(_, ranges)| ranges.to_vec());
25284 if existing_pending.is_none() && pending.is_empty() {
25285 return;
25286 }
25287 let transaction =
25288 self.transact(window, cx, |this, window, cx| {
25289 let selections = this
25290 .selections
25291 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25292 let edits = selections
25293 .iter()
25294 .map(|selection| (selection.end..selection.end, pending.clone()));
25295 this.edit(edits, cx);
25296 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25297 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25298 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25299 }));
25300 });
25301 if let Some(existing_ranges) = existing_pending {
25302 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25303 this.edit(edits, cx);
25304 }
25305 });
25306
25307 let snapshot = self.snapshot(window, cx);
25308 let ranges = self
25309 .selections
25310 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25311 .into_iter()
25312 .map(|selection| {
25313 snapshot.buffer_snapshot().anchor_after(selection.end)
25314 ..snapshot
25315 .buffer_snapshot()
25316 .anchor_before(selection.end + pending.len())
25317 })
25318 .collect();
25319
25320 if pending.is_empty() {
25321 self.clear_highlights(HighlightKey::PendingInput, cx);
25322 } else {
25323 self.highlight_text(
25324 HighlightKey::PendingInput,
25325 ranges,
25326 HighlightStyle {
25327 underline: Some(UnderlineStyle {
25328 thickness: px(1.),
25329 color: None,
25330 wavy: false,
25331 }),
25332 ..Default::default()
25333 },
25334 cx,
25335 );
25336 }
25337
25338 self.ime_transaction = self.ime_transaction.or(transaction);
25339 if let Some(transaction) = self.ime_transaction {
25340 self.buffer.update(cx, |buffer, cx| {
25341 buffer.group_until_transaction(transaction, cx);
25342 });
25343 }
25344
25345 if self
25346 .text_highlights(HighlightKey::PendingInput, cx)
25347 .is_none()
25348 {
25349 self.ime_transaction.take();
25350 }
25351 }
25352
25353 pub fn register_action_renderer(
25354 &mut self,
25355 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25356 ) -> Subscription {
25357 let id = self.next_editor_action_id.post_inc();
25358 self.editor_actions
25359 .borrow_mut()
25360 .insert(id, Box::new(listener));
25361
25362 let editor_actions = self.editor_actions.clone();
25363 Subscription::new(move || {
25364 editor_actions.borrow_mut().remove(&id);
25365 })
25366 }
25367
25368 pub fn register_action<A: Action>(
25369 &mut self,
25370 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25371 ) -> Subscription {
25372 let id = self.next_editor_action_id.post_inc();
25373 let listener = Arc::new(listener);
25374 self.editor_actions.borrow_mut().insert(
25375 id,
25376 Box::new(move |_, window, _| {
25377 let listener = listener.clone();
25378 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25379 let action = action.downcast_ref().unwrap();
25380 if phase == DispatchPhase::Bubble {
25381 listener(action, window, cx)
25382 }
25383 })
25384 }),
25385 );
25386
25387 let editor_actions = self.editor_actions.clone();
25388 Subscription::new(move || {
25389 editor_actions.borrow_mut().remove(&id);
25390 })
25391 }
25392
25393 pub fn file_header_size(&self) -> u32 {
25394 FILE_HEADER_HEIGHT
25395 }
25396
25397 pub fn restore(
25398 &mut self,
25399 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25400 window: &mut Window,
25401 cx: &mut Context<Self>,
25402 ) {
25403 self.buffer().update(cx, |multi_buffer, cx| {
25404 for (buffer_id, changes) in revert_changes {
25405 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25406 buffer.update(cx, |buffer, cx| {
25407 buffer.edit(
25408 changes
25409 .into_iter()
25410 .map(|(range, text)| (range, text.to_string())),
25411 None,
25412 cx,
25413 );
25414 });
25415 }
25416 }
25417 });
25418 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25419 selections.refresh()
25420 });
25421 }
25422
25423 pub fn to_pixel_point(
25424 &mut self,
25425 source: Anchor,
25426 editor_snapshot: &EditorSnapshot,
25427 window: &mut Window,
25428 cx: &mut App,
25429 ) -> Option<gpui::Point<Pixels>> {
25430 let source_point = source.to_display_point(editor_snapshot);
25431 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25432 }
25433
25434 pub fn display_to_pixel_point(
25435 &mut self,
25436 source: DisplayPoint,
25437 editor_snapshot: &EditorSnapshot,
25438 window: &mut Window,
25439 cx: &mut App,
25440 ) -> Option<gpui::Point<Pixels>> {
25441 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25442 let text_layout_details = self.text_layout_details(window, cx);
25443 let scroll_top = text_layout_details
25444 .scroll_anchor
25445 .scroll_position(editor_snapshot)
25446 .y;
25447
25448 if source.row().as_f64() < scroll_top.floor() {
25449 return None;
25450 }
25451 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25452 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25453 Some(gpui::Point::new(source_x, source_y))
25454 }
25455
25456 pub fn has_visible_completions_menu(&self) -> bool {
25457 !self.edit_prediction_preview_is_active()
25458 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25459 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25460 })
25461 }
25462
25463 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25464 if self.mode.is_minimap() {
25465 return;
25466 }
25467 self.addons
25468 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25469 }
25470
25471 pub fn unregister_addon<T: Addon>(&mut self) {
25472 self.addons.remove(&std::any::TypeId::of::<T>());
25473 }
25474
25475 pub fn addon<T: Addon>(&self) -> Option<&T> {
25476 let type_id = std::any::TypeId::of::<T>();
25477 self.addons
25478 .get(&type_id)
25479 .and_then(|item| item.to_any().downcast_ref::<T>())
25480 }
25481
25482 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25483 let type_id = std::any::TypeId::of::<T>();
25484 self.addons
25485 .get_mut(&type_id)
25486 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25487 }
25488
25489 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25490 let text_layout_details = self.text_layout_details(window, cx);
25491 let style = &text_layout_details.editor_style;
25492 let font_id = window.text_system().resolve_font(&style.text.font());
25493 let font_size = style.text.font_size.to_pixels(window.rem_size());
25494 let line_height = style.text.line_height_in_pixels(window.rem_size());
25495 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25496 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25497
25498 CharacterDimensions {
25499 em_width,
25500 em_advance,
25501 line_height,
25502 }
25503 }
25504
25505 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25506 self.load_diff_task.clone()
25507 }
25508
25509 fn read_metadata_from_db(
25510 &mut self,
25511 item_id: u64,
25512 workspace_id: WorkspaceId,
25513 window: &mut Window,
25514 cx: &mut Context<Editor>,
25515 ) {
25516 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25517 && !self.mode.is_minimap()
25518 && WorkspaceSettings::get(None, cx).restore_on_startup
25519 != RestoreOnStartupBehavior::EmptyTab
25520 {
25521 let buffer_snapshot = OnceCell::new();
25522
25523 // Get file path for path-based fold lookup
25524 let file_path: Option<Arc<Path>> =
25525 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25526 project::File::from_dyn(buffer.read(cx).file())
25527 .map(|file| Arc::from(file.abs_path(cx)))
25528 });
25529
25530 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25531 let db = EditorDb::global(cx);
25532 let (folds, needs_migration) = if let Some(ref path) = file_path {
25533 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
25534 && !folds.is_empty()
25535 {
25536 (Some(folds), false)
25537 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
25538 && !folds.is_empty()
25539 {
25540 // Found old editor_folds data, will migrate to file_folds
25541 (Some(folds), true)
25542 } else {
25543 (None, false)
25544 }
25545 } else {
25546 // No file path, try editor_folds as fallback
25547 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
25548 (folds.filter(|f| !f.is_empty()), false)
25549 };
25550
25551 if let Some(folds) = folds {
25552 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25553 let snapshot_len = snapshot.len().0;
25554
25555 // Helper: search for fingerprint in buffer, return offset if found
25556 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25557 // Ensure we start at a character boundary (defensive)
25558 let search_start = snapshot
25559 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25560 .0;
25561 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25562
25563 let mut byte_offset = search_start;
25564 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25565 if byte_offset > search_end {
25566 break;
25567 }
25568 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25569 return Some(byte_offset);
25570 }
25571 byte_offset += ch.len_utf8();
25572 }
25573 None
25574 };
25575
25576 // Track search position to handle duplicate fingerprints correctly.
25577 // Folds are stored in document order, so we advance after each match.
25578 let mut search_start = 0usize;
25579
25580 // Collect db_folds for migration (only folds with valid fingerprints)
25581 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25582
25583 let valid_folds: Vec<_> = folds
25584 .into_iter()
25585 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25586 // Skip folds without fingerprints (old data before migration)
25587 let sfp = start_fp?;
25588 let efp = end_fp?;
25589 let efp_len = efp.len();
25590
25591 // Fast path: check if fingerprints match at stored offsets
25592 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25593 let start_matches = stored_start < snapshot_len
25594 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25595 let efp_check_pos = stored_end.saturating_sub(efp_len);
25596 let end_matches = efp_check_pos >= stored_start
25597 && stored_end <= snapshot_len
25598 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25599
25600 let (new_start, new_end) = if start_matches && end_matches {
25601 // Offsets unchanged, use stored values
25602 (stored_start, stored_end)
25603 } else if sfp == efp {
25604 // Short fold: identical fingerprints can only match once per search
25605 // Use stored fold length to compute new_end
25606 let new_start = find_fingerprint(&sfp, search_start)?;
25607 let fold_len = stored_end - stored_start;
25608 let new_end = new_start + fold_len;
25609 (new_start, new_end)
25610 } else {
25611 // Slow path: search for fingerprints in buffer
25612 let new_start = find_fingerprint(&sfp, search_start)?;
25613 // Search for end_fp after start, then add efp_len to get actual fold end
25614 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25615 let new_end = efp_pos + efp_len;
25616 (new_start, new_end)
25617 };
25618
25619 // Advance search position for next fold
25620 search_start = new_end;
25621
25622 // Validate fold makes sense (end must be after start)
25623 if new_end <= new_start {
25624 return None;
25625 }
25626
25627 // Collect for migration if needed
25628 if needs_migration {
25629 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25630 }
25631
25632 Some(
25633 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25634 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25635 )
25636 })
25637 .collect();
25638
25639 if !valid_folds.is_empty() {
25640 self.fold_ranges(valid_folds, false, window, cx);
25641
25642 // Migrate from editor_folds to file_folds if we loaded from old table
25643 if needs_migration {
25644 if let Some(ref path) = file_path {
25645 let path = path.clone();
25646 let db = EditorDb::global(cx);
25647 cx.spawn(async move |_, _| {
25648 db.save_file_folds(workspace_id, path, db_folds_for_migration)
25649 .await
25650 .log_err();
25651 })
25652 .detach();
25653 }
25654 }
25655 }
25656 }
25657
25658 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
25659 && !selections.is_empty()
25660 {
25661 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25662 // skip adding the initial selection to selection history
25663 self.selection_history.mode = SelectionHistoryMode::Skipping;
25664 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25665 s.select_ranges(selections.into_iter().map(|(start, end)| {
25666 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25667 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25668 }));
25669 });
25670 self.selection_history.mode = SelectionHistoryMode::Normal;
25671 };
25672 }
25673
25674 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25675 }
25676
25677 /// Load folds from the file_folds database table by file path.
25678 /// Used when manually opening a file that was previously closed.
25679 fn load_folds_from_db(
25680 &mut self,
25681 workspace_id: WorkspaceId,
25682 file_path: PathBuf,
25683 window: &mut Window,
25684 cx: &mut Context<Editor>,
25685 ) {
25686 if self.mode.is_minimap()
25687 || WorkspaceSettings::get(None, cx).restore_on_startup
25688 == RestoreOnStartupBehavior::EmptyTab
25689 {
25690 return;
25691 }
25692
25693 let Some(folds) = EditorDb::global(cx)
25694 .get_file_folds(workspace_id, &file_path)
25695 .log_err()
25696 else {
25697 return;
25698 };
25699 if folds.is_empty() {
25700 return;
25701 }
25702
25703 let snapshot = self.buffer.read(cx).snapshot(cx);
25704 let snapshot_len = snapshot.len().0;
25705
25706 // Helper: search for fingerprint in buffer, return offset if found
25707 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25708 let search_start = snapshot
25709 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25710 .0;
25711 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25712
25713 let mut byte_offset = search_start;
25714 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25715 if byte_offset > search_end {
25716 break;
25717 }
25718 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25719 return Some(byte_offset);
25720 }
25721 byte_offset += ch.len_utf8();
25722 }
25723 None
25724 };
25725
25726 let mut search_start = 0usize;
25727
25728 let valid_folds: Vec<_> = folds
25729 .into_iter()
25730 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25731 let sfp = start_fp?;
25732 let efp = end_fp?;
25733 let efp_len = efp.len();
25734
25735 let start_matches = stored_start < snapshot_len
25736 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25737 let efp_check_pos = stored_end.saturating_sub(efp_len);
25738 let end_matches = efp_check_pos >= stored_start
25739 && stored_end <= snapshot_len
25740 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25741
25742 let (new_start, new_end) = if start_matches && end_matches {
25743 (stored_start, stored_end)
25744 } else if sfp == efp {
25745 let new_start = find_fingerprint(&sfp, search_start)?;
25746 let fold_len = stored_end - stored_start;
25747 let new_end = new_start + fold_len;
25748 (new_start, new_end)
25749 } else {
25750 let new_start = find_fingerprint(&sfp, search_start)?;
25751 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25752 let new_end = efp_pos + efp_len;
25753 (new_start, new_end)
25754 };
25755
25756 search_start = new_end;
25757
25758 if new_end <= new_start {
25759 return None;
25760 }
25761
25762 Some(
25763 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25764 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25765 )
25766 })
25767 .collect();
25768
25769 if !valid_folds.is_empty() {
25770 self.fold_ranges(valid_folds, false, window, cx);
25771 }
25772 }
25773
25774 fn lsp_data_enabled(&self) -> bool {
25775 self.enable_lsp_data && self.mode().is_full()
25776 }
25777
25778 fn update_lsp_data(
25779 &mut self,
25780 for_buffer: Option<BufferId>,
25781 window: &mut Window,
25782 cx: &mut Context<'_, Self>,
25783 ) {
25784 if !self.lsp_data_enabled() {
25785 return;
25786 }
25787
25788 if let Some(buffer_id) = for_buffer {
25789 self.pull_diagnostics(buffer_id, window, cx);
25790 }
25791 self.refresh_semantic_tokens(for_buffer, None, cx);
25792 self.refresh_document_colors(for_buffer, window, cx);
25793 self.refresh_folding_ranges(for_buffer, window, cx);
25794 self.refresh_document_symbols(for_buffer, cx);
25795 }
25796
25797 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25798 if !self.lsp_data_enabled() {
25799 return;
25800 }
25801 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25802 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25803 }
25804 }
25805
25806 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25807 if !self.lsp_data_enabled() {
25808 return;
25809 }
25810
25811 if !self.registered_buffers.contains_key(&buffer_id)
25812 && let Some(project) = self.project.as_ref()
25813 {
25814 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25815 project.update(cx, |project, cx| {
25816 self.registered_buffers.insert(
25817 buffer_id,
25818 project.register_buffer_with_language_servers(&buffer, cx),
25819 );
25820 });
25821 } else {
25822 self.registered_buffers.remove(&buffer_id);
25823 }
25824 }
25825 }
25826
25827 fn create_style(&self, cx: &App) -> EditorStyle {
25828 let settings = ThemeSettings::get_global(cx);
25829
25830 let mut text_style = match self.mode {
25831 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25832 color: cx.theme().colors().editor_foreground,
25833 font_family: settings.ui_font.family.clone(),
25834 font_features: settings.ui_font.features.clone(),
25835 font_fallbacks: settings.ui_font.fallbacks.clone(),
25836 font_size: rems(0.875).into(),
25837 font_weight: settings.ui_font.weight,
25838 line_height: relative(settings.buffer_line_height.value()),
25839 ..Default::default()
25840 },
25841 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25842 color: cx.theme().colors().editor_foreground,
25843 font_family: settings.buffer_font.family.clone(),
25844 font_features: settings.buffer_font.features.clone(),
25845 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25846 font_size: settings.buffer_font_size(cx).into(),
25847 font_weight: settings.buffer_font.weight,
25848 line_height: relative(settings.buffer_line_height.value()),
25849 ..Default::default()
25850 },
25851 };
25852 if let Some(text_style_refinement) = &self.text_style_refinement {
25853 text_style.refine(text_style_refinement)
25854 }
25855
25856 let background = match self.mode {
25857 EditorMode::SingleLine => cx.theme().system().transparent,
25858 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25859 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25860 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25861 };
25862
25863 EditorStyle {
25864 background,
25865 border: cx.theme().colors().border,
25866 local_player: cx.theme().players().local(),
25867 text: text_style,
25868 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25869 syntax: cx.theme().syntax().clone(),
25870 status: cx.theme().status().clone(),
25871 inlay_hints_style: make_inlay_hints_style(cx),
25872 edit_prediction_styles: make_suggestion_styles(cx),
25873 unnecessary_code_fade: settings.unnecessary_code_fade,
25874 show_underlines: self.diagnostics_enabled(),
25875 }
25876 }
25877
25878 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
25879 let multibuffer = self.buffer().read(cx);
25880 let is_singleton = multibuffer.is_singleton();
25881 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25882 let buffer = multibuffer.buffer(*buffer_id)?;
25883
25884 let buffer = buffer.read(cx);
25885 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25886 let mut breadcrumbs = if is_singleton {
25887 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25888 buffer
25889 .snapshot()
25890 .resolve_file_path(
25891 self.project
25892 .as_ref()
25893 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25894 .unwrap_or_default(),
25895 cx,
25896 )
25897 .unwrap_or_else(|| {
25898 if multibuffer.is_singleton() {
25899 multibuffer.title(cx).to_string()
25900 } else {
25901 "untitled".to_string()
25902 }
25903 })
25904 });
25905 vec![HighlightedText {
25906 text: text.into(),
25907 highlights: vec![],
25908 }]
25909 } else {
25910 vec![]
25911 };
25912
25913 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
25914 text: symbol.text.clone().into(),
25915 highlights: symbol.highlight_ranges.clone(),
25916 }));
25917 Some(breadcrumbs)
25918 }
25919
25920 fn disable_lsp_data(&mut self) {
25921 self.enable_lsp_data = false;
25922 }
25923
25924 fn disable_runnables(&mut self) {
25925 self.enable_runnables = false;
25926 }
25927
25928 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
25929 self.register_visible_buffers(cx);
25930 self.colorize_brackets(false, cx);
25931 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
25932 if !self.buffer().read(cx).is_singleton() {
25933 self.update_lsp_data(None, window, cx);
25934 self.refresh_runnables(None, window, cx);
25935 }
25936 }
25937}
25938
25939fn edit_for_markdown_paste<'a>(
25940 buffer: &MultiBufferSnapshot,
25941 range: Range<MultiBufferOffset>,
25942 to_insert: &'a str,
25943 url: Option<url::Url>,
25944) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25945 if url.is_none() {
25946 return (range, Cow::Borrowed(to_insert));
25947 };
25948
25949 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25950
25951 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25952 Cow::Borrowed(to_insert)
25953 } else {
25954 Cow::Owned(format!("[{old_text}]({to_insert})"))
25955 };
25956 (range, new_text)
25957}
25958
25959fn process_completion_for_edit(
25960 completion: &Completion,
25961 intent: CompletionIntent,
25962 buffer: &Entity<Buffer>,
25963 cursor_position: &text::Anchor,
25964 cx: &mut Context<Editor>,
25965) -> CompletionEdit {
25966 let buffer = buffer.read(cx);
25967 let buffer_snapshot = buffer.snapshot();
25968 let (snippet, new_text) = if completion.is_snippet() {
25969 let mut snippet_source = completion.new_text.clone();
25970 // Workaround for typescript language server issues so that methods don't expand within
25971 // strings and functions with type expressions. The previous point is used because the query
25972 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25973 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25974 let previous_point = if previous_point.column > 0 {
25975 cursor_position.to_previous_offset(&buffer_snapshot)
25976 } else {
25977 cursor_position.to_offset(&buffer_snapshot)
25978 };
25979 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25980 && scope.prefers_label_for_snippet_in_completion()
25981 && let Some(label) = completion.label()
25982 && matches!(
25983 completion.kind(),
25984 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25985 )
25986 {
25987 snippet_source = label;
25988 }
25989 match Snippet::parse(&snippet_source).log_err() {
25990 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25991 None => (None, completion.new_text.clone()),
25992 }
25993 } else {
25994 (None, completion.new_text.clone())
25995 };
25996
25997 let mut range_to_replace = {
25998 let replace_range = &completion.replace_range;
25999 if let CompletionSource::Lsp {
26000 insert_range: Some(insert_range),
26001 ..
26002 } = &completion.source
26003 {
26004 debug_assert_eq!(
26005 insert_range.start, replace_range.start,
26006 "insert_range and replace_range should start at the same position"
26007 );
26008 debug_assert!(
26009 insert_range
26010 .start
26011 .cmp(cursor_position, &buffer_snapshot)
26012 .is_le(),
26013 "insert_range should start before or at cursor position"
26014 );
26015 debug_assert!(
26016 replace_range
26017 .start
26018 .cmp(cursor_position, &buffer_snapshot)
26019 .is_le(),
26020 "replace_range should start before or at cursor position"
26021 );
26022
26023 let should_replace = match intent {
26024 CompletionIntent::CompleteWithInsert => false,
26025 CompletionIntent::CompleteWithReplace => true,
26026 CompletionIntent::Complete | CompletionIntent::Compose => {
26027 let insert_mode = LanguageSettings::for_buffer(&buffer, cx)
26028 .completions
26029 .lsp_insert_mode;
26030 match insert_mode {
26031 LspInsertMode::Insert => false,
26032 LspInsertMode::Replace => true,
26033 LspInsertMode::ReplaceSubsequence => {
26034 let mut text_to_replace = buffer.chars_for_range(
26035 buffer.anchor_before(replace_range.start)
26036 ..buffer.anchor_after(replace_range.end),
26037 );
26038 let mut current_needle = text_to_replace.next();
26039 for haystack_ch in completion.label.text.chars() {
26040 if let Some(needle_ch) = current_needle
26041 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
26042 {
26043 current_needle = text_to_replace.next();
26044 }
26045 }
26046 current_needle.is_none()
26047 }
26048 LspInsertMode::ReplaceSuffix => {
26049 if replace_range
26050 .end
26051 .cmp(cursor_position, &buffer_snapshot)
26052 .is_gt()
26053 {
26054 let range_after_cursor = *cursor_position..replace_range.end;
26055 let text_after_cursor = buffer
26056 .text_for_range(
26057 buffer.anchor_before(range_after_cursor.start)
26058 ..buffer.anchor_after(range_after_cursor.end),
26059 )
26060 .collect::<String>()
26061 .to_ascii_lowercase();
26062 completion
26063 .label
26064 .text
26065 .to_ascii_lowercase()
26066 .ends_with(&text_after_cursor)
26067 } else {
26068 true
26069 }
26070 }
26071 }
26072 }
26073 };
26074
26075 if should_replace {
26076 replace_range.clone()
26077 } else {
26078 insert_range.clone()
26079 }
26080 } else {
26081 replace_range.clone()
26082 }
26083 };
26084
26085 if range_to_replace
26086 .end
26087 .cmp(cursor_position, &buffer_snapshot)
26088 .is_lt()
26089 {
26090 range_to_replace.end = *cursor_position;
26091 }
26092
26093 let replace_range = range_to_replace.to_offset(buffer);
26094 CompletionEdit {
26095 new_text,
26096 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
26097 snippet,
26098 }
26099}
26100
26101struct CompletionEdit {
26102 new_text: String,
26103 replace_range: Range<BufferOffset>,
26104 snippet: Option<Snippet>,
26105}
26106
26107fn comment_delimiter_for_newline(
26108 start_point: &Point,
26109 buffer: &MultiBufferSnapshot,
26110 language: &LanguageScope,
26111) -> Option<Arc<str>> {
26112 let delimiters = language.line_comment_prefixes();
26113 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
26114 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26115
26116 let num_of_whitespaces = snapshot
26117 .chars_for_range(range.clone())
26118 .take_while(|c| c.is_whitespace())
26119 .count();
26120 let comment_candidate = snapshot
26121 .chars_for_range(range.clone())
26122 .skip(num_of_whitespaces)
26123 .take(max_len_of_delimiter + 2)
26124 .collect::<String>();
26125 let (delimiter, trimmed_len, is_repl) = delimiters
26126 .iter()
26127 .filter_map(|delimiter| {
26128 let prefix = delimiter.trim_end();
26129 if comment_candidate.starts_with(prefix) {
26130 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
26131 {
26132 stripped_comment.starts_with(" %%")
26133 } else {
26134 false
26135 };
26136 Some((delimiter, prefix.len(), is_repl))
26137 } else {
26138 None
26139 }
26140 })
26141 .max_by_key(|(_, len, _)| *len)?;
26142
26143 if let Some(BlockCommentConfig {
26144 start: block_start, ..
26145 }) = language.block_comment()
26146 {
26147 let block_start_trimmed = block_start.trim_end();
26148 if block_start_trimmed.starts_with(delimiter.trim_end()) {
26149 let line_content = snapshot
26150 .chars_for_range(range.clone())
26151 .skip(num_of_whitespaces)
26152 .take(block_start_trimmed.len())
26153 .collect::<String>();
26154
26155 if line_content.starts_with(block_start_trimmed) {
26156 return None;
26157 }
26158 }
26159 }
26160
26161 let cursor_is_placed_after_comment_marker =
26162 num_of_whitespaces + trimmed_len <= start_point.column as usize;
26163 if cursor_is_placed_after_comment_marker {
26164 if !is_repl {
26165 return Some(delimiter.clone());
26166 }
26167
26168 let line_content_after_cursor: String = snapshot
26169 .chars_for_range(range)
26170 .skip(start_point.column as usize)
26171 .collect();
26172
26173 if line_content_after_cursor.trim().is_empty() {
26174 return None;
26175 } else {
26176 return Some(delimiter.clone());
26177 }
26178 } else {
26179 None
26180 }
26181}
26182
26183fn documentation_delimiter_for_newline(
26184 start_point: &Point,
26185 buffer: &MultiBufferSnapshot,
26186 language: &LanguageScope,
26187 newline_config: &mut NewlineConfig,
26188) -> Option<Arc<str>> {
26189 let BlockCommentConfig {
26190 start: start_tag,
26191 end: end_tag,
26192 prefix: delimiter,
26193 tab_size: len,
26194 } = language.documentation_comment()?;
26195 let is_within_block_comment = buffer
26196 .language_scope_at(*start_point)
26197 .is_some_and(|scope| scope.override_name() == Some("comment"));
26198 if !is_within_block_comment {
26199 return None;
26200 }
26201
26202 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26203
26204 let num_of_whitespaces = snapshot
26205 .chars_for_range(range.clone())
26206 .take_while(|c| c.is_whitespace())
26207 .count();
26208
26209 // 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.
26210 let column = start_point.column;
26211 let cursor_is_after_start_tag = {
26212 let start_tag_len = start_tag.len();
26213 let start_tag_line = snapshot
26214 .chars_for_range(range.clone())
26215 .skip(num_of_whitespaces)
26216 .take(start_tag_len)
26217 .collect::<String>();
26218 if start_tag_line.starts_with(start_tag.as_ref()) {
26219 num_of_whitespaces + start_tag_len <= column as usize
26220 } else {
26221 false
26222 }
26223 };
26224
26225 let cursor_is_after_delimiter = {
26226 let delimiter_trim = delimiter.trim_end();
26227 let delimiter_line = snapshot
26228 .chars_for_range(range.clone())
26229 .skip(num_of_whitespaces)
26230 .take(delimiter_trim.len())
26231 .collect::<String>();
26232 if delimiter_line.starts_with(delimiter_trim) {
26233 num_of_whitespaces + delimiter_trim.len() <= column as usize
26234 } else {
26235 false
26236 }
26237 };
26238
26239 let mut needs_extra_line = false;
26240 let mut extra_line_additional_indent = IndentSize::spaces(0);
26241
26242 let cursor_is_before_end_tag_if_exists = {
26243 let mut char_position = 0u32;
26244 let mut end_tag_offset = None;
26245
26246 'outer: for chunk in snapshot.text_for_range(range) {
26247 if let Some(byte_pos) = chunk.find(&**end_tag) {
26248 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26249 end_tag_offset = Some(char_position + chars_before_match);
26250 break 'outer;
26251 }
26252 char_position += chunk.chars().count() as u32;
26253 }
26254
26255 if let Some(end_tag_offset) = end_tag_offset {
26256 let cursor_is_before_end_tag = column <= end_tag_offset;
26257 if cursor_is_after_start_tag {
26258 if cursor_is_before_end_tag {
26259 needs_extra_line = true;
26260 }
26261 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26262 if cursor_is_at_start_of_end_tag {
26263 extra_line_additional_indent.len = *len;
26264 }
26265 }
26266 cursor_is_before_end_tag
26267 } else {
26268 true
26269 }
26270 };
26271
26272 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26273 && cursor_is_before_end_tag_if_exists
26274 {
26275 let additional_indent = if cursor_is_after_start_tag {
26276 IndentSize::spaces(*len)
26277 } else {
26278 IndentSize::spaces(0)
26279 };
26280
26281 *newline_config = NewlineConfig::Newline {
26282 additional_indent,
26283 extra_line_additional_indent: if needs_extra_line {
26284 Some(extra_line_additional_indent)
26285 } else {
26286 None
26287 },
26288 prevent_auto_indent: true,
26289 };
26290 Some(delimiter.clone())
26291 } else {
26292 None
26293 }
26294}
26295
26296const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26297
26298fn list_delimiter_for_newline(
26299 start_point: &Point,
26300 buffer: &MultiBufferSnapshot,
26301 language: &LanguageScope,
26302 newline_config: &mut NewlineConfig,
26303) -> Option<Arc<str>> {
26304 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26305
26306 let num_of_whitespaces = snapshot
26307 .chars_for_range(range.clone())
26308 .take_while(|c| c.is_whitespace())
26309 .count();
26310
26311 let task_list_entries: Vec<_> = language
26312 .task_list()
26313 .into_iter()
26314 .flat_map(|config| {
26315 config
26316 .prefixes
26317 .iter()
26318 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26319 })
26320 .collect();
26321 let unordered_list_entries: Vec<_> = language
26322 .unordered_list()
26323 .iter()
26324 .map(|marker| (marker.as_ref(), marker.as_ref()))
26325 .collect();
26326
26327 let all_entries: Vec<_> = task_list_entries
26328 .into_iter()
26329 .chain(unordered_list_entries)
26330 .collect();
26331
26332 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26333 let candidate: String = snapshot
26334 .chars_for_range(range.clone())
26335 .skip(num_of_whitespaces)
26336 .take(max_prefix_len)
26337 .collect();
26338
26339 if let Some((prefix, continuation)) = all_entries
26340 .iter()
26341 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26342 .max_by_key(|(prefix, _)| prefix.len())
26343 {
26344 let end_of_prefix = num_of_whitespaces + prefix.len();
26345 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26346 let has_content_after_marker = snapshot
26347 .chars_for_range(range)
26348 .skip(end_of_prefix)
26349 .any(|c| !c.is_whitespace());
26350
26351 if has_content_after_marker && cursor_is_after_prefix {
26352 return Some((*continuation).into());
26353 }
26354
26355 if start_point.column as usize == end_of_prefix {
26356 if num_of_whitespaces == 0 {
26357 *newline_config = NewlineConfig::ClearCurrentLine;
26358 } else {
26359 *newline_config = NewlineConfig::UnindentCurrentLine {
26360 continuation: (*continuation).into(),
26361 };
26362 }
26363 }
26364
26365 return None;
26366 }
26367 }
26368
26369 let candidate: String = snapshot
26370 .chars_for_range(range.clone())
26371 .skip(num_of_whitespaces)
26372 .take(ORDERED_LIST_MAX_MARKER_LEN)
26373 .collect();
26374
26375 for ordered_config in language.ordered_list() {
26376 let regex = match Regex::new(&ordered_config.pattern) {
26377 Ok(r) => r,
26378 Err(_) => continue,
26379 };
26380
26381 if let Some(captures) = regex.captures(&candidate) {
26382 let full_match = captures.get(0)?;
26383 let marker_len = full_match.len();
26384 let end_of_prefix = num_of_whitespaces + marker_len;
26385 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26386
26387 let has_content_after_marker = snapshot
26388 .chars_for_range(range)
26389 .skip(end_of_prefix)
26390 .any(|c| !c.is_whitespace());
26391
26392 if has_content_after_marker && cursor_is_after_prefix {
26393 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26394 let continuation = ordered_config
26395 .format
26396 .replace("{1}", &(number + 1).to_string());
26397 return Some(continuation.into());
26398 }
26399
26400 if start_point.column as usize == end_of_prefix {
26401 let continuation = ordered_config.format.replace("{1}", "1");
26402 if num_of_whitespaces == 0 {
26403 *newline_config = NewlineConfig::ClearCurrentLine;
26404 } else {
26405 *newline_config = NewlineConfig::UnindentCurrentLine {
26406 continuation: continuation.into(),
26407 };
26408 }
26409 }
26410
26411 return None;
26412 }
26413 }
26414
26415 None
26416}
26417
26418fn is_list_prefix_row(
26419 row: MultiBufferRow,
26420 buffer: &MultiBufferSnapshot,
26421 language: &LanguageScope,
26422) -> bool {
26423 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26424 return false;
26425 };
26426
26427 let num_of_whitespaces = snapshot
26428 .chars_for_range(range.clone())
26429 .take_while(|c| c.is_whitespace())
26430 .count();
26431
26432 let task_list_prefixes: Vec<_> = language
26433 .task_list()
26434 .into_iter()
26435 .flat_map(|config| {
26436 config
26437 .prefixes
26438 .iter()
26439 .map(|p| p.as_ref())
26440 .collect::<Vec<_>>()
26441 })
26442 .collect();
26443 let unordered_list_markers: Vec<_> = language
26444 .unordered_list()
26445 .iter()
26446 .map(|marker| marker.as_ref())
26447 .collect();
26448 let all_prefixes: Vec<_> = task_list_prefixes
26449 .into_iter()
26450 .chain(unordered_list_markers)
26451 .collect();
26452 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26453 let candidate: String = snapshot
26454 .chars_for_range(range.clone())
26455 .skip(num_of_whitespaces)
26456 .take(max_prefix_len)
26457 .collect();
26458 if all_prefixes
26459 .iter()
26460 .any(|prefix| candidate.starts_with(*prefix))
26461 {
26462 return true;
26463 }
26464 }
26465
26466 let ordered_list_candidate: String = snapshot
26467 .chars_for_range(range)
26468 .skip(num_of_whitespaces)
26469 .take(ORDERED_LIST_MAX_MARKER_LEN)
26470 .collect();
26471 for ordered_config in language.ordered_list() {
26472 let regex = match Regex::new(&ordered_config.pattern) {
26473 Ok(r) => r,
26474 Err(_) => continue,
26475 };
26476 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26477 return captures.get(0).is_some();
26478 }
26479 }
26480
26481 false
26482}
26483
26484#[derive(Debug)]
26485enum NewlineConfig {
26486 /// Insert newline with optional additional indent and optional extra blank line
26487 Newline {
26488 additional_indent: IndentSize,
26489 extra_line_additional_indent: Option<IndentSize>,
26490 prevent_auto_indent: bool,
26491 },
26492 /// Clear the current line
26493 ClearCurrentLine,
26494 /// Unindent the current line and add continuation
26495 UnindentCurrentLine { continuation: Arc<str> },
26496}
26497
26498impl NewlineConfig {
26499 fn has_extra_line(&self) -> bool {
26500 matches!(
26501 self,
26502 Self::Newline {
26503 extra_line_additional_indent: Some(_),
26504 ..
26505 }
26506 )
26507 }
26508
26509 fn insert_extra_newline_brackets(
26510 buffer: &MultiBufferSnapshot,
26511 range: Range<MultiBufferOffset>,
26512 language: &language::LanguageScope,
26513 ) -> bool {
26514 let leading_whitespace_len = buffer
26515 .reversed_chars_at(range.start)
26516 .take_while(|c| c.is_whitespace() && *c != '\n')
26517 .map(|c| c.len_utf8())
26518 .sum::<usize>();
26519 let trailing_whitespace_len = buffer
26520 .chars_at(range.end)
26521 .take_while(|c| c.is_whitespace() && *c != '\n')
26522 .map(|c| c.len_utf8())
26523 .sum::<usize>();
26524 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26525
26526 language.brackets().any(|(pair, enabled)| {
26527 let pair_start = pair.start.trim_end();
26528 let pair_end = pair.end.trim_start();
26529
26530 enabled
26531 && pair.newline
26532 && buffer.contains_str_at(range.end, pair_end)
26533 && buffer.contains_str_at(
26534 range.start.saturating_sub_usize(pair_start.len()),
26535 pair_start,
26536 )
26537 })
26538 }
26539
26540 fn insert_extra_newline_tree_sitter(
26541 buffer: &MultiBufferSnapshot,
26542 range: Range<MultiBufferOffset>,
26543 ) -> bool {
26544 let (buffer, range) = match buffer
26545 .range_to_buffer_ranges(range.start..=range.end)
26546 .as_slice()
26547 {
26548 [(buffer, range, _)] => (*buffer, range.clone()),
26549 _ => return false,
26550 };
26551 let pair = {
26552 let mut result: Option<BracketMatch<usize>> = None;
26553
26554 for pair in buffer
26555 .all_bracket_ranges(range.start.0..range.end.0)
26556 .filter(move |pair| {
26557 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26558 })
26559 {
26560 let len = pair.close_range.end - pair.open_range.start;
26561
26562 if let Some(existing) = &result {
26563 let existing_len = existing.close_range.end - existing.open_range.start;
26564 if len > existing_len {
26565 continue;
26566 }
26567 }
26568
26569 result = Some(pair);
26570 }
26571
26572 result
26573 };
26574 let Some(pair) = pair else {
26575 return false;
26576 };
26577 pair.newline_only
26578 && buffer
26579 .chars_for_range(pair.open_range.end..range.start.0)
26580 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26581 .all(|c| c.is_whitespace() && c != '\n')
26582 }
26583}
26584
26585fn update_uncommitted_diff_for_buffer(
26586 editor: Entity<Editor>,
26587 project: &Entity<Project>,
26588 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26589 buffer: Entity<MultiBuffer>,
26590 cx: &mut App,
26591) -> Task<()> {
26592 let mut tasks = Vec::new();
26593 project.update(cx, |project, cx| {
26594 for buffer in buffers {
26595 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26596 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26597 }
26598 }
26599 });
26600 cx.spawn(async move |cx| {
26601 let diffs = future::join_all(tasks).await;
26602 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26603 return;
26604 }
26605
26606 buffer.update(cx, |buffer, cx| {
26607 for diff in diffs.into_iter().flatten() {
26608 buffer.add_diff(diff, cx);
26609 }
26610 });
26611 })
26612}
26613
26614fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26615 let tab_size = tab_size.get() as usize;
26616 let mut width = offset;
26617
26618 for ch in text.chars() {
26619 width += if ch == '\t' {
26620 tab_size - (width % tab_size)
26621 } else {
26622 1
26623 };
26624 }
26625
26626 width - offset
26627}
26628
26629#[cfg(test)]
26630mod tests {
26631 use super::*;
26632
26633 #[test]
26634 fn test_string_size_with_expanded_tabs() {
26635 let nz = |val| NonZeroU32::new(val).unwrap();
26636 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26637 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26638 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26639 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26640 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26641 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26642 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26643 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26644 }
26645}
26646
26647/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26648struct WordBreakingTokenizer<'a> {
26649 input: &'a str,
26650}
26651
26652impl<'a> WordBreakingTokenizer<'a> {
26653 fn new(input: &'a str) -> Self {
26654 Self { input }
26655 }
26656}
26657
26658fn is_char_ideographic(ch: char) -> bool {
26659 use unicode_script::Script::*;
26660 use unicode_script::UnicodeScript;
26661 matches!(ch.script(), Han | Tangut | Yi)
26662}
26663
26664fn is_grapheme_ideographic(text: &str) -> bool {
26665 text.chars().any(is_char_ideographic)
26666}
26667
26668fn is_grapheme_whitespace(text: &str) -> bool {
26669 text.chars().any(|x| x.is_whitespace())
26670}
26671
26672fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26673 text.chars()
26674 .next()
26675 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26676}
26677
26678#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26679enum WordBreakToken<'a> {
26680 Word { token: &'a str, grapheme_len: usize },
26681 InlineWhitespace { token: &'a str, grapheme_len: usize },
26682 Newline,
26683}
26684
26685impl<'a> Iterator for WordBreakingTokenizer<'a> {
26686 /// Yields a span, the count of graphemes in the token, and whether it was
26687 /// whitespace. Note that it also breaks at word boundaries.
26688 type Item = WordBreakToken<'a>;
26689
26690 fn next(&mut self) -> Option<Self::Item> {
26691 use unicode_segmentation::UnicodeSegmentation;
26692 if self.input.is_empty() {
26693 return None;
26694 }
26695
26696 let mut iter = self.input.graphemes(true).peekable();
26697 let mut offset = 0;
26698 let mut grapheme_len = 0;
26699 if let Some(first_grapheme) = iter.next() {
26700 let is_newline = first_grapheme == "\n";
26701 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26702 offset += first_grapheme.len();
26703 grapheme_len += 1;
26704 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26705 if let Some(grapheme) = iter.peek().copied()
26706 && should_stay_with_preceding_ideograph(grapheme)
26707 {
26708 offset += grapheme.len();
26709 grapheme_len += 1;
26710 }
26711 } else {
26712 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26713 let mut next_word_bound = words.peek().copied();
26714 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26715 next_word_bound = words.next();
26716 }
26717 while let Some(grapheme) = iter.peek().copied() {
26718 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26719 break;
26720 };
26721 if is_grapheme_whitespace(grapheme) != is_whitespace
26722 || (grapheme == "\n") != is_newline
26723 {
26724 break;
26725 };
26726 offset += grapheme.len();
26727 grapheme_len += 1;
26728 iter.next();
26729 }
26730 }
26731 let token = &self.input[..offset];
26732 self.input = &self.input[offset..];
26733 if token == "\n" {
26734 Some(WordBreakToken::Newline)
26735 } else if is_whitespace {
26736 Some(WordBreakToken::InlineWhitespace {
26737 token,
26738 grapheme_len,
26739 })
26740 } else {
26741 Some(WordBreakToken::Word {
26742 token,
26743 grapheme_len,
26744 })
26745 }
26746 } else {
26747 None
26748 }
26749 }
26750}
26751
26752#[test]
26753fn test_word_breaking_tokenizer() {
26754 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26755 ("", &[]),
26756 (" ", &[whitespace(" ", 2)]),
26757 ("Ʒ", &[word("Ʒ", 1)]),
26758 ("Ǽ", &[word("Ǽ", 1)]),
26759 ("⋑", &[word("⋑", 1)]),
26760 ("⋑⋑", &[word("⋑⋑", 2)]),
26761 (
26762 "原理,进而",
26763 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26764 ),
26765 (
26766 "hello world",
26767 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26768 ),
26769 (
26770 "hello, world",
26771 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26772 ),
26773 (
26774 " hello world",
26775 &[
26776 whitespace(" ", 2),
26777 word("hello", 5),
26778 whitespace(" ", 1),
26779 word("world", 5),
26780 ],
26781 ),
26782 (
26783 "这是什么 \n 钢笔",
26784 &[
26785 word("这", 1),
26786 word("是", 1),
26787 word("什", 1),
26788 word("么", 1),
26789 whitespace(" ", 1),
26790 newline(),
26791 whitespace(" ", 1),
26792 word("钢", 1),
26793 word("笔", 1),
26794 ],
26795 ),
26796 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26797 ];
26798
26799 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26800 WordBreakToken::Word {
26801 token,
26802 grapheme_len,
26803 }
26804 }
26805
26806 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26807 WordBreakToken::InlineWhitespace {
26808 token,
26809 grapheme_len,
26810 }
26811 }
26812
26813 fn newline() -> WordBreakToken<'static> {
26814 WordBreakToken::Newline
26815 }
26816
26817 for (input, result) in tests {
26818 assert_eq!(
26819 WordBreakingTokenizer::new(input)
26820 .collect::<Vec<_>>()
26821 .as_slice(),
26822 *result,
26823 );
26824 }
26825}
26826
26827fn wrap_with_prefix(
26828 first_line_prefix: String,
26829 subsequent_lines_prefix: String,
26830 unwrapped_text: String,
26831 wrap_column: usize,
26832 tab_size: NonZeroU32,
26833 preserve_existing_whitespace: bool,
26834) -> String {
26835 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26836 let subsequent_lines_prefix_len =
26837 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26838 let mut wrapped_text = String::new();
26839 let mut current_line = first_line_prefix;
26840 let mut is_first_line = true;
26841
26842 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26843 let mut current_line_len = first_line_prefix_len;
26844 let mut in_whitespace = false;
26845 for token in tokenizer {
26846 let have_preceding_whitespace = in_whitespace;
26847 match token {
26848 WordBreakToken::Word {
26849 token,
26850 grapheme_len,
26851 } => {
26852 in_whitespace = false;
26853 let current_prefix_len = if is_first_line {
26854 first_line_prefix_len
26855 } else {
26856 subsequent_lines_prefix_len
26857 };
26858 if current_line_len + grapheme_len > wrap_column
26859 && current_line_len != current_prefix_len
26860 {
26861 wrapped_text.push_str(current_line.trim_end());
26862 wrapped_text.push('\n');
26863 is_first_line = false;
26864 current_line = subsequent_lines_prefix.clone();
26865 current_line_len = subsequent_lines_prefix_len;
26866 }
26867 current_line.push_str(token);
26868 current_line_len += grapheme_len;
26869 }
26870 WordBreakToken::InlineWhitespace {
26871 mut token,
26872 mut grapheme_len,
26873 } => {
26874 in_whitespace = true;
26875 if have_preceding_whitespace && !preserve_existing_whitespace {
26876 continue;
26877 }
26878 if !preserve_existing_whitespace {
26879 // Keep a single whitespace grapheme as-is
26880 if let Some(first) =
26881 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26882 {
26883 token = first;
26884 } else {
26885 token = " ";
26886 }
26887 grapheme_len = 1;
26888 }
26889 let current_prefix_len = if is_first_line {
26890 first_line_prefix_len
26891 } else {
26892 subsequent_lines_prefix_len
26893 };
26894 if current_line_len + grapheme_len > wrap_column {
26895 wrapped_text.push_str(current_line.trim_end());
26896 wrapped_text.push('\n');
26897 is_first_line = false;
26898 current_line = subsequent_lines_prefix.clone();
26899 current_line_len = subsequent_lines_prefix_len;
26900 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26901 current_line.push_str(token);
26902 current_line_len += grapheme_len;
26903 }
26904 }
26905 WordBreakToken::Newline => {
26906 in_whitespace = true;
26907 let current_prefix_len = if is_first_line {
26908 first_line_prefix_len
26909 } else {
26910 subsequent_lines_prefix_len
26911 };
26912 if preserve_existing_whitespace {
26913 wrapped_text.push_str(current_line.trim_end());
26914 wrapped_text.push('\n');
26915 is_first_line = false;
26916 current_line = subsequent_lines_prefix.clone();
26917 current_line_len = subsequent_lines_prefix_len;
26918 } else if have_preceding_whitespace {
26919 continue;
26920 } else if current_line_len + 1 > wrap_column
26921 && current_line_len != current_prefix_len
26922 {
26923 wrapped_text.push_str(current_line.trim_end());
26924 wrapped_text.push('\n');
26925 is_first_line = false;
26926 current_line = subsequent_lines_prefix.clone();
26927 current_line_len = subsequent_lines_prefix_len;
26928 } else if current_line_len != current_prefix_len {
26929 current_line.push(' ');
26930 current_line_len += 1;
26931 }
26932 }
26933 }
26934 }
26935
26936 if !current_line.is_empty() {
26937 wrapped_text.push_str(¤t_line);
26938 }
26939 wrapped_text
26940}
26941
26942#[test]
26943fn test_wrap_with_prefix() {
26944 assert_eq!(
26945 wrap_with_prefix(
26946 "# ".to_string(),
26947 "# ".to_string(),
26948 "abcdefg".to_string(),
26949 4,
26950 NonZeroU32::new(4).unwrap(),
26951 false,
26952 ),
26953 "# abcdefg"
26954 );
26955 assert_eq!(
26956 wrap_with_prefix(
26957 "".to_string(),
26958 "".to_string(),
26959 "\thello world".to_string(),
26960 8,
26961 NonZeroU32::new(4).unwrap(),
26962 false,
26963 ),
26964 "hello\nworld"
26965 );
26966 assert_eq!(
26967 wrap_with_prefix(
26968 "// ".to_string(),
26969 "// ".to_string(),
26970 "xx \nyy zz aa bb cc".to_string(),
26971 12,
26972 NonZeroU32::new(4).unwrap(),
26973 false,
26974 ),
26975 "// xx yy zz\n// aa bb cc"
26976 );
26977 assert_eq!(
26978 wrap_with_prefix(
26979 String::new(),
26980 String::new(),
26981 "这是什么 \n 钢笔".to_string(),
26982 3,
26983 NonZeroU32::new(4).unwrap(),
26984 false,
26985 ),
26986 "这是什\n么 钢\n笔"
26987 );
26988 assert_eq!(
26989 wrap_with_prefix(
26990 String::new(),
26991 String::new(),
26992 format!("foo{}bar", '\u{2009}'), // thin space
26993 80,
26994 NonZeroU32::new(4).unwrap(),
26995 false,
26996 ),
26997 format!("foo{}bar", '\u{2009}')
26998 );
26999}
27000
27001pub trait CollaborationHub {
27002 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
27003 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
27004 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
27005}
27006
27007impl CollaborationHub for Entity<Project> {
27008 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
27009 self.read(cx).collaborators()
27010 }
27011
27012 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
27013 self.read(cx).user_store().read(cx).participant_indices()
27014 }
27015
27016 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
27017 let this = self.read(cx);
27018 let user_ids = this.collaborators().values().map(|c| c.user_id);
27019 this.user_store().read(cx).participant_names(user_ids, cx)
27020 }
27021}
27022
27023pub trait SemanticsProvider {
27024 fn hover(
27025 &self,
27026 buffer: &Entity<Buffer>,
27027 position: text::Anchor,
27028 cx: &mut App,
27029 ) -> Option<Task<Option<Vec<project::Hover>>>>;
27030
27031 fn inline_values(
27032 &self,
27033 buffer_handle: Entity<Buffer>,
27034 range: Range<text::Anchor>,
27035 cx: &mut App,
27036 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
27037
27038 fn applicable_inlay_chunks(
27039 &self,
27040 buffer: &Entity<Buffer>,
27041 ranges: &[Range<text::Anchor>],
27042 cx: &mut App,
27043 ) -> Vec<Range<BufferRow>>;
27044
27045 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
27046
27047 fn inlay_hints(
27048 &self,
27049 invalidate: InvalidationStrategy,
27050 buffer: Entity<Buffer>,
27051 ranges: Vec<Range<text::Anchor>>,
27052 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27053 cx: &mut App,
27054 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
27055
27056 fn semantic_tokens(
27057 &self,
27058 buffer: Entity<Buffer>,
27059 refresh: Option<RefreshForServer>,
27060 cx: &mut App,
27061 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
27062
27063 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27064
27065 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27066
27067 fn document_highlights(
27068 &self,
27069 buffer: &Entity<Buffer>,
27070 position: text::Anchor,
27071 cx: &mut App,
27072 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
27073
27074 fn definitions(
27075 &self,
27076 buffer: &Entity<Buffer>,
27077 position: text::Anchor,
27078 kind: GotoDefinitionKind,
27079 cx: &mut App,
27080 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
27081
27082 fn range_for_rename(
27083 &self,
27084 buffer: &Entity<Buffer>,
27085 position: text::Anchor,
27086 cx: &mut App,
27087 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
27088
27089 fn perform_rename(
27090 &self,
27091 buffer: &Entity<Buffer>,
27092 position: text::Anchor,
27093 new_name: String,
27094 cx: &mut App,
27095 ) -> Option<Task<Result<ProjectTransaction>>>;
27096}
27097
27098pub trait CompletionProvider {
27099 fn completions(
27100 &self,
27101 excerpt_id: ExcerptId,
27102 buffer: &Entity<Buffer>,
27103 buffer_position: text::Anchor,
27104 trigger: CompletionContext,
27105 window: &mut Window,
27106 cx: &mut Context<Editor>,
27107 ) -> Task<Result<Vec<CompletionResponse>>>;
27108
27109 fn resolve_completions(
27110 &self,
27111 _buffer: Entity<Buffer>,
27112 _completion_indices: Vec<usize>,
27113 _completions: Rc<RefCell<Box<[Completion]>>>,
27114 _cx: &mut Context<Editor>,
27115 ) -> Task<Result<bool>> {
27116 Task::ready(Ok(false))
27117 }
27118
27119 fn apply_additional_edits_for_completion(
27120 &self,
27121 _buffer: Entity<Buffer>,
27122 _completions: Rc<RefCell<Box<[Completion]>>>,
27123 _completion_index: usize,
27124 _push_to_history: bool,
27125 _all_commit_ranges: Vec<Range<language::Anchor>>,
27126 _cx: &mut Context<Editor>,
27127 ) -> Task<Result<Option<language::Transaction>>> {
27128 Task::ready(Ok(None))
27129 }
27130
27131 fn is_completion_trigger(
27132 &self,
27133 buffer: &Entity<Buffer>,
27134 position: language::Anchor,
27135 text: &str,
27136 trigger_in_words: bool,
27137 cx: &mut Context<Editor>,
27138 ) -> bool;
27139
27140 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
27141
27142 fn sort_completions(&self) -> bool {
27143 true
27144 }
27145
27146 fn filter_completions(&self) -> bool {
27147 true
27148 }
27149
27150 fn show_snippets(&self) -> bool {
27151 false
27152 }
27153}
27154
27155pub trait CodeActionProvider {
27156 fn id(&self) -> Arc<str>;
27157
27158 fn code_actions(
27159 &self,
27160 buffer: &Entity<Buffer>,
27161 range: Range<text::Anchor>,
27162 window: &mut Window,
27163 cx: &mut App,
27164 ) -> Task<Result<Vec<CodeAction>>>;
27165
27166 fn apply_code_action(
27167 &self,
27168 buffer_handle: Entity<Buffer>,
27169 action: CodeAction,
27170 excerpt_id: ExcerptId,
27171 push_to_history: bool,
27172 window: &mut Window,
27173 cx: &mut App,
27174 ) -> Task<Result<ProjectTransaction>>;
27175}
27176
27177impl CodeActionProvider for Entity<Project> {
27178 fn id(&self) -> Arc<str> {
27179 "project".into()
27180 }
27181
27182 fn code_actions(
27183 &self,
27184 buffer: &Entity<Buffer>,
27185 range: Range<text::Anchor>,
27186 _window: &mut Window,
27187 cx: &mut App,
27188 ) -> Task<Result<Vec<CodeAction>>> {
27189 self.update(cx, |project, cx| {
27190 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27191 let code_actions = project.code_actions(buffer, range, None, cx);
27192 cx.background_spawn(async move {
27193 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27194 Ok(code_lens_actions
27195 .context("code lens fetch")?
27196 .into_iter()
27197 .flatten()
27198 .chain(
27199 code_actions
27200 .context("code action fetch")?
27201 .into_iter()
27202 .flatten(),
27203 )
27204 .collect())
27205 })
27206 })
27207 }
27208
27209 fn apply_code_action(
27210 &self,
27211 buffer_handle: Entity<Buffer>,
27212 action: CodeAction,
27213 _excerpt_id: ExcerptId,
27214 push_to_history: bool,
27215 _window: &mut Window,
27216 cx: &mut App,
27217 ) -> Task<Result<ProjectTransaction>> {
27218 self.update(cx, |project, cx| {
27219 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27220 })
27221 }
27222}
27223
27224fn snippet_completions(
27225 project: &Project,
27226 buffer: &Entity<Buffer>,
27227 buffer_anchor: text::Anchor,
27228 classifier: CharClassifier,
27229 cx: &mut App,
27230) -> Task<Result<CompletionResponse>> {
27231 let languages = buffer.read(cx).languages_at(buffer_anchor);
27232 let snippet_store = project.snippets().read(cx);
27233
27234 let scopes: Vec<_> = languages
27235 .iter()
27236 .filter_map(|language| {
27237 let language_name = language.lsp_id();
27238 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27239
27240 if snippets.is_empty() {
27241 None
27242 } else {
27243 Some((language.default_scope(), snippets))
27244 }
27245 })
27246 .collect();
27247
27248 if scopes.is_empty() {
27249 return Task::ready(Ok(CompletionResponse {
27250 completions: vec![],
27251 display_options: CompletionDisplayOptions::default(),
27252 is_incomplete: false,
27253 }));
27254 }
27255
27256 let snapshot = buffer.read(cx).text_snapshot();
27257 let executor = cx.background_executor().clone();
27258
27259 cx.background_spawn(async move {
27260 let is_word_char = |c| classifier.is_word(c);
27261
27262 let mut is_incomplete = false;
27263 let mut completions: Vec<Completion> = Vec::new();
27264
27265 const MAX_PREFIX_LEN: usize = 128;
27266 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27267 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27268 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27269
27270 let max_buffer_window: String = snapshot
27271 .text_for_range(window_start..buffer_offset)
27272 .collect();
27273
27274 if max_buffer_window.is_empty() {
27275 return Ok(CompletionResponse {
27276 completions: vec![],
27277 display_options: CompletionDisplayOptions::default(),
27278 is_incomplete: true,
27279 });
27280 }
27281
27282 for (_scope, snippets) in scopes.into_iter() {
27283 // Sort snippets by word count to match longer snippet prefixes first.
27284 let mut sorted_snippet_candidates = snippets
27285 .iter()
27286 .enumerate()
27287 .flat_map(|(snippet_ix, snippet)| {
27288 snippet
27289 .prefix
27290 .iter()
27291 .enumerate()
27292 .map(move |(prefix_ix, prefix)| {
27293 let word_count =
27294 snippet_candidate_suffixes(prefix, &is_word_char).count();
27295 ((snippet_ix, prefix_ix), prefix, word_count)
27296 })
27297 })
27298 .collect_vec();
27299 sorted_snippet_candidates
27300 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27301
27302 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27303
27304 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27305 .take(
27306 sorted_snippet_candidates
27307 .first()
27308 .map(|(_, _, word_count)| *word_count)
27309 .unwrap_or_default(),
27310 )
27311 .collect_vec();
27312
27313 const MAX_RESULTS: usize = 100;
27314 // Each match also remembers how many characters from the buffer it consumed
27315 let mut matches: Vec<(StringMatch, usize)> = vec![];
27316
27317 let mut snippet_list_cutoff_index = 0;
27318 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27319 let word_count = buffer_index + 1;
27320 // Increase `snippet_list_cutoff_index` until we have all of the
27321 // snippets with sufficiently many words.
27322 while sorted_snippet_candidates
27323 .get(snippet_list_cutoff_index)
27324 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27325 *snippet_word_count >= word_count
27326 })
27327 {
27328 snippet_list_cutoff_index += 1;
27329 }
27330
27331 // Take only the candidates with at least `word_count` many words
27332 let snippet_candidates_at_word_len =
27333 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27334
27335 let candidates = snippet_candidates_at_word_len
27336 .iter()
27337 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27338 .enumerate() // index in `sorted_snippet_candidates`
27339 // First char must match
27340 .filter(|(_ix, prefix)| {
27341 itertools::equal(
27342 prefix
27343 .chars()
27344 .next()
27345 .into_iter()
27346 .flat_map(|c| c.to_lowercase()),
27347 buffer_window
27348 .chars()
27349 .next()
27350 .into_iter()
27351 .flat_map(|c| c.to_lowercase()),
27352 )
27353 })
27354 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27355 .collect::<Vec<StringMatchCandidate>>();
27356
27357 matches.extend(
27358 fuzzy::match_strings(
27359 &candidates,
27360 &buffer_window,
27361 buffer_window.chars().any(|c| c.is_uppercase()),
27362 true,
27363 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27364 &Default::default(),
27365 executor.clone(),
27366 )
27367 .await
27368 .into_iter()
27369 .map(|string_match| (string_match, buffer_window.len())),
27370 );
27371
27372 if matches.len() >= MAX_RESULTS {
27373 break;
27374 }
27375 }
27376
27377 let to_lsp = |point: &text::Anchor| {
27378 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27379 point_to_lsp(end)
27380 };
27381 let lsp_end = to_lsp(&buffer_anchor);
27382
27383 if matches.len() >= MAX_RESULTS {
27384 is_incomplete = true;
27385 }
27386
27387 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27388 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27389 sorted_snippet_candidates[string_match.candidate_id];
27390 let snippet = &snippets[snippet_index];
27391 let start = buffer_offset - buffer_window_len;
27392 let start = snapshot.anchor_before(start);
27393 let range = start..buffer_anchor;
27394 let lsp_start = to_lsp(&start);
27395 let lsp_range = lsp::Range {
27396 start: lsp_start,
27397 end: lsp_end,
27398 };
27399 Completion {
27400 replace_range: range,
27401 new_text: snippet.body.clone(),
27402 source: CompletionSource::Lsp {
27403 insert_range: None,
27404 server_id: LanguageServerId(usize::MAX),
27405 resolved: true,
27406 lsp_completion: Box::new(lsp::CompletionItem {
27407 label: snippet.prefix.first().unwrap().clone(),
27408 kind: Some(CompletionItemKind::SNIPPET),
27409 label_details: snippet.description.as_ref().map(|description| {
27410 lsp::CompletionItemLabelDetails {
27411 detail: Some(description.clone()),
27412 description: None,
27413 }
27414 }),
27415 insert_text_format: Some(InsertTextFormat::SNIPPET),
27416 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27417 lsp::InsertReplaceEdit {
27418 new_text: snippet.body.clone(),
27419 insert: lsp_range,
27420 replace: lsp_range,
27421 },
27422 )),
27423 filter_text: Some(snippet.body.clone()),
27424 sort_text: Some(char::MAX.to_string()),
27425 ..lsp::CompletionItem::default()
27426 }),
27427 lsp_defaults: None,
27428 },
27429 label: CodeLabel {
27430 text: matching_prefix.clone(),
27431 runs: Vec::new(),
27432 filter_range: 0..matching_prefix.len(),
27433 },
27434 icon_path: None,
27435 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27436 single_line: snippet.name.clone().into(),
27437 plain_text: snippet
27438 .description
27439 .clone()
27440 .map(|description| description.into()),
27441 }),
27442 insert_text_mode: None,
27443 confirm: None,
27444 match_start: Some(start),
27445 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27446 }
27447 }));
27448 }
27449
27450 Ok(CompletionResponse {
27451 completions,
27452 display_options: CompletionDisplayOptions::default(),
27453 is_incomplete,
27454 })
27455 })
27456}
27457
27458impl CompletionProvider for Entity<Project> {
27459 fn completions(
27460 &self,
27461 _excerpt_id: ExcerptId,
27462 buffer: &Entity<Buffer>,
27463 buffer_position: text::Anchor,
27464 options: CompletionContext,
27465 _window: &mut Window,
27466 cx: &mut Context<Editor>,
27467 ) -> Task<Result<Vec<CompletionResponse>>> {
27468 self.update(cx, |project, cx| {
27469 let task = project.completions(buffer, buffer_position, options, cx);
27470 cx.background_spawn(task)
27471 })
27472 }
27473
27474 fn resolve_completions(
27475 &self,
27476 buffer: Entity<Buffer>,
27477 completion_indices: Vec<usize>,
27478 completions: Rc<RefCell<Box<[Completion]>>>,
27479 cx: &mut Context<Editor>,
27480 ) -> Task<Result<bool>> {
27481 self.update(cx, |project, cx| {
27482 project.lsp_store().update(cx, |lsp_store, cx| {
27483 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27484 })
27485 })
27486 }
27487
27488 fn apply_additional_edits_for_completion(
27489 &self,
27490 buffer: Entity<Buffer>,
27491 completions: Rc<RefCell<Box<[Completion]>>>,
27492 completion_index: usize,
27493 push_to_history: bool,
27494 all_commit_ranges: Vec<Range<language::Anchor>>,
27495 cx: &mut Context<Editor>,
27496 ) -> Task<Result<Option<language::Transaction>>> {
27497 self.update(cx, |project, cx| {
27498 project.lsp_store().update(cx, |lsp_store, cx| {
27499 lsp_store.apply_additional_edits_for_completion(
27500 buffer,
27501 completions,
27502 completion_index,
27503 push_to_history,
27504 all_commit_ranges,
27505 cx,
27506 )
27507 })
27508 })
27509 }
27510
27511 fn is_completion_trigger(
27512 &self,
27513 buffer: &Entity<Buffer>,
27514 position: language::Anchor,
27515 text: &str,
27516 trigger_in_words: bool,
27517 cx: &mut Context<Editor>,
27518 ) -> bool {
27519 let mut chars = text.chars();
27520 let char = if let Some(char) = chars.next() {
27521 char
27522 } else {
27523 return false;
27524 };
27525 if chars.next().is_some() {
27526 return false;
27527 }
27528
27529 let buffer = buffer.read(cx);
27530 let snapshot = buffer.snapshot();
27531 let classifier = snapshot
27532 .char_classifier_at(position)
27533 .scope_context(Some(CharScopeContext::Completion));
27534 if trigger_in_words && classifier.is_word(char) {
27535 return true;
27536 }
27537
27538 buffer.completion_triggers().contains(text)
27539 }
27540
27541 fn show_snippets(&self) -> bool {
27542 true
27543 }
27544}
27545
27546impl SemanticsProvider for WeakEntity<Project> {
27547 fn hover(
27548 &self,
27549 buffer: &Entity<Buffer>,
27550 position: text::Anchor,
27551 cx: &mut App,
27552 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27553 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27554 .ok()
27555 }
27556
27557 fn document_highlights(
27558 &self,
27559 buffer: &Entity<Buffer>,
27560 position: text::Anchor,
27561 cx: &mut App,
27562 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27563 self.update(cx, |project, cx| {
27564 project.document_highlights(buffer, position, cx)
27565 })
27566 .ok()
27567 }
27568
27569 fn definitions(
27570 &self,
27571 buffer: &Entity<Buffer>,
27572 position: text::Anchor,
27573 kind: GotoDefinitionKind,
27574 cx: &mut App,
27575 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27576 self.update(cx, |project, cx| match kind {
27577 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27578 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27579 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27580 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27581 })
27582 .ok()
27583 }
27584
27585 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27586 self.update(cx, |project, cx| {
27587 if project
27588 .active_debug_session(cx)
27589 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27590 {
27591 return true;
27592 }
27593
27594 buffer.update(cx, |buffer, cx| {
27595 project.any_language_server_supports_inlay_hints(buffer, cx)
27596 })
27597 })
27598 .unwrap_or(false)
27599 }
27600
27601 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27602 self.update(cx, |project, cx| {
27603 buffer.update(cx, |buffer, cx| {
27604 project.any_language_server_supports_semantic_tokens(buffer, cx)
27605 })
27606 })
27607 .unwrap_or(false)
27608 }
27609
27610 fn inline_values(
27611 &self,
27612 buffer_handle: Entity<Buffer>,
27613 range: Range<text::Anchor>,
27614 cx: &mut App,
27615 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27616 self.update(cx, |project, cx| {
27617 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27618
27619 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27620 })
27621 .ok()
27622 .flatten()
27623 }
27624
27625 fn applicable_inlay_chunks(
27626 &self,
27627 buffer: &Entity<Buffer>,
27628 ranges: &[Range<text::Anchor>],
27629 cx: &mut App,
27630 ) -> Vec<Range<BufferRow>> {
27631 self.update(cx, |project, cx| {
27632 project.lsp_store().update(cx, |lsp_store, cx| {
27633 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27634 })
27635 })
27636 .unwrap_or_default()
27637 }
27638
27639 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27640 self.update(cx, |project, cx| {
27641 project.lsp_store().update(cx, |lsp_store, _| {
27642 lsp_store.invalidate_inlay_hints(for_buffers)
27643 })
27644 })
27645 .ok();
27646 }
27647
27648 fn inlay_hints(
27649 &self,
27650 invalidate: InvalidationStrategy,
27651 buffer: Entity<Buffer>,
27652 ranges: Vec<Range<text::Anchor>>,
27653 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27654 cx: &mut App,
27655 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27656 self.update(cx, |project, cx| {
27657 project.lsp_store().update(cx, |lsp_store, cx| {
27658 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27659 })
27660 })
27661 .ok()
27662 }
27663
27664 fn semantic_tokens(
27665 &self,
27666 buffer: Entity<Buffer>,
27667 refresh: Option<RefreshForServer>,
27668 cx: &mut App,
27669 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27670 self.update(cx, |this, cx| {
27671 this.lsp_store().update(cx, |lsp_store, cx| {
27672 lsp_store.semantic_tokens(buffer, refresh, cx)
27673 })
27674 })
27675 .ok()
27676 }
27677
27678 fn range_for_rename(
27679 &self,
27680 buffer: &Entity<Buffer>,
27681 position: text::Anchor,
27682 cx: &mut App,
27683 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27684 self.update(cx, |project, cx| {
27685 let buffer = buffer.clone();
27686 let task = project.prepare_rename(buffer.clone(), position, cx);
27687 cx.spawn(async move |_, cx| {
27688 Ok(match task.await? {
27689 PrepareRenameResponse::Success(range) => Some(range),
27690 PrepareRenameResponse::InvalidPosition => None,
27691 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27692 // Fallback on using TreeSitter info to determine identifier range
27693 buffer.read_with(cx, |buffer, _| {
27694 let snapshot = buffer.snapshot();
27695 let (range, kind) = snapshot.surrounding_word(position, None);
27696 if kind != Some(CharKind::Word) {
27697 return None;
27698 }
27699 Some(
27700 snapshot.anchor_before(range.start)
27701 ..snapshot.anchor_after(range.end),
27702 )
27703 })
27704 }
27705 })
27706 })
27707 })
27708 .ok()
27709 }
27710
27711 fn perform_rename(
27712 &self,
27713 buffer: &Entity<Buffer>,
27714 position: text::Anchor,
27715 new_name: String,
27716 cx: &mut App,
27717 ) -> Option<Task<Result<ProjectTransaction>>> {
27718 self.update(cx, |project, cx| {
27719 project.perform_rename(buffer.clone(), position, new_name, cx)
27720 })
27721 .ok()
27722 }
27723}
27724
27725fn consume_contiguous_rows(
27726 contiguous_row_selections: &mut Vec<Selection<Point>>,
27727 selection: &Selection<Point>,
27728 display_map: &DisplaySnapshot,
27729 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27730) -> (MultiBufferRow, MultiBufferRow) {
27731 contiguous_row_selections.push(selection.clone());
27732 let start_row = starting_row(selection, display_map);
27733 let mut end_row = ending_row(selection, display_map);
27734
27735 while let Some(next_selection) = selections.peek() {
27736 if next_selection.start.row <= end_row.0 {
27737 end_row = ending_row(next_selection, display_map);
27738 contiguous_row_selections.push(selections.next().unwrap().clone());
27739 } else {
27740 break;
27741 }
27742 }
27743 (start_row, end_row)
27744}
27745
27746fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27747 if selection.start.column > 0 {
27748 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27749 } else {
27750 MultiBufferRow(selection.start.row)
27751 }
27752}
27753
27754fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27755 if next_selection.end.column > 0 || next_selection.is_empty() {
27756 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27757 } else {
27758 MultiBufferRow(next_selection.end.row)
27759 }
27760}
27761
27762impl EditorSnapshot {
27763 pub fn remote_selections_in_range<'a>(
27764 &'a self,
27765 range: &'a Range<Anchor>,
27766 collaboration_hub: &dyn CollaborationHub,
27767 cx: &'a App,
27768 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27769 let participant_names = collaboration_hub.user_names(cx);
27770 let participant_indices = collaboration_hub.user_participant_indices(cx);
27771 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27772 let collaborators_by_replica_id = collaborators_by_peer_id
27773 .values()
27774 .map(|collaborator| (collaborator.replica_id, collaborator))
27775 .collect::<HashMap<_, _>>();
27776 self.buffer_snapshot()
27777 .selections_in_range(range, false)
27778 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27779 if replica_id == ReplicaId::AGENT {
27780 Some(RemoteSelection {
27781 replica_id,
27782 selection,
27783 cursor_shape,
27784 line_mode,
27785 collaborator_id: CollaboratorId::Agent,
27786 user_name: Some("Agent".into()),
27787 color: cx.theme().players().agent(),
27788 })
27789 } else {
27790 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27791 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27792 let user_name = participant_names.get(&collaborator.user_id).cloned();
27793 Some(RemoteSelection {
27794 replica_id,
27795 selection,
27796 cursor_shape,
27797 line_mode,
27798 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27799 user_name,
27800 color: if let Some(index) = participant_index {
27801 cx.theme().players().color_for_participant(index.0)
27802 } else {
27803 cx.theme().players().absent()
27804 },
27805 })
27806 }
27807 })
27808 }
27809
27810 pub fn hunks_for_ranges(
27811 &self,
27812 ranges: impl IntoIterator<Item = Range<Point>>,
27813 ) -> Vec<MultiBufferDiffHunk> {
27814 let mut hunks = Vec::new();
27815 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27816 HashMap::default();
27817 for query_range in ranges {
27818 let query_rows =
27819 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27820 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27821 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27822 ) {
27823 // Include deleted hunks that are adjacent to the query range, because
27824 // otherwise they would be missed.
27825 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27826 if hunk.status().is_deleted() {
27827 intersects_range |= hunk.row_range.start == query_rows.end;
27828 intersects_range |= hunk.row_range.end == query_rows.start;
27829 }
27830 if intersects_range {
27831 if !processed_buffer_rows
27832 .entry(hunk.buffer_id)
27833 .or_default()
27834 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27835 {
27836 continue;
27837 }
27838 hunks.push(hunk);
27839 }
27840 }
27841 }
27842
27843 hunks
27844 }
27845
27846 fn display_diff_hunks_for_rows<'a>(
27847 &'a self,
27848 display_rows: Range<DisplayRow>,
27849 folded_buffers: &'a HashSet<BufferId>,
27850 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27851 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27852 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27853
27854 self.buffer_snapshot()
27855 .diff_hunks_in_range(buffer_start..buffer_end)
27856 .filter_map(|hunk| {
27857 if folded_buffers.contains(&hunk.buffer_id)
27858 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27859 {
27860 return None;
27861 }
27862
27863 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27864 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27865 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27866 let line_len = self.buffer_snapshot().line_len(last_row);
27867 Point::new(last_row.0, line_len)
27868 } else {
27869 Point::new(hunk.row_range.end.0, 0)
27870 };
27871
27872 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27873 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27874
27875 let display_hunk = if hunk_display_start.column() != 0 {
27876 DisplayDiffHunk::Folded {
27877 display_row: hunk_display_start.row(),
27878 }
27879 } else {
27880 let mut end_row = hunk_display_end.row();
27881 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27882 end_row.0 += 1;
27883 }
27884 let is_created_file = hunk.is_created_file();
27885
27886 DisplayDiffHunk::Unfolded {
27887 status: hunk.status(),
27888 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27889 ..hunk.diff_base_byte_range.end.0,
27890 word_diffs: hunk.word_diffs,
27891 display_row_range: hunk_display_start.row()..end_row,
27892 multi_buffer_range: Anchor::range_in_buffer(
27893 hunk.excerpt_id,
27894 hunk.buffer_range,
27895 ),
27896 is_created_file,
27897 }
27898 };
27899
27900 Some(display_hunk)
27901 })
27902 }
27903
27904 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27905 self.display_snapshot
27906 .buffer_snapshot()
27907 .language_at(position)
27908 }
27909
27910 pub fn is_focused(&self) -> bool {
27911 self.is_focused
27912 }
27913
27914 pub fn placeholder_text(&self) -> Option<String> {
27915 self.placeholder_display_snapshot
27916 .as_ref()
27917 .map(|display_map| display_map.text())
27918 }
27919
27920 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27921 self.scroll_anchor.scroll_position(&self.display_snapshot)
27922 }
27923
27924 pub fn gutter_dimensions(
27925 &self,
27926 font_id: FontId,
27927 font_size: Pixels,
27928 style: &EditorStyle,
27929 window: &mut Window,
27930 cx: &App,
27931 ) -> GutterDimensions {
27932 if self.show_gutter
27933 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27934 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27935 {
27936 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27937 matches!(
27938 ProjectSettings::get_global(cx).git.git_gutter,
27939 GitGutterSetting::TrackedFiles
27940 )
27941 });
27942 let gutter_settings = EditorSettings::get_global(cx).gutter;
27943 let show_line_numbers = self
27944 .show_line_numbers
27945 .unwrap_or(gutter_settings.line_numbers);
27946 let line_gutter_width = if show_line_numbers {
27947 // Avoid flicker-like gutter resizes when the line number gains another digit by
27948 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27949 let min_width_for_number_on_gutter =
27950 ch_advance * gutter_settings.min_line_number_digits as f32;
27951 self.max_line_number_width(style, window)
27952 .max(min_width_for_number_on_gutter)
27953 } else {
27954 0.0.into()
27955 };
27956
27957 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27958 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27959
27960 let git_blame_entries_width =
27961 self.git_blame_gutter_max_author_length
27962 .map(|max_author_length| {
27963 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27964 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27965
27966 /// The number of characters to dedicate to gaps and margins.
27967 const SPACING_WIDTH: usize = 4;
27968
27969 let max_char_count = max_author_length.min(renderer.max_author_length())
27970 + ::git::SHORT_SHA_LENGTH
27971 + MAX_RELATIVE_TIMESTAMP.len()
27972 + SPACING_WIDTH;
27973
27974 ch_advance * max_char_count
27975 });
27976
27977 let is_singleton = self.buffer_snapshot().is_singleton();
27978
27979 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27980 left_padding += if !is_singleton {
27981 ch_width * 4.0
27982 } else if show_runnables || show_breakpoints {
27983 ch_width * 3.0
27984 } else if show_git_gutter && show_line_numbers {
27985 ch_width * 2.0
27986 } else if show_git_gutter || show_line_numbers {
27987 ch_width
27988 } else {
27989 px(0.)
27990 };
27991
27992 let shows_folds = is_singleton && gutter_settings.folds;
27993
27994 let right_padding = if shows_folds && show_line_numbers {
27995 ch_width * 4.0
27996 } else if shows_folds || (!is_singleton && show_line_numbers) {
27997 ch_width * 3.0
27998 } else if show_line_numbers {
27999 ch_width
28000 } else {
28001 px(0.)
28002 };
28003
28004 GutterDimensions {
28005 left_padding,
28006 right_padding,
28007 width: line_gutter_width + left_padding + right_padding,
28008 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
28009 git_blame_entries_width,
28010 }
28011 } else if self.offset_content {
28012 GutterDimensions::default_with_margin(font_id, font_size, cx)
28013 } else {
28014 GutterDimensions::default()
28015 }
28016 }
28017
28018 pub fn render_crease_toggle(
28019 &self,
28020 buffer_row: MultiBufferRow,
28021 row_contains_cursor: bool,
28022 editor: Entity<Editor>,
28023 window: &mut Window,
28024 cx: &mut App,
28025 ) -> Option<AnyElement> {
28026 let folded = self.is_line_folded(buffer_row);
28027 let mut is_foldable = false;
28028
28029 if let Some(crease) = self
28030 .crease_snapshot
28031 .query_row(buffer_row, self.buffer_snapshot())
28032 {
28033 is_foldable = true;
28034 match crease {
28035 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
28036 if let Some(render_toggle) = render_toggle {
28037 let toggle_callback =
28038 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
28039 if folded {
28040 editor.update(cx, |editor, cx| {
28041 editor.fold_at(buffer_row, window, cx)
28042 });
28043 } else {
28044 editor.update(cx, |editor, cx| {
28045 editor.unfold_at(buffer_row, window, cx)
28046 });
28047 }
28048 });
28049 return Some((render_toggle)(
28050 buffer_row,
28051 folded,
28052 toggle_callback,
28053 window,
28054 cx,
28055 ));
28056 }
28057 }
28058 }
28059 }
28060
28061 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
28062
28063 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
28064 Some(
28065 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
28066 .toggle_state(folded)
28067 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
28068 if folded {
28069 this.unfold_at(buffer_row, window, cx);
28070 } else {
28071 this.fold_at(buffer_row, window, cx);
28072 }
28073 }))
28074 .into_any_element(),
28075 )
28076 } else {
28077 None
28078 }
28079 }
28080
28081 pub fn render_crease_trailer(
28082 &self,
28083 buffer_row: MultiBufferRow,
28084 window: &mut Window,
28085 cx: &mut App,
28086 ) -> Option<AnyElement> {
28087 let folded = self.is_line_folded(buffer_row);
28088 if let Crease::Inline { render_trailer, .. } = self
28089 .crease_snapshot
28090 .query_row(buffer_row, self.buffer_snapshot())?
28091 {
28092 let render_trailer = render_trailer.as_ref()?;
28093 Some(render_trailer(buffer_row, folded, window, cx))
28094 } else {
28095 None
28096 }
28097 }
28098
28099 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
28100 let digit_count = self.widest_line_number().ilog10() + 1;
28101 column_pixels(style, digit_count as usize, window)
28102 }
28103
28104 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
28105 ///
28106 /// This is positive if `base` is before `line`.
28107 fn relative_line_delta(
28108 &self,
28109 current_selection_head: DisplayRow,
28110 first_visible_row: DisplayRow,
28111 consider_wrapped_lines: bool,
28112 ) -> i64 {
28113 let current_selection_head = current_selection_head.as_display_point().to_point(self);
28114 let first_visible_row = first_visible_row.as_display_point().to_point(self);
28115
28116 if consider_wrapped_lines {
28117 let wrap_snapshot = self.wrap_snapshot();
28118 let base_wrap_row = wrap_snapshot
28119 .make_wrap_point(current_selection_head, Bias::Left)
28120 .row();
28121 let wrap_row = wrap_snapshot
28122 .make_wrap_point(first_visible_row, Bias::Left)
28123 .row();
28124
28125 wrap_row.0 as i64 - base_wrap_row.0 as i64
28126 } else {
28127 let fold_snapshot = self.fold_snapshot();
28128 let base_fold_row = fold_snapshot
28129 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
28130 .row();
28131 let fold_row = fold_snapshot
28132 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
28133 .row();
28134
28135 fold_row as i64 - base_fold_row as i64
28136 }
28137 }
28138
28139 /// Returns the unsigned relative line number to display for each row in `rows`.
28140 ///
28141 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
28142 pub fn calculate_relative_line_numbers(
28143 &self,
28144 rows: &Range<DisplayRow>,
28145 current_selection_head: DisplayRow,
28146 count_wrapped_lines: bool,
28147 ) -> HashMap<DisplayRow, u32> {
28148 let initial_offset =
28149 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
28150
28151 self.row_infos(rows.start)
28152 .take(rows.len())
28153 .enumerate()
28154 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
28155 .filter(|(_row, row_info)| {
28156 row_info.buffer_row.is_some()
28157 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
28158 })
28159 .enumerate()
28160 .filter_map(|(i, (row, row_info))| {
28161 // We want to ensure here that the current line has absolute
28162 // numbering, even if we are in a soft-wrapped line. With the
28163 // exception that if we are in a deleted line, we should number this
28164 // relative with 0, as otherwise it would have no line number at all
28165 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
28166
28167 (relative_line_number != 0
28168 || row_info
28169 .diff_status
28170 .is_some_and(|status| status.is_deleted()))
28171 .then_some((row, relative_line_number))
28172 })
28173 .collect()
28174 }
28175}
28176
28177pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28178 let font_size = style.text.font_size.to_pixels(window.rem_size());
28179 let layout = window.text_system().shape_line(
28180 SharedString::from(" ".repeat(column)),
28181 font_size,
28182 &[TextRun {
28183 len: column,
28184 font: style.text.font(),
28185 color: Hsla::default(),
28186 ..Default::default()
28187 }],
28188 None,
28189 );
28190
28191 layout.width
28192}
28193
28194impl Deref for EditorSnapshot {
28195 type Target = DisplaySnapshot;
28196
28197 fn deref(&self) -> &Self::Target {
28198 &self.display_snapshot
28199 }
28200}
28201
28202#[derive(Clone, Debug, PartialEq, Eq)]
28203pub enum EditorEvent {
28204 /// Emitted when the stored review comments change (added, removed, or updated).
28205 ReviewCommentsChanged {
28206 /// The new total count of review comments.
28207 total_count: usize,
28208 },
28209 InputIgnored {
28210 text: Arc<str>,
28211 },
28212 InputHandled {
28213 utf16_range_to_replace: Option<Range<isize>>,
28214 text: Arc<str>,
28215 },
28216 ExcerptsAdded {
28217 buffer: Entity<Buffer>,
28218 predecessor: ExcerptId,
28219 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
28220 },
28221 ExcerptsRemoved {
28222 ids: Vec<ExcerptId>,
28223 removed_buffer_ids: Vec<BufferId>,
28224 },
28225 BufferFoldToggled {
28226 ids: Vec<ExcerptId>,
28227 folded: bool,
28228 },
28229 ExcerptsEdited {
28230 ids: Vec<ExcerptId>,
28231 },
28232 ExcerptsExpanded {
28233 ids: Vec<ExcerptId>,
28234 },
28235 ExpandExcerptsRequested {
28236 excerpt_ids: Vec<ExcerptId>,
28237 lines: u32,
28238 direction: ExpandExcerptDirection,
28239 },
28240 StageOrUnstageRequested {
28241 stage: bool,
28242 hunks: Vec<MultiBufferDiffHunk>,
28243 },
28244 OpenExcerptsRequested {
28245 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28246 split: bool,
28247 },
28248 RestoreRequested {
28249 hunks: Vec<MultiBufferDiffHunk>,
28250 },
28251 BufferEdited,
28252 Edited {
28253 transaction_id: clock::Lamport,
28254 },
28255 Reparsed(BufferId),
28256 Focused,
28257 FocusedIn,
28258 Blurred,
28259 DirtyChanged,
28260 Saved,
28261 TitleChanged,
28262 SelectionsChanged {
28263 local: bool,
28264 },
28265 ScrollPositionChanged {
28266 local: bool,
28267 autoscroll: bool,
28268 },
28269 TransactionUndone {
28270 transaction_id: clock::Lamport,
28271 },
28272 TransactionBegun {
28273 transaction_id: clock::Lamport,
28274 },
28275 CursorShapeChanged,
28276 BreadcrumbsChanged,
28277 OutlineSymbolsChanged,
28278 PushedToNavHistory {
28279 anchor: Anchor,
28280 is_deactivate: bool,
28281 },
28282}
28283
28284impl EventEmitter<EditorEvent> for Editor {}
28285
28286impl Focusable for Editor {
28287 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28288 self.focus_handle.clone()
28289 }
28290}
28291
28292impl Render for Editor {
28293 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28294 EditorElement::new(&cx.entity(), self.create_style(cx))
28295 }
28296}
28297
28298impl EntityInputHandler for Editor {
28299 fn text_for_range(
28300 &mut self,
28301 range_utf16: Range<usize>,
28302 adjusted_range: &mut Option<Range<usize>>,
28303 _: &mut Window,
28304 cx: &mut Context<Self>,
28305 ) -> Option<String> {
28306 let snapshot = self.buffer.read(cx).read(cx);
28307 let start = snapshot.clip_offset_utf16(
28308 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28309 Bias::Left,
28310 );
28311 let end = snapshot.clip_offset_utf16(
28312 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28313 Bias::Right,
28314 );
28315 if (start.0.0..end.0.0) != range_utf16 {
28316 adjusted_range.replace(start.0.0..end.0.0);
28317 }
28318 Some(snapshot.text_for_range(start..end).collect())
28319 }
28320
28321 fn selected_text_range(
28322 &mut self,
28323 ignore_disabled_input: bool,
28324 _: &mut Window,
28325 cx: &mut Context<Self>,
28326 ) -> Option<UTF16Selection> {
28327 // Prevent the IME menu from appearing when holding down an alphabetic key
28328 // while input is disabled.
28329 if !ignore_disabled_input && !self.input_enabled {
28330 return None;
28331 }
28332
28333 let selection = self
28334 .selections
28335 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28336 let range = selection.range();
28337
28338 Some(UTF16Selection {
28339 range: range.start.0.0..range.end.0.0,
28340 reversed: selection.reversed,
28341 })
28342 }
28343
28344 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28345 let snapshot = self.buffer.read(cx).read(cx);
28346 let range = self
28347 .text_highlights(HighlightKey::InputComposition, cx)?
28348 .1
28349 .first()?;
28350 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28351 }
28352
28353 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28354 self.clear_highlights(HighlightKey::InputComposition, cx);
28355 self.ime_transaction.take();
28356 }
28357
28358 fn replace_text_in_range(
28359 &mut self,
28360 range_utf16: Option<Range<usize>>,
28361 text: &str,
28362 window: &mut Window,
28363 cx: &mut Context<Self>,
28364 ) {
28365 if !self.input_enabled {
28366 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28367 return;
28368 }
28369
28370 self.transact(window, cx, |this, window, cx| {
28371 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28372 if let Some(marked_ranges) = this.marked_text_ranges(cx) {
28373 // During IME composition, macOS reports the replacement range
28374 // relative to the first marked region (the only one visible via
28375 // marked_text_range). The correct targets for replacement are the
28376 // marked ranges themselves — one per cursor — so use them directly.
28377 Some(marked_ranges)
28378 } else if range_utf16.start == range_utf16.end {
28379 // An empty replacement range means "insert at cursor" with no text
28380 // to replace. macOS reports the cursor position from its own
28381 // (single-cursor) view of the buffer, which diverges from our actual
28382 // cursor positions after multi-cursor edits have shifted offsets.
28383 // Treating this as range_utf16=None lets each cursor insert in place.
28384 None
28385 } else {
28386 // Outside of IME composition (e.g. Accessibility Keyboard word
28387 // completion), the range is an absolute document offset for the
28388 // newest cursor. Fan it out to all cursors via
28389 // selection_replacement_ranges, which applies the delta relative
28390 // to the newest selection to every cursor.
28391 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28392 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28393 Some(this.selection_replacement_ranges(range_utf16, cx))
28394 }
28395 } else {
28396 this.marked_text_ranges(cx)
28397 };
28398
28399 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28400 let newest_selection_id = this.selections.newest_anchor().id;
28401 this.selections
28402 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28403 .iter()
28404 .zip(ranges_to_replace.iter())
28405 .find_map(|(selection, range)| {
28406 if selection.id == newest_selection_id {
28407 Some(
28408 (range.start.0.0 as isize - selection.head().0.0 as isize)
28409 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28410 )
28411 } else {
28412 None
28413 }
28414 })
28415 });
28416
28417 cx.emit(EditorEvent::InputHandled {
28418 utf16_range_to_replace: range_to_replace,
28419 text: text.into(),
28420 });
28421
28422 if let Some(new_selected_ranges) = new_selected_ranges {
28423 // Only backspace if at least one range covers actual text. When all
28424 // ranges are empty (e.g. a trailing-space insertion from Accessibility
28425 // Keyboard sends replacementRange=cursor..cursor), backspace would
28426 // incorrectly delete the character just before the cursor.
28427 let should_backspace = new_selected_ranges.iter().any(|r| r.start != r.end);
28428 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28429 selections.select_ranges(new_selected_ranges)
28430 });
28431 if should_backspace {
28432 this.backspace(&Default::default(), window, cx);
28433 }
28434 }
28435
28436 this.handle_input(text, window, cx);
28437 });
28438
28439 if let Some(transaction) = self.ime_transaction {
28440 self.buffer.update(cx, |buffer, cx| {
28441 buffer.group_until_transaction(transaction, cx);
28442 });
28443 }
28444
28445 self.unmark_text(window, cx);
28446 }
28447
28448 fn replace_and_mark_text_in_range(
28449 &mut self,
28450 range_utf16: Option<Range<usize>>,
28451 text: &str,
28452 new_selected_range_utf16: Option<Range<usize>>,
28453 window: &mut Window,
28454 cx: &mut Context<Self>,
28455 ) {
28456 if !self.input_enabled {
28457 return;
28458 }
28459
28460 let transaction = self.transact(window, cx, |this, window, cx| {
28461 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28462 let snapshot = this.buffer.read(cx).read(cx);
28463 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28464 for marked_range in &mut marked_ranges {
28465 marked_range.end = marked_range.start + relative_range_utf16.end;
28466 marked_range.start += relative_range_utf16.start;
28467 marked_range.start =
28468 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28469 marked_range.end =
28470 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28471 }
28472 }
28473 Some(marked_ranges)
28474 } else if let Some(range_utf16) = range_utf16 {
28475 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28476 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28477 Some(this.selection_replacement_ranges(range_utf16, cx))
28478 } else {
28479 None
28480 };
28481
28482 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28483 let newest_selection_id = this.selections.newest_anchor().id;
28484 this.selections
28485 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28486 .iter()
28487 .zip(ranges_to_replace.iter())
28488 .find_map(|(selection, range)| {
28489 if selection.id == newest_selection_id {
28490 Some(
28491 (range.start.0.0 as isize - selection.head().0.0 as isize)
28492 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28493 )
28494 } else {
28495 None
28496 }
28497 })
28498 });
28499
28500 cx.emit(EditorEvent::InputHandled {
28501 utf16_range_to_replace: range_to_replace,
28502 text: text.into(),
28503 });
28504
28505 if let Some(ranges) = ranges_to_replace {
28506 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28507 s.select_ranges(ranges)
28508 });
28509 }
28510
28511 let marked_ranges = {
28512 let snapshot = this.buffer.read(cx).read(cx);
28513 this.selections
28514 .disjoint_anchors_arc()
28515 .iter()
28516 .map(|selection| {
28517 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28518 })
28519 .collect::<Vec<_>>()
28520 };
28521
28522 if text.is_empty() {
28523 this.unmark_text(window, cx);
28524 } else {
28525 this.highlight_text(
28526 HighlightKey::InputComposition,
28527 marked_ranges.clone(),
28528 HighlightStyle {
28529 underline: Some(UnderlineStyle {
28530 thickness: px(1.),
28531 color: None,
28532 wavy: false,
28533 }),
28534 ..Default::default()
28535 },
28536 cx,
28537 );
28538 }
28539
28540 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28541 let use_autoclose = this.use_autoclose;
28542 let use_auto_surround = this.use_auto_surround;
28543 this.set_use_autoclose(false);
28544 this.set_use_auto_surround(false);
28545 this.handle_input(text, window, cx);
28546 this.set_use_autoclose(use_autoclose);
28547 this.set_use_auto_surround(use_auto_surround);
28548
28549 if let Some(new_selected_range) = new_selected_range_utf16 {
28550 let snapshot = this.buffer.read(cx).read(cx);
28551 let new_selected_ranges = marked_ranges
28552 .into_iter()
28553 .map(|marked_range| {
28554 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28555 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28556 insertion_start.0 + new_selected_range.start,
28557 ));
28558 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28559 insertion_start.0 + new_selected_range.end,
28560 ));
28561 snapshot.clip_offset_utf16(new_start, Bias::Left)
28562 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28563 })
28564 .collect::<Vec<_>>();
28565
28566 drop(snapshot);
28567 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28568 selections.select_ranges(new_selected_ranges)
28569 });
28570 }
28571 });
28572
28573 self.ime_transaction = self.ime_transaction.or(transaction);
28574 if let Some(transaction) = self.ime_transaction {
28575 self.buffer.update(cx, |buffer, cx| {
28576 buffer.group_until_transaction(transaction, cx);
28577 });
28578 }
28579
28580 if self
28581 .text_highlights(HighlightKey::InputComposition, cx)
28582 .is_none()
28583 {
28584 self.ime_transaction.take();
28585 }
28586 }
28587
28588 fn bounds_for_range(
28589 &mut self,
28590 range_utf16: Range<usize>,
28591 element_bounds: gpui::Bounds<Pixels>,
28592 window: &mut Window,
28593 cx: &mut Context<Self>,
28594 ) -> Option<gpui::Bounds<Pixels>> {
28595 let text_layout_details = self.text_layout_details(window, cx);
28596 let CharacterDimensions {
28597 em_width,
28598 em_advance,
28599 line_height,
28600 } = self.character_dimensions(window, cx);
28601
28602 let snapshot = self.snapshot(window, cx);
28603 let scroll_position = snapshot.scroll_position();
28604 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28605
28606 let start =
28607 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28608 let x = Pixels::from(
28609 ScrollOffset::from(
28610 snapshot.x_for_display_point(start, &text_layout_details)
28611 + self.gutter_dimensions.full_width(),
28612 ) - scroll_left,
28613 );
28614 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28615
28616 Some(Bounds {
28617 origin: element_bounds.origin + point(x, y),
28618 size: size(em_width, line_height),
28619 })
28620 }
28621
28622 fn character_index_for_point(
28623 &mut self,
28624 point: gpui::Point<Pixels>,
28625 _window: &mut Window,
28626 _cx: &mut Context<Self>,
28627 ) -> Option<usize> {
28628 let position_map = self.last_position_map.as_ref()?;
28629 if !position_map.text_hitbox.contains(&point) {
28630 return None;
28631 }
28632 let display_point = position_map.point_for_position(point).previous_valid;
28633 let anchor = position_map
28634 .snapshot
28635 .display_point_to_anchor(display_point, Bias::Left);
28636 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28637 Some(utf16_offset.0.0)
28638 }
28639
28640 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28641 self.expects_character_input
28642 }
28643}
28644
28645trait SelectionExt {
28646 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28647 fn spanned_rows(
28648 &self,
28649 include_end_if_at_line_start: bool,
28650 map: &DisplaySnapshot,
28651 ) -> Range<MultiBufferRow>;
28652}
28653
28654impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28655 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28656 let start = self
28657 .start
28658 .to_point(map.buffer_snapshot())
28659 .to_display_point(map);
28660 let end = self
28661 .end
28662 .to_point(map.buffer_snapshot())
28663 .to_display_point(map);
28664 if self.reversed {
28665 end..start
28666 } else {
28667 start..end
28668 }
28669 }
28670
28671 fn spanned_rows(
28672 &self,
28673 include_end_if_at_line_start: bool,
28674 map: &DisplaySnapshot,
28675 ) -> Range<MultiBufferRow> {
28676 let start = self.start.to_point(map.buffer_snapshot());
28677 let mut end = self.end.to_point(map.buffer_snapshot());
28678 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28679 end.row -= 1;
28680 }
28681
28682 let buffer_start = map.prev_line_boundary(start).0;
28683 let buffer_end = map.next_line_boundary(end).0;
28684 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28685 }
28686}
28687
28688impl<T: InvalidationRegion> InvalidationStack<T> {
28689 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28690 where
28691 S: Clone + ToOffset,
28692 {
28693 while let Some(region) = self.last() {
28694 let all_selections_inside_invalidation_ranges =
28695 if selections.len() == region.ranges().len() {
28696 selections
28697 .iter()
28698 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28699 .all(|(selection, invalidation_range)| {
28700 let head = selection.head().to_offset(buffer);
28701 invalidation_range.start <= head && invalidation_range.end >= head
28702 })
28703 } else {
28704 false
28705 };
28706
28707 if all_selections_inside_invalidation_ranges {
28708 break;
28709 } else {
28710 self.pop();
28711 }
28712 }
28713 }
28714}
28715
28716#[derive(Clone)]
28717struct ErasedEditorImpl(Entity<Editor>);
28718
28719impl ui_input::ErasedEditor for ErasedEditorImpl {
28720 fn text(&self, cx: &App) -> String {
28721 self.0.read(cx).text(cx)
28722 }
28723
28724 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28725 self.0.update(cx, |this, cx| {
28726 this.set_text(text, window, cx);
28727 })
28728 }
28729
28730 fn clear(&self, window: &mut Window, cx: &mut App) {
28731 self.0.update(cx, |this, cx| this.clear(window, cx));
28732 }
28733
28734 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28735 self.0.update(cx, |this, cx| {
28736 this.set_placeholder_text(text, window, cx);
28737 });
28738 }
28739
28740 fn focus_handle(&self, cx: &App) -> FocusHandle {
28741 self.0.read(cx).focus_handle(cx)
28742 }
28743
28744 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28745 let settings = ThemeSettings::get_global(cx);
28746 let theme_color = cx.theme().colors();
28747
28748 let text_style = TextStyle {
28749 font_family: settings.ui_font.family.clone(),
28750 font_features: settings.ui_font.features.clone(),
28751 font_size: rems(0.875).into(),
28752 font_weight: settings.ui_font.weight,
28753 font_style: FontStyle::Normal,
28754 line_height: relative(1.2),
28755 color: theme_color.text,
28756 ..Default::default()
28757 };
28758 let editor_style = EditorStyle {
28759 background: theme_color.ghost_element_background,
28760 local_player: cx.theme().players().local(),
28761 syntax: cx.theme().syntax().clone(),
28762 text: text_style,
28763 ..Default::default()
28764 };
28765 EditorElement::new(&self.0, editor_style).into_any()
28766 }
28767
28768 fn as_any(&self) -> &dyn Any {
28769 &self.0
28770 }
28771
28772 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28773 self.0.update(cx, |editor, cx| {
28774 let editor_offset = editor.buffer().read(cx).len(cx);
28775 editor.change_selections(
28776 SelectionEffects::scroll(Autoscroll::Next),
28777 window,
28778 cx,
28779 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28780 );
28781 });
28782 }
28783
28784 fn subscribe(
28785 &self,
28786 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28787 window: &mut Window,
28788 cx: &mut App,
28789 ) -> Subscription {
28790 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28791 let event = match event {
28792 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28793 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28794 _ => return,
28795 };
28796 (callback)(event, window, cx);
28797 })
28798 }
28799
28800 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28801 self.0.update(cx, |editor, cx| {
28802 editor.set_masked(masked, cx);
28803 });
28804 }
28805}
28806impl<T> Default for InvalidationStack<T> {
28807 fn default() -> Self {
28808 Self(Default::default())
28809 }
28810}
28811
28812impl<T> Deref for InvalidationStack<T> {
28813 type Target = Vec<T>;
28814
28815 fn deref(&self) -> &Self::Target {
28816 &self.0
28817 }
28818}
28819
28820impl<T> DerefMut for InvalidationStack<T> {
28821 fn deref_mut(&mut self) -> &mut Self::Target {
28822 &mut self.0
28823 }
28824}
28825
28826impl InvalidationRegion for SnippetState {
28827 fn ranges(&self) -> &[Range<Anchor>] {
28828 &self.ranges[self.active_index]
28829 }
28830}
28831
28832fn edit_prediction_edit_text(
28833 current_snapshot: &BufferSnapshot,
28834 edits: &[(Range<Anchor>, impl AsRef<str>)],
28835 edit_preview: &EditPreview,
28836 include_deletions: bool,
28837 cx: &App,
28838) -> HighlightedText {
28839 let edits = edits
28840 .iter()
28841 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28842 .collect::<Vec<_>>();
28843
28844 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28845}
28846
28847fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28848 // Fallback for providers that don't provide edit_preview (like Copilot)
28849 // Just show the raw edit text with basic styling
28850 let mut text = String::new();
28851 let mut highlights = Vec::new();
28852
28853 let insertion_highlight_style = HighlightStyle {
28854 color: Some(cx.theme().colors().text),
28855 ..Default::default()
28856 };
28857
28858 for (_, edit_text) in edits {
28859 let start_offset = text.len();
28860 text.push_str(edit_text);
28861 let end_offset = text.len();
28862
28863 if start_offset < end_offset {
28864 highlights.push((start_offset..end_offset, insertion_highlight_style));
28865 }
28866 }
28867
28868 HighlightedText {
28869 text: text.into(),
28870 highlights,
28871 }
28872}
28873
28874pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28875 match severity {
28876 lsp::DiagnosticSeverity::ERROR => colors.error,
28877 lsp::DiagnosticSeverity::WARNING => colors.warning,
28878 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28879 lsp::DiagnosticSeverity::HINT => colors.info,
28880 _ => colors.ignored,
28881 }
28882}
28883
28884pub fn styled_runs_for_code_label<'a>(
28885 label: &'a CodeLabel,
28886 syntax_theme: &'a theme::SyntaxTheme,
28887 local_player: &'a theme::PlayerColor,
28888) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28889 let fade_out = HighlightStyle {
28890 fade_out: Some(0.35),
28891 ..Default::default()
28892 };
28893
28894 if label.runs.is_empty() {
28895 let desc_start = label.filter_range.end;
28896 let fade_run =
28897 (desc_start < label.text.len()).then(|| (desc_start..label.text.len(), fade_out));
28898 return Either::Left(fade_run.into_iter());
28899 }
28900
28901 let mut prev_end = label.filter_range.end;
28902 Either::Right(
28903 label
28904 .runs
28905 .iter()
28906 .enumerate()
28907 .flat_map(move |(ix, (range, highlight_id))| {
28908 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28909 HighlightStyle {
28910 color: Some(local_player.cursor),
28911 ..Default::default()
28912 }
28913 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28914 HighlightStyle {
28915 background_color: Some(local_player.selection),
28916 ..Default::default()
28917 }
28918 } else if let Some(style) = syntax_theme.get(*highlight_id).cloned() {
28919 style
28920 } else {
28921 return Default::default();
28922 };
28923
28924 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28925 let muted_style = style.highlight(fade_out);
28926 if range.start >= label.filter_range.end {
28927 if range.start > prev_end {
28928 runs.push((prev_end..range.start, fade_out));
28929 }
28930 runs.push((range.clone(), muted_style));
28931 } else if range.end <= label.filter_range.end {
28932 runs.push((range.clone(), style));
28933 } else {
28934 runs.push((range.start..label.filter_range.end, style));
28935 runs.push((label.filter_range.end..range.end, muted_style));
28936 }
28937 prev_end = cmp::max(prev_end, range.end);
28938
28939 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28940 runs.push((prev_end..label.text.len(), fade_out));
28941 }
28942
28943 runs
28944 }),
28945 )
28946}
28947
28948pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28949 let mut prev_index = 0;
28950 let mut prev_codepoint: Option<char> = None;
28951 text.char_indices()
28952 .chain([(text.len(), '\0')])
28953 .filter_map(move |(index, codepoint)| {
28954 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28955 let is_boundary = index == text.len()
28956 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28957 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28958 if is_boundary {
28959 let chunk = &text[prev_index..index];
28960 prev_index = index;
28961 Some(chunk)
28962 } else {
28963 None
28964 }
28965 })
28966}
28967
28968/// Given a string of text immediately before the cursor, iterates over possible
28969/// strings a snippet could match to. More precisely: returns an iterator over
28970/// suffixes of `text` created by splitting at word boundaries (before & after
28971/// every non-word character).
28972///
28973/// Shorter suffixes are returned first.
28974pub(crate) fn snippet_candidate_suffixes<'a>(
28975 text: &'a str,
28976 is_word_char: &'a dyn Fn(char) -> bool,
28977) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28978 let mut prev_index = text.len();
28979 let mut prev_codepoint = None;
28980 text.char_indices()
28981 .rev()
28982 .chain([(0, '\0')])
28983 .filter_map(move |(index, codepoint)| {
28984 let prev_index = std::mem::replace(&mut prev_index, index);
28985 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28986 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28987 None
28988 } else {
28989 let chunk = &text[prev_index..]; // go to end of string
28990 Some(chunk)
28991 }
28992 })
28993}
28994
28995pub trait RangeToAnchorExt: Sized {
28996 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28997
28998 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28999 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
29000 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
29001 }
29002}
29003
29004impl<T: ToOffset> RangeToAnchorExt for Range<T> {
29005 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
29006 let start_offset = self.start.to_offset(snapshot);
29007 let end_offset = self.end.to_offset(snapshot);
29008 if start_offset == end_offset {
29009 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
29010 } else {
29011 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
29012 }
29013 }
29014}
29015
29016pub trait RowExt {
29017 fn as_f64(&self) -> f64;
29018
29019 fn next_row(&self) -> Self;
29020
29021 fn previous_row(&self) -> Self;
29022
29023 fn minus(&self, other: Self) -> u32;
29024}
29025
29026impl RowExt for DisplayRow {
29027 fn as_f64(&self) -> f64 {
29028 self.0 as _
29029 }
29030
29031 fn next_row(&self) -> Self {
29032 Self(self.0 + 1)
29033 }
29034
29035 fn previous_row(&self) -> Self {
29036 Self(self.0.saturating_sub(1))
29037 }
29038
29039 fn minus(&self, other: Self) -> u32 {
29040 self.0 - other.0
29041 }
29042}
29043
29044impl RowExt for MultiBufferRow {
29045 fn as_f64(&self) -> f64 {
29046 self.0 as _
29047 }
29048
29049 fn next_row(&self) -> Self {
29050 Self(self.0 + 1)
29051 }
29052
29053 fn previous_row(&self) -> Self {
29054 Self(self.0.saturating_sub(1))
29055 }
29056
29057 fn minus(&self, other: Self) -> u32 {
29058 self.0 - other.0
29059 }
29060}
29061
29062trait RowRangeExt {
29063 type Row;
29064
29065 fn len(&self) -> usize;
29066
29067 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
29068}
29069
29070impl RowRangeExt for Range<MultiBufferRow> {
29071 type Row = MultiBufferRow;
29072
29073 fn len(&self) -> usize {
29074 (self.end.0 - self.start.0) as usize
29075 }
29076
29077 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
29078 (self.start.0..self.end.0).map(MultiBufferRow)
29079 }
29080}
29081
29082impl RowRangeExt for Range<DisplayRow> {
29083 type Row = DisplayRow;
29084
29085 fn len(&self) -> usize {
29086 (self.end.0 - self.start.0) as usize
29087 }
29088
29089 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
29090 (self.start.0..self.end.0).map(DisplayRow)
29091 }
29092}
29093
29094/// If select range has more than one line, we
29095/// just point the cursor to range.start.
29096fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
29097 if range.start.row == range.end.row {
29098 range
29099 } else {
29100 range.start..range.start
29101 }
29102}
29103pub struct KillRing(ClipboardItem);
29104impl Global for KillRing {}
29105
29106const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
29107
29108enum BreakpointPromptEditAction {
29109 Log,
29110 Condition,
29111 HitCondition,
29112}
29113
29114struct BreakpointPromptEditor {
29115 pub(crate) prompt: Entity<Editor>,
29116 editor: WeakEntity<Editor>,
29117 breakpoint_anchor: Anchor,
29118 breakpoint: Breakpoint,
29119 edit_action: BreakpointPromptEditAction,
29120 block_ids: HashSet<CustomBlockId>,
29121 editor_margins: Arc<Mutex<EditorMargins>>,
29122 _subscriptions: Vec<Subscription>,
29123}
29124
29125impl BreakpointPromptEditor {
29126 const MAX_LINES: u8 = 4;
29127
29128 fn new(
29129 editor: WeakEntity<Editor>,
29130 breakpoint_anchor: Anchor,
29131 breakpoint: Breakpoint,
29132 edit_action: BreakpointPromptEditAction,
29133 window: &mut Window,
29134 cx: &mut Context<Self>,
29135 ) -> Self {
29136 let base_text = match edit_action {
29137 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
29138 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
29139 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
29140 }
29141 .map(|msg| msg.to_string())
29142 .unwrap_or_default();
29143
29144 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
29145 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29146
29147 let prompt = cx.new(|cx| {
29148 let mut prompt = Editor::new(
29149 EditorMode::AutoHeight {
29150 min_lines: 1,
29151 max_lines: Some(Self::MAX_LINES as usize),
29152 },
29153 buffer,
29154 None,
29155 window,
29156 cx,
29157 );
29158 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
29159 prompt.set_show_cursor_when_unfocused(false, cx);
29160 prompt.set_placeholder_text(
29161 match edit_action {
29162 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
29163 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
29164 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
29165 },
29166 window,
29167 cx,
29168 );
29169
29170 prompt
29171 });
29172
29173 Self {
29174 prompt,
29175 editor,
29176 breakpoint_anchor,
29177 breakpoint,
29178 edit_action,
29179 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
29180 block_ids: Default::default(),
29181 _subscriptions: vec![],
29182 }
29183 }
29184
29185 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
29186 self.block_ids.extend(block_ids)
29187 }
29188
29189 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
29190 if let Some(editor) = self.editor.upgrade() {
29191 let message = self
29192 .prompt
29193 .read(cx)
29194 .buffer
29195 .read(cx)
29196 .as_singleton()
29197 .expect("A multi buffer in breakpoint prompt isn't possible")
29198 .read(cx)
29199 .as_rope()
29200 .to_string();
29201
29202 editor.update(cx, |editor, cx| {
29203 editor.edit_breakpoint_at_anchor(
29204 self.breakpoint_anchor,
29205 self.breakpoint.clone(),
29206 match self.edit_action {
29207 BreakpointPromptEditAction::Log => {
29208 BreakpointEditAction::EditLogMessage(message.into())
29209 }
29210 BreakpointPromptEditAction::Condition => {
29211 BreakpointEditAction::EditCondition(message.into())
29212 }
29213 BreakpointPromptEditAction::HitCondition => {
29214 BreakpointEditAction::EditHitCondition(message.into())
29215 }
29216 },
29217 cx,
29218 );
29219
29220 editor.remove_blocks(self.block_ids.clone(), None, cx);
29221 cx.focus_self(window);
29222 });
29223 }
29224 }
29225
29226 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29227 self.editor
29228 .update(cx, |editor, cx| {
29229 editor.remove_blocks(self.block_ids.clone(), None, cx);
29230 window.focus(&editor.focus_handle, cx);
29231 })
29232 .log_err();
29233 }
29234
29235 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29236 let settings = ThemeSettings::get_global(cx);
29237 let text_style = TextStyle {
29238 color: if self.prompt.read(cx).read_only(cx) {
29239 cx.theme().colors().text_disabled
29240 } else {
29241 cx.theme().colors().text
29242 },
29243 font_family: settings.buffer_font.family.clone(),
29244 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29245 font_size: settings.buffer_font_size(cx).into(),
29246 font_weight: settings.buffer_font.weight,
29247 line_height: relative(settings.buffer_line_height.value()),
29248 ..Default::default()
29249 };
29250 EditorElement::new(
29251 &self.prompt,
29252 EditorStyle {
29253 background: cx.theme().colors().editor_background,
29254 local_player: cx.theme().players().local(),
29255 text: text_style,
29256 ..Default::default()
29257 },
29258 )
29259 }
29260
29261 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29262 let focus_handle = self.prompt.focus_handle(cx);
29263 IconButton::new("cancel", IconName::Close)
29264 .icon_color(Color::Muted)
29265 .shape(IconButtonShape::Square)
29266 .tooltip(move |_window, cx| {
29267 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29268 })
29269 .on_click(cx.listener(|this, _, window, cx| {
29270 this.cancel(&menu::Cancel, window, cx);
29271 }))
29272 }
29273
29274 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29275 let focus_handle = self.prompt.focus_handle(cx);
29276 IconButton::new("confirm", IconName::Return)
29277 .icon_color(Color::Muted)
29278 .shape(IconButtonShape::Square)
29279 .tooltip(move |_window, cx| {
29280 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29281 })
29282 .on_click(cx.listener(|this, _, window, cx| {
29283 this.confirm(&menu::Confirm, window, cx);
29284 }))
29285 }
29286}
29287
29288impl Render for BreakpointPromptEditor {
29289 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29290 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29291 let editor_margins = *self.editor_margins.lock();
29292 let gutter_dimensions = editor_margins.gutter;
29293 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29294 let right_padding = editor_margins.right + px(9.);
29295 h_flex()
29296 .key_context("Editor")
29297 .bg(cx.theme().colors().editor_background)
29298 .border_y_1()
29299 .border_color(cx.theme().status().info_border)
29300 .size_full()
29301 .py(window.line_height() / 2.5)
29302 .pr(right_padding)
29303 .on_action(cx.listener(Self::confirm))
29304 .on_action(cx.listener(Self::cancel))
29305 .child(
29306 WithRemSize::new(ui_font_size)
29307 .h_full()
29308 .w(left_gutter_width)
29309 .flex()
29310 .flex_row()
29311 .flex_shrink_0()
29312 .items_center()
29313 .justify_center()
29314 .gap_1()
29315 .child(self.render_close_button(cx)),
29316 )
29317 .child(
29318 h_flex()
29319 .w_full()
29320 .justify_between()
29321 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29322 .child(
29323 WithRemSize::new(ui_font_size)
29324 .flex()
29325 .flex_row()
29326 .items_center()
29327 .child(self.render_confirm_button(cx)),
29328 ),
29329 )
29330 }
29331}
29332
29333impl Focusable for BreakpointPromptEditor {
29334 fn focus_handle(&self, cx: &App) -> FocusHandle {
29335 self.prompt.focus_handle(cx)
29336 }
29337}
29338
29339fn all_edits_insertions_or_deletions(
29340 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29341 snapshot: &MultiBufferSnapshot,
29342) -> bool {
29343 let mut all_insertions = true;
29344 let mut all_deletions = true;
29345
29346 for (range, new_text) in edits.iter() {
29347 let range_is_empty = range.to_offset(snapshot).is_empty();
29348 let text_is_empty = new_text.is_empty();
29349
29350 if range_is_empty != text_is_empty {
29351 if range_is_empty {
29352 all_deletions = false;
29353 } else {
29354 all_insertions = false;
29355 }
29356 } else {
29357 return false;
29358 }
29359
29360 if !all_insertions && !all_deletions {
29361 return false;
29362 }
29363 }
29364 all_insertions || all_deletions
29365}
29366
29367struct MissingEditPredictionKeybindingTooltip;
29368
29369impl Render for MissingEditPredictionKeybindingTooltip {
29370 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29371 ui::tooltip_container(cx, |container, cx| {
29372 container
29373 .flex_shrink_0()
29374 .max_w_80()
29375 .min_h(rems_from_px(124.))
29376 .justify_between()
29377 .child(
29378 v_flex()
29379 .flex_1()
29380 .text_ui_sm(cx)
29381 .child(Label::new("Conflict with Accept Keybinding"))
29382 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29383 )
29384 .child(
29385 h_flex()
29386 .pb_1()
29387 .gap_1()
29388 .items_end()
29389 .w_full()
29390 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29391 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29392 }))
29393 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29394 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29395 })),
29396 )
29397 })
29398 }
29399}
29400
29401#[derive(Debug, Clone, Copy, PartialEq)]
29402pub struct LineHighlight {
29403 pub background: Background,
29404 pub border: Option<gpui::Hsla>,
29405 pub include_gutter: bool,
29406 pub type_id: Option<TypeId>,
29407}
29408
29409struct LineManipulationResult {
29410 pub new_text: String,
29411 pub line_count_before: usize,
29412 pub line_count_after: usize,
29413}
29414
29415fn render_diff_hunk_controls(
29416 row: u32,
29417 status: &DiffHunkStatus,
29418 hunk_range: Range<Anchor>,
29419 is_created_file: bool,
29420 line_height: Pixels,
29421 editor: &Entity<Editor>,
29422 _window: &mut Window,
29423 cx: &mut App,
29424) -> AnyElement {
29425 h_flex()
29426 .h(line_height)
29427 .mr_1()
29428 .gap_1()
29429 .px_0p5()
29430 .pb_1()
29431 .border_x_1()
29432 .border_b_1()
29433 .border_color(cx.theme().colors().border_variant)
29434 .rounded_b_lg()
29435 .bg(cx.theme().colors().editor_background)
29436 .gap_1()
29437 .block_mouse_except_scroll()
29438 .shadow_md()
29439 .child(if status.has_secondary_hunk() {
29440 Button::new(("stage", row as u64), "Stage")
29441 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29442 .tooltip({
29443 let focus_handle = editor.focus_handle(cx);
29444 move |_window, cx| {
29445 Tooltip::for_action_in(
29446 "Stage Hunk",
29447 &::git::ToggleStaged,
29448 &focus_handle,
29449 cx,
29450 )
29451 }
29452 })
29453 .on_click({
29454 let editor = editor.clone();
29455 move |_event, _window, cx| {
29456 editor.update(cx, |editor, cx| {
29457 editor.stage_or_unstage_diff_hunks(
29458 true,
29459 vec![hunk_range.start..hunk_range.start],
29460 cx,
29461 );
29462 });
29463 }
29464 })
29465 } else {
29466 Button::new(("unstage", row as u64), "Unstage")
29467 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29468 .tooltip({
29469 let focus_handle = editor.focus_handle(cx);
29470 move |_window, cx| {
29471 Tooltip::for_action_in(
29472 "Unstage Hunk",
29473 &::git::ToggleStaged,
29474 &focus_handle,
29475 cx,
29476 )
29477 }
29478 })
29479 .on_click({
29480 let editor = editor.clone();
29481 move |_event, _window, cx| {
29482 editor.update(cx, |editor, cx| {
29483 editor.stage_or_unstage_diff_hunks(
29484 false,
29485 vec![hunk_range.start..hunk_range.start],
29486 cx,
29487 );
29488 });
29489 }
29490 })
29491 })
29492 .child(
29493 Button::new(("restore", row as u64), "Restore")
29494 .tooltip({
29495 let focus_handle = editor.focus_handle(cx);
29496 move |_window, cx| {
29497 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29498 }
29499 })
29500 .on_click({
29501 let editor = editor.clone();
29502 move |_event, window, cx| {
29503 editor.update(cx, |editor, cx| {
29504 let snapshot = editor.snapshot(window, cx);
29505 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29506 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29507 });
29508 }
29509 })
29510 .disabled(is_created_file),
29511 )
29512 .when(
29513 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29514 |el| {
29515 el.child(
29516 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29517 .shape(IconButtonShape::Square)
29518 .icon_size(IconSize::Small)
29519 // .disabled(!has_multiple_hunks)
29520 .tooltip({
29521 let focus_handle = editor.focus_handle(cx);
29522 move |_window, cx| {
29523 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29524 }
29525 })
29526 .on_click({
29527 let editor = editor.clone();
29528 move |_event, window, cx| {
29529 editor.update(cx, |editor, cx| {
29530 let snapshot = editor.snapshot(window, cx);
29531 let position =
29532 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29533 editor.go_to_hunk_before_or_after_position(
29534 &snapshot,
29535 position,
29536 Direction::Next,
29537 true,
29538 window,
29539 cx,
29540 );
29541 editor.expand_selected_diff_hunks(cx);
29542 });
29543 }
29544 }),
29545 )
29546 .child(
29547 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29548 .shape(IconButtonShape::Square)
29549 .icon_size(IconSize::Small)
29550 // .disabled(!has_multiple_hunks)
29551 .tooltip({
29552 let focus_handle = editor.focus_handle(cx);
29553 move |_window, cx| {
29554 Tooltip::for_action_in(
29555 "Previous Hunk",
29556 &GoToPreviousHunk,
29557 &focus_handle,
29558 cx,
29559 )
29560 }
29561 })
29562 .on_click({
29563 let editor = editor.clone();
29564 move |_event, window, cx| {
29565 editor.update(cx, |editor, cx| {
29566 let snapshot = editor.snapshot(window, cx);
29567 let point =
29568 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29569 editor.go_to_hunk_before_or_after_position(
29570 &snapshot,
29571 point,
29572 Direction::Prev,
29573 true,
29574 window,
29575 cx,
29576 );
29577 editor.expand_selected_diff_hunks(cx);
29578 });
29579 }
29580 }),
29581 )
29582 },
29583 )
29584 .into_any_element()
29585}
29586
29587pub fn multibuffer_context_lines(cx: &App) -> u32 {
29588 EditorSettings::try_get(cx)
29589 .map(|settings| settings.excerpt_context_lines)
29590 .unwrap_or(2)
29591 .min(32)
29592}