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, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings,
65 ShowMinimap,
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 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 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 Ok(())
6792 }))
6793 }
6794
6795 pub fn toggle_code_actions(
6796 &mut self,
6797 action: &ToggleCodeActions,
6798 window: &mut Window,
6799 cx: &mut Context<Self>,
6800 ) {
6801 let quick_launch = action.quick_launch;
6802 let mut context_menu = self.context_menu.borrow_mut();
6803 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6804 if code_actions.deployed_from == action.deployed_from {
6805 // Toggle if we're selecting the same one
6806 *context_menu = None;
6807 cx.notify();
6808 return;
6809 } else {
6810 // Otherwise, clear it and start a new one
6811 *context_menu = None;
6812 cx.notify();
6813 }
6814 }
6815 drop(context_menu);
6816 let snapshot = self.snapshot(window, cx);
6817 let deployed_from = action.deployed_from.clone();
6818 let action = action.clone();
6819 self.completion_tasks.clear();
6820 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6821
6822 let multibuffer_point = match &action.deployed_from {
6823 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6824 DisplayPoint::new(*row, 0).to_point(&snapshot)
6825 }
6826 _ => self
6827 .selections
6828 .newest::<Point>(&snapshot.display_snapshot)
6829 .head(),
6830 };
6831 let Some((buffer, buffer_row)) = snapshot
6832 .buffer_snapshot()
6833 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6834 .and_then(|(buffer_snapshot, range)| {
6835 self.buffer()
6836 .read(cx)
6837 .buffer(buffer_snapshot.remote_id())
6838 .map(|buffer| (buffer, range.start.row))
6839 })
6840 else {
6841 return;
6842 };
6843 let buffer_id = buffer.read(cx).remote_id();
6844 let tasks = self
6845 .runnables
6846 .runnables((buffer_id, buffer_row))
6847 .map(|t| Arc::new(t.to_owned()));
6848
6849 if !self.focus_handle.is_focused(window) {
6850 return;
6851 }
6852 let project = self.project.clone();
6853
6854 let code_actions_task = match deployed_from {
6855 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6856 _ => self.code_actions(buffer_row, window, cx),
6857 };
6858
6859 let runnable_task = match deployed_from {
6860 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6861 _ => {
6862 let mut task_context_task = Task::ready(None);
6863 if let Some(tasks) = &tasks
6864 && let Some(project) = project
6865 {
6866 task_context_task =
6867 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6868 }
6869
6870 cx.spawn_in(window, {
6871 let buffer = buffer.clone();
6872 async move |editor, cx| {
6873 let task_context = task_context_task.await;
6874
6875 let resolved_tasks =
6876 tasks
6877 .zip(task_context.clone())
6878 .map(|(tasks, task_context)| ResolvedTasks {
6879 templates: tasks.resolve(&task_context).collect(),
6880 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6881 multibuffer_point.row,
6882 tasks.column,
6883 )),
6884 });
6885 let debug_scenarios = editor
6886 .update(cx, |editor, cx| {
6887 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6888 })?
6889 .await;
6890 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6891 }
6892 })
6893 }
6894 };
6895
6896 cx.spawn_in(window, async move |editor, cx| {
6897 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6898 let code_actions = code_actions_task.await;
6899 let spawn_straight_away = quick_launch
6900 && resolved_tasks
6901 .as_ref()
6902 .is_some_and(|tasks| tasks.templates.len() == 1)
6903 && code_actions
6904 .as_ref()
6905 .is_none_or(|actions| actions.is_empty())
6906 && debug_scenarios.is_empty();
6907
6908 editor.update_in(cx, |editor, window, cx| {
6909 crate::hover_popover::hide_hover(editor, cx);
6910 let actions = CodeActionContents::new(
6911 resolved_tasks,
6912 code_actions,
6913 debug_scenarios,
6914 task_context.unwrap_or_default(),
6915 );
6916
6917 // Don't show the menu if there are no actions available
6918 if actions.is_empty() {
6919 cx.notify();
6920 return Task::ready(Ok(()));
6921 }
6922
6923 *editor.context_menu.borrow_mut() =
6924 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6925 buffer,
6926 actions,
6927 selected_item: Default::default(),
6928 scroll_handle: UniformListScrollHandle::default(),
6929 deployed_from,
6930 }));
6931 cx.notify();
6932 if spawn_straight_away
6933 && let Some(task) = editor.confirm_code_action(
6934 &ConfirmCodeAction { item_ix: Some(0) },
6935 window,
6936 cx,
6937 )
6938 {
6939 return task;
6940 }
6941
6942 Task::ready(Ok(()))
6943 })
6944 })
6945 .detach_and_log_err(cx);
6946 }
6947
6948 fn debug_scenarios(
6949 &mut self,
6950 resolved_tasks: &Option<ResolvedTasks>,
6951 buffer: &Entity<Buffer>,
6952 cx: &mut App,
6953 ) -> Task<Vec<task::DebugScenario>> {
6954 maybe!({
6955 let project = self.project()?;
6956 let dap_store = project.read(cx).dap_store();
6957 let mut scenarios = vec![];
6958 let resolved_tasks = resolved_tasks.as_ref()?;
6959 let buffer = buffer.read(cx);
6960 let language = buffer.language()?;
6961 let debug_adapter = LanguageSettings::for_buffer(&buffer, cx)
6962 .debuggers
6963 .first()
6964 .map(SharedString::from)
6965 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6966
6967 dap_store.update(cx, |dap_store, cx| {
6968 for (_, task) in &resolved_tasks.templates {
6969 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6970 task.original_task().clone(),
6971 debug_adapter.clone().into(),
6972 task.display_label().to_owned().into(),
6973 cx,
6974 );
6975 scenarios.push(maybe_scenario);
6976 }
6977 });
6978 Some(cx.background_spawn(async move {
6979 futures::future::join_all(scenarios)
6980 .await
6981 .into_iter()
6982 .flatten()
6983 .collect::<Vec<_>>()
6984 }))
6985 })
6986 .unwrap_or_else(|| Task::ready(vec![]))
6987 }
6988
6989 fn code_actions(
6990 &mut self,
6991 buffer_row: u32,
6992 window: &mut Window,
6993 cx: &mut Context<Self>,
6994 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6995 let mut task = self.code_actions_task.take();
6996 cx.spawn_in(window, async move |editor, cx| {
6997 while let Some(prev_task) = task {
6998 prev_task.await.log_err();
6999 task = editor
7000 .update(cx, |this, _| this.code_actions_task.take())
7001 .ok()?;
7002 }
7003
7004 editor
7005 .update(cx, |editor, cx| {
7006 editor
7007 .available_code_actions
7008 .clone()
7009 .and_then(|(location, code_actions)| {
7010 let snapshot = location.buffer.read(cx).snapshot();
7011 let point_range = location.range.to_point(&snapshot);
7012 let point_range = point_range.start.row..=point_range.end.row;
7013 if point_range.contains(&buffer_row) {
7014 Some(code_actions)
7015 } else {
7016 None
7017 }
7018 })
7019 })
7020 .ok()
7021 .flatten()
7022 })
7023 }
7024
7025 pub fn confirm_code_action(
7026 &mut self,
7027 action: &ConfirmCodeAction,
7028 window: &mut Window,
7029 cx: &mut Context<Self>,
7030 ) -> Option<Task<Result<()>>> {
7031 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
7032
7033 let actions_menu =
7034 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
7035 menu
7036 } else {
7037 return None;
7038 };
7039
7040 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
7041 let action = actions_menu.actions.get(action_ix)?;
7042 let title = action.label();
7043 let buffer = actions_menu.buffer;
7044 let workspace = self.workspace()?;
7045
7046 match action {
7047 CodeActionsItem::Task(task_source_kind, resolved_task) => {
7048 workspace.update(cx, |workspace, cx| {
7049 workspace.schedule_resolved_task(
7050 task_source_kind,
7051 resolved_task,
7052 false,
7053 window,
7054 cx,
7055 );
7056
7057 Some(Task::ready(Ok(())))
7058 })
7059 }
7060 CodeActionsItem::CodeAction {
7061 excerpt_id,
7062 action,
7063 provider,
7064 } => {
7065 let apply_code_action =
7066 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
7067 let workspace = workspace.downgrade();
7068 Some(cx.spawn_in(window, async move |editor, cx| {
7069 let project_transaction = apply_code_action.await?;
7070 Self::open_project_transaction(
7071 &editor,
7072 workspace,
7073 project_transaction,
7074 title,
7075 cx,
7076 )
7077 .await
7078 }))
7079 }
7080 CodeActionsItem::DebugScenario(scenario) => {
7081 let context = actions_menu.actions.context.into();
7082
7083 workspace.update(cx, |workspace, cx| {
7084 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
7085 workspace.start_debug_session(
7086 scenario,
7087 context,
7088 Some(buffer),
7089 None,
7090 window,
7091 cx,
7092 );
7093 });
7094 Some(Task::ready(Ok(())))
7095 }
7096 }
7097 }
7098
7099 fn open_transaction_for_hidden_buffers(
7100 workspace: Entity<Workspace>,
7101 transaction: ProjectTransaction,
7102 title: String,
7103 window: &mut Window,
7104 cx: &mut Context<Self>,
7105 ) {
7106 if transaction.0.is_empty() {
7107 return;
7108 }
7109
7110 let edited_buffers_already_open = {
7111 let other_editors: Vec<Entity<Editor>> = workspace
7112 .read(cx)
7113 .panes()
7114 .iter()
7115 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
7116 .filter(|editor| editor.entity_id() != cx.entity_id())
7117 .collect();
7118
7119 transaction.0.keys().all(|buffer| {
7120 other_editors.iter().any(|editor| {
7121 let multi_buffer = editor.read(cx).buffer();
7122 multi_buffer.read(cx).is_singleton()
7123 && multi_buffer
7124 .read(cx)
7125 .as_singleton()
7126 .map_or(false, |singleton| {
7127 singleton.entity_id() == buffer.entity_id()
7128 })
7129 })
7130 })
7131 };
7132 if !edited_buffers_already_open {
7133 let workspace = workspace.downgrade();
7134 cx.defer_in(window, move |_, window, cx| {
7135 cx.spawn_in(window, async move |editor, cx| {
7136 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
7137 .await
7138 .ok()
7139 })
7140 .detach();
7141 });
7142 }
7143 }
7144
7145 pub async fn open_project_transaction(
7146 editor: &WeakEntity<Editor>,
7147 workspace: WeakEntity<Workspace>,
7148 transaction: ProjectTransaction,
7149 title: String,
7150 cx: &mut AsyncWindowContext,
7151 ) -> Result<()> {
7152 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7153 cx.update(|_, cx| {
7154 entries.sort_unstable_by_key(|(buffer, _)| {
7155 buffer.read(cx).file().map(|f| f.path().clone())
7156 });
7157 })?;
7158 if entries.is_empty() {
7159 return Ok(());
7160 }
7161
7162 // If the project transaction's edits are all contained within this editor, then
7163 // avoid opening a new editor to display them.
7164
7165 if let [(buffer, transaction)] = &*entries {
7166 let excerpt = editor.update(cx, |editor, cx| {
7167 editor
7168 .buffer()
7169 .read(cx)
7170 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7171 })?;
7172 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7173 && excerpted_buffer == *buffer
7174 {
7175 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7176 let excerpt_range = excerpt_range.to_offset(buffer);
7177 buffer
7178 .edited_ranges_for_transaction::<usize>(transaction)
7179 .all(|range| {
7180 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7181 })
7182 });
7183
7184 if all_edits_within_excerpt {
7185 return Ok(());
7186 }
7187 }
7188 }
7189
7190 let mut ranges_to_highlight = Vec::new();
7191 let excerpt_buffer = cx.new(|cx| {
7192 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7193 for (buffer_handle, transaction) in &entries {
7194 let edited_ranges = buffer_handle
7195 .read(cx)
7196 .edited_ranges_for_transaction::<Point>(transaction)
7197 .collect::<Vec<_>>();
7198 let (ranges, _) = multibuffer.set_excerpts_for_path(
7199 PathKey::for_buffer(buffer_handle, cx),
7200 buffer_handle.clone(),
7201 edited_ranges,
7202 multibuffer_context_lines(cx),
7203 cx,
7204 );
7205
7206 ranges_to_highlight.extend(ranges);
7207 }
7208 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7209 multibuffer
7210 });
7211
7212 workspace.update_in(cx, |workspace, window, cx| {
7213 let project = workspace.project().clone();
7214 let editor =
7215 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7216 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7217 editor.update(cx, |editor, cx| {
7218 editor.highlight_background(
7219 HighlightKey::Editor,
7220 &ranges_to_highlight,
7221 |_, theme| theme.colors().editor_highlighted_line_background,
7222 cx,
7223 );
7224 });
7225 })?;
7226
7227 Ok(())
7228 }
7229
7230 pub fn clear_code_action_providers(&mut self) {
7231 self.code_action_providers.clear();
7232 self.available_code_actions.take();
7233 }
7234
7235 pub fn add_code_action_provider(
7236 &mut self,
7237 provider: Rc<dyn CodeActionProvider>,
7238 window: &mut Window,
7239 cx: &mut Context<Self>,
7240 ) {
7241 if self
7242 .code_action_providers
7243 .iter()
7244 .any(|existing_provider| existing_provider.id() == provider.id())
7245 {
7246 return;
7247 }
7248
7249 self.code_action_providers.push(provider);
7250 self.refresh_code_actions(window, cx);
7251 }
7252
7253 pub fn remove_code_action_provider(
7254 &mut self,
7255 id: Arc<str>,
7256 window: &mut Window,
7257 cx: &mut Context<Self>,
7258 ) {
7259 self.code_action_providers
7260 .retain(|provider| provider.id() != id);
7261 self.refresh_code_actions(window, cx);
7262 }
7263
7264 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7265 !self.code_action_providers.is_empty()
7266 && EditorSettings::get_global(cx).toolbar.code_actions
7267 }
7268
7269 pub fn has_available_code_actions(&self) -> bool {
7270 self.available_code_actions
7271 .as_ref()
7272 .is_some_and(|(_, actions)| !actions.is_empty())
7273 }
7274
7275 fn render_inline_code_actions(
7276 &self,
7277 icon_size: ui::IconSize,
7278 display_row: DisplayRow,
7279 is_active: bool,
7280 cx: &mut Context<Self>,
7281 ) -> AnyElement {
7282 let show_tooltip = !self.context_menu_visible();
7283 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7284 .icon_size(icon_size)
7285 .shape(ui::IconButtonShape::Square)
7286 .icon_color(ui::Color::Hidden)
7287 .toggle_state(is_active)
7288 .when(show_tooltip, |this| {
7289 this.tooltip({
7290 let focus_handle = self.focus_handle.clone();
7291 move |_window, cx| {
7292 Tooltip::for_action_in(
7293 "Toggle Code Actions",
7294 &ToggleCodeActions {
7295 deployed_from: None,
7296 quick_launch: false,
7297 },
7298 &focus_handle,
7299 cx,
7300 )
7301 }
7302 })
7303 })
7304 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7305 window.focus(&editor.focus_handle(cx), cx);
7306 editor.toggle_code_actions(
7307 &crate::actions::ToggleCodeActions {
7308 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7309 display_row,
7310 )),
7311 quick_launch: false,
7312 },
7313 window,
7314 cx,
7315 );
7316 }))
7317 .into_any_element()
7318 }
7319
7320 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7321 &self.context_menu
7322 }
7323
7324 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7325 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7326 cx.background_executor()
7327 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7328 .await;
7329
7330 let (start_buffer, start, _, end, newest_selection) = this
7331 .update(cx, |this, cx| {
7332 let newest_selection = this.selections.newest_anchor().clone();
7333 if newest_selection.head().diff_base_anchor.is_some() {
7334 return None;
7335 }
7336 let display_snapshot = this.display_snapshot(cx);
7337 let newest_selection_adjusted =
7338 this.selections.newest_adjusted(&display_snapshot);
7339 let buffer = this.buffer.read(cx);
7340
7341 let (start_buffer, start) =
7342 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7343 let (end_buffer, end) =
7344 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7345
7346 Some((start_buffer, start, end_buffer, end, newest_selection))
7347 })?
7348 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7349 .context(
7350 "Expected selection to lie in a single buffer when refreshing code actions",
7351 )?;
7352 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7353 let providers = this.code_action_providers.clone();
7354 let tasks = this
7355 .code_action_providers
7356 .iter()
7357 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7358 .collect::<Vec<_>>();
7359 (providers, tasks)
7360 })?;
7361
7362 let mut actions = Vec::new();
7363 for (provider, provider_actions) in
7364 providers.into_iter().zip(future::join_all(tasks).await)
7365 {
7366 if let Some(provider_actions) = provider_actions.log_err() {
7367 actions.extend(provider_actions.into_iter().map(|action| {
7368 AvailableCodeAction {
7369 excerpt_id: newest_selection.start.excerpt_id,
7370 action,
7371 provider: provider.clone(),
7372 }
7373 }));
7374 }
7375 }
7376
7377 this.update(cx, |this, cx| {
7378 this.available_code_actions = if actions.is_empty() {
7379 None
7380 } else {
7381 Some((
7382 Location {
7383 buffer: start_buffer,
7384 range: start..end,
7385 },
7386 actions.into(),
7387 ))
7388 };
7389 cx.notify();
7390 })
7391 }));
7392 }
7393
7394 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7395 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7396 self.show_git_blame_inline = false;
7397
7398 self.show_git_blame_inline_delay_task =
7399 Some(cx.spawn_in(window, async move |this, cx| {
7400 cx.background_executor().timer(delay).await;
7401
7402 this.update(cx, |this, cx| {
7403 this.show_git_blame_inline = true;
7404 cx.notify();
7405 })
7406 .log_err();
7407 }));
7408 }
7409 }
7410
7411 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7412 let snapshot = self.snapshot(window, cx);
7413 let cursor = self
7414 .selections
7415 .newest::<Point>(&snapshot.display_snapshot)
7416 .head();
7417 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7418 else {
7419 return;
7420 };
7421
7422 if self.blame.is_none() {
7423 self.start_git_blame(true, window, cx);
7424 }
7425 let Some(blame) = self.blame.as_ref() else {
7426 return;
7427 };
7428
7429 let row_info = RowInfo {
7430 buffer_id: Some(buffer.remote_id()),
7431 buffer_row: Some(point.row),
7432 ..Default::default()
7433 };
7434 let Some((buffer, blame_entry)) = blame
7435 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7436 .flatten()
7437 else {
7438 return;
7439 };
7440
7441 let anchor = self.selections.newest_anchor().head();
7442 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7443 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7444 self.show_blame_popover(
7445 buffer,
7446 &blame_entry,
7447 position + last_bounds.origin,
7448 true,
7449 cx,
7450 );
7451 };
7452 }
7453
7454 fn show_blame_popover(
7455 &mut self,
7456 buffer: BufferId,
7457 blame_entry: &BlameEntry,
7458 position: gpui::Point<Pixels>,
7459 ignore_timeout: bool,
7460 cx: &mut Context<Self>,
7461 ) {
7462 if let Some(state) = &mut self.inline_blame_popover {
7463 state.hide_task.take();
7464 } else {
7465 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7466 let blame_entry = blame_entry.clone();
7467 let show_task = cx.spawn(async move |editor, cx| {
7468 if !ignore_timeout {
7469 cx.background_executor()
7470 .timer(std::time::Duration::from_millis(blame_popover_delay))
7471 .await;
7472 }
7473 editor
7474 .update(cx, |editor, cx| {
7475 editor.inline_blame_popover_show_task.take();
7476 let Some(blame) = editor.blame.as_ref() else {
7477 return;
7478 };
7479 let blame = blame.read(cx);
7480 let details = blame.details_for_entry(buffer, &blame_entry);
7481 let markdown = cx.new(|cx| {
7482 Markdown::new(
7483 details
7484 .as_ref()
7485 .map(|message| message.message.clone())
7486 .unwrap_or_default(),
7487 None,
7488 None,
7489 cx,
7490 )
7491 });
7492 editor.inline_blame_popover = Some(InlineBlamePopover {
7493 position,
7494 hide_task: None,
7495 popover_bounds: None,
7496 popover_state: InlineBlamePopoverState {
7497 scroll_handle: ScrollHandle::new(),
7498 commit_message: details,
7499 markdown,
7500 },
7501 keyboard_grace: ignore_timeout,
7502 });
7503 cx.notify();
7504 })
7505 .ok();
7506 });
7507 self.inline_blame_popover_show_task = Some(show_task);
7508 }
7509 }
7510
7511 pub fn has_mouse_context_menu(&self) -> bool {
7512 self.mouse_context_menu.is_some()
7513 }
7514
7515 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7516 self.inline_blame_popover_show_task.take();
7517 if let Some(state) = &mut self.inline_blame_popover {
7518 let hide_task = cx.spawn(async move |editor, cx| {
7519 if !ignore_timeout {
7520 cx.background_executor()
7521 .timer(std::time::Duration::from_millis(100))
7522 .await;
7523 }
7524 editor
7525 .update(cx, |editor, cx| {
7526 editor.inline_blame_popover.take();
7527 cx.notify();
7528 })
7529 .ok();
7530 });
7531 state.hide_task = Some(hide_task);
7532 true
7533 } else {
7534 false
7535 }
7536 }
7537
7538 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7539 if self.pending_rename.is_some() {
7540 return None;
7541 }
7542
7543 let provider = self.semantics_provider.clone()?;
7544 let buffer = self.buffer.read(cx);
7545 let newest_selection = self.selections.newest_anchor().clone();
7546 let cursor_position = newest_selection.head();
7547 let (cursor_buffer, cursor_buffer_position) =
7548 buffer.text_anchor_for_position(cursor_position, cx)?;
7549 let (tail_buffer, tail_buffer_position) =
7550 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7551 if cursor_buffer != tail_buffer {
7552 return None;
7553 }
7554
7555 let snapshot = cursor_buffer.read(cx).snapshot();
7556 let word_ranges = cx.background_spawn(async move {
7557 // this might look odd to put on the background thread, but
7558 // `surrounding_word` can be quite expensive as it calls into
7559 // tree-sitter language scopes
7560 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7561 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7562 (start_word_range, end_word_range)
7563 });
7564
7565 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7566 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7567 let (start_word_range, end_word_range) = word_ranges.await;
7568 if start_word_range != end_word_range {
7569 this.update(cx, |this, cx| {
7570 this.document_highlights_task.take();
7571 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7572 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7573 })
7574 .ok();
7575 return;
7576 }
7577 cx.background_executor()
7578 .timer(Duration::from_millis(debounce))
7579 .await;
7580
7581 let highlights = if let Some(highlights) = cx.update(|cx| {
7582 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7583 }) {
7584 highlights.await.log_err()
7585 } else {
7586 None
7587 };
7588
7589 if let Some(highlights) = highlights {
7590 this.update(cx, |this, cx| {
7591 if this.pending_rename.is_some() {
7592 return;
7593 }
7594
7595 let buffer = this.buffer.read(cx);
7596 if buffer
7597 .text_anchor_for_position(cursor_position, cx)
7598 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7599 {
7600 return;
7601 }
7602
7603 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7604 let mut write_ranges = Vec::new();
7605 let mut read_ranges = Vec::new();
7606 for highlight in highlights {
7607 let buffer_id = cursor_buffer.read(cx).remote_id();
7608 for (excerpt_id, _, excerpt_range) in
7609 buffer.excerpts_for_buffer(buffer_id, cx)
7610 {
7611 let start = highlight
7612 .range
7613 .start
7614 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7615 let end = highlight
7616 .range
7617 .end
7618 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7619 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7620 continue;
7621 }
7622
7623 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7624 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7625 write_ranges.push(range);
7626 } else {
7627 read_ranges.push(range);
7628 }
7629 }
7630 }
7631
7632 this.highlight_background(
7633 HighlightKey::DocumentHighlightRead,
7634 &read_ranges,
7635 |_, theme| theme.colors().editor_document_highlight_read_background,
7636 cx,
7637 );
7638 this.highlight_background(
7639 HighlightKey::DocumentHighlightWrite,
7640 &write_ranges,
7641 |_, theme| theme.colors().editor_document_highlight_write_background,
7642 cx,
7643 );
7644 cx.notify();
7645 })
7646 .log_err();
7647 }
7648 }));
7649 None
7650 }
7651
7652 fn prepare_highlight_query_from_selection(
7653 &mut self,
7654 snapshot: &DisplaySnapshot,
7655 cx: &mut Context<Editor>,
7656 ) -> Option<(String, Range<Anchor>)> {
7657 if matches!(self.mode, EditorMode::SingleLine) {
7658 return None;
7659 }
7660 if !EditorSettings::get_global(cx).selection_highlight {
7661 return None;
7662 }
7663 if self.selections.count() != 1 || self.selections.line_mode() {
7664 return None;
7665 }
7666 let selection = self.selections.newest::<Point>(&snapshot);
7667 // If the selection spans multiple rows OR it is empty
7668 if selection.start.row != selection.end.row
7669 || selection.start.column == selection.end.column
7670 {
7671 return None;
7672 }
7673 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7674 let query = snapshot
7675 .buffer_snapshot()
7676 .text_for_range(selection_anchor_range.clone())
7677 .collect::<String>();
7678 if query.trim().is_empty() {
7679 return None;
7680 }
7681 Some((query, selection_anchor_range))
7682 }
7683
7684 #[ztracing::instrument(skip_all)]
7685 fn update_selection_occurrence_highlights(
7686 &mut self,
7687 multi_buffer_snapshot: MultiBufferSnapshot,
7688 query_text: String,
7689 query_range: Range<Anchor>,
7690 multi_buffer_range_to_query: Range<Point>,
7691 use_debounce: bool,
7692 window: &mut Window,
7693 cx: &mut Context<Editor>,
7694 ) -> Task<()> {
7695 cx.spawn_in(window, async move |editor, cx| {
7696 if use_debounce {
7697 cx.background_executor()
7698 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7699 .await;
7700 }
7701 let match_task = cx.background_spawn(async move {
7702 let buffer_ranges = multi_buffer_snapshot
7703 .range_to_buffer_ranges(
7704 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7705 )
7706 .into_iter()
7707 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7708 let mut match_ranges = Vec::new();
7709 let Ok(regex) = project::search::SearchQuery::text(
7710 query_text,
7711 false,
7712 false,
7713 false,
7714 Default::default(),
7715 Default::default(),
7716 false,
7717 None,
7718 ) else {
7719 return Vec::default();
7720 };
7721 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7722 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7723 match_ranges.extend(
7724 regex
7725 .search(
7726 buffer_snapshot,
7727 Some(search_range.start.0..search_range.end.0),
7728 )
7729 .await
7730 .into_iter()
7731 .filter_map(|match_range| {
7732 let match_start = buffer_snapshot
7733 .anchor_after(search_range.start + match_range.start);
7734 let match_end = buffer_snapshot
7735 .anchor_before(search_range.start + match_range.end);
7736 let match_anchor_range =
7737 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7738 (match_anchor_range != query_range).then_some(match_anchor_range)
7739 }),
7740 );
7741 }
7742 match_ranges
7743 });
7744 let match_ranges = match_task.await;
7745 editor
7746 .update_in(cx, |editor, _, cx| {
7747 if use_debounce {
7748 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7749 editor.debounced_selection_highlight_complete = true;
7750 } else if editor.debounced_selection_highlight_complete {
7751 return;
7752 }
7753 if !match_ranges.is_empty() {
7754 editor.highlight_background(
7755 HighlightKey::SelectedTextHighlight,
7756 &match_ranges,
7757 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7758 cx,
7759 )
7760 }
7761 })
7762 .log_err();
7763 })
7764 }
7765
7766 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7767 struct NewlineFold;
7768 let type_id = std::any::TypeId::of::<NewlineFold>();
7769 if !self.mode.is_single_line() {
7770 return;
7771 }
7772 let snapshot = self.snapshot(window, cx);
7773 if snapshot.buffer_snapshot().max_point().row == 0 {
7774 return;
7775 }
7776 let task = cx.background_spawn(async move {
7777 let new_newlines = snapshot
7778 .buffer_chars_at(MultiBufferOffset(0))
7779 .filter_map(|(c, i)| {
7780 if c == '\n' {
7781 Some(
7782 snapshot.buffer_snapshot().anchor_after(i)
7783 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7784 )
7785 } else {
7786 None
7787 }
7788 })
7789 .collect::<Vec<_>>();
7790 let existing_newlines = snapshot
7791 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7792 .filter_map(|fold| {
7793 if fold.placeholder.type_tag == Some(type_id) {
7794 Some(fold.range.start..fold.range.end)
7795 } else {
7796 None
7797 }
7798 })
7799 .collect::<Vec<_>>();
7800
7801 (new_newlines, existing_newlines)
7802 });
7803 self.folding_newlines = cx.spawn(async move |this, cx| {
7804 let (new_newlines, existing_newlines) = task.await;
7805 if new_newlines == existing_newlines {
7806 return;
7807 }
7808 let placeholder = FoldPlaceholder {
7809 render: Arc::new(move |_, _, cx| {
7810 div()
7811 .bg(cx.theme().status().hint_background)
7812 .border_b_1()
7813 .size_full()
7814 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7815 .border_color(cx.theme().status().hint)
7816 .child("\\n")
7817 .into_any()
7818 }),
7819 constrain_width: false,
7820 merge_adjacent: false,
7821 type_tag: Some(type_id),
7822 collapsed_text: None,
7823 };
7824 let creases = new_newlines
7825 .into_iter()
7826 .map(|range| Crease::simple(range, placeholder.clone()))
7827 .collect();
7828 this.update(cx, |this, cx| {
7829 this.display_map.update(cx, |display_map, cx| {
7830 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7831 display_map.fold(creases, cx);
7832 });
7833 })
7834 .ok();
7835 });
7836 }
7837
7838 #[ztracing::instrument(skip_all)]
7839 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7840 if !self.lsp_data_enabled() {
7841 return;
7842 }
7843 let cursor = self.selections.newest_anchor().head();
7844 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7845
7846 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7847 self.outline_symbols_at_cursor =
7848 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7849 cx.emit(EditorEvent::OutlineSymbolsChanged);
7850 cx.notify();
7851 } else {
7852 let syntax = cx.theme().syntax().clone();
7853 let background_task = cx.background_spawn(async move {
7854 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7855 });
7856 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7857 cx.spawn(async move |this, cx| {
7858 let symbols = background_task.await;
7859 this.update(cx, |this, cx| {
7860 this.outline_symbols_at_cursor = symbols;
7861 cx.emit(EditorEvent::OutlineSymbolsChanged);
7862 cx.notify();
7863 })
7864 .ok();
7865 });
7866 }
7867 }
7868
7869 #[ztracing::instrument(skip_all)]
7870 fn refresh_selected_text_highlights(
7871 &mut self,
7872 snapshot: &DisplaySnapshot,
7873 on_buffer_edit: bool,
7874 window: &mut Window,
7875 cx: &mut Context<Editor>,
7876 ) {
7877 let Some((query_text, query_range)) =
7878 self.prepare_highlight_query_from_selection(snapshot, cx)
7879 else {
7880 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7881 self.quick_selection_highlight_task.take();
7882 self.debounced_selection_highlight_task.take();
7883 self.debounced_selection_highlight_complete = false;
7884 return;
7885 };
7886 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7887 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7888 let query_changed = self
7889 .quick_selection_highlight_task
7890 .as_ref()
7891 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7892 if query_changed {
7893 self.debounced_selection_highlight_complete = false;
7894 }
7895 if on_buffer_edit || query_changed {
7896 self.quick_selection_highlight_task = Some((
7897 query_range.clone(),
7898 self.update_selection_occurrence_highlights(
7899 snapshot.buffer.clone(),
7900 query_text.clone(),
7901 query_range.clone(),
7902 self.multi_buffer_visible_range(&display_snapshot, cx),
7903 false,
7904 window,
7905 cx,
7906 ),
7907 ));
7908 }
7909 if on_buffer_edit
7910 || self
7911 .debounced_selection_highlight_task
7912 .as_ref()
7913 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7914 {
7915 let multi_buffer_start = multi_buffer_snapshot
7916 .anchor_before(MultiBufferOffset(0))
7917 .to_point(&multi_buffer_snapshot);
7918 let multi_buffer_end = multi_buffer_snapshot
7919 .anchor_after(multi_buffer_snapshot.len())
7920 .to_point(&multi_buffer_snapshot);
7921 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7922 self.debounced_selection_highlight_task = Some((
7923 query_range.clone(),
7924 self.update_selection_occurrence_highlights(
7925 snapshot.buffer.clone(),
7926 query_text,
7927 query_range,
7928 multi_buffer_full_range,
7929 true,
7930 window,
7931 cx,
7932 ),
7933 ));
7934 }
7935 }
7936
7937 pub fn multi_buffer_visible_range(
7938 &self,
7939 display_snapshot: &DisplaySnapshot,
7940 cx: &App,
7941 ) -> Range<Point> {
7942 let visible_start = self
7943 .scroll_manager
7944 .native_anchor(display_snapshot, cx)
7945 .anchor
7946 .to_point(display_snapshot.buffer_snapshot())
7947 .to_display_point(display_snapshot);
7948
7949 let mut target_end = visible_start;
7950 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
7951
7952 visible_start.to_point(display_snapshot)
7953 ..display_snapshot
7954 .clip_point(target_end, Bias::Right)
7955 .to_point(display_snapshot)
7956 }
7957
7958 pub fn refresh_edit_prediction(
7959 &mut self,
7960 debounce: bool,
7961 user_requested: bool,
7962 window: &mut Window,
7963 cx: &mut Context<Self>,
7964 ) -> Option<()> {
7965 if self.leader_id.is_some() {
7966 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7967 return None;
7968 }
7969
7970 let cursor = self.selections.newest_anchor().head();
7971 let (buffer, cursor_buffer_position) =
7972 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7973
7974 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7975 return None;
7976 }
7977
7978 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7979 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7980 return None;
7981 }
7982
7983 self.update_visible_edit_prediction(window, cx);
7984
7985 if !user_requested
7986 && (!self.should_show_edit_predictions()
7987 || !self.is_focused(window)
7988 || buffer.read(cx).is_empty())
7989 {
7990 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7991 return None;
7992 }
7993
7994 self.edit_prediction_provider()?
7995 .refresh(buffer, cursor_buffer_position, debounce, cx);
7996 Some(())
7997 }
7998
7999 fn show_edit_predictions_in_menu(&self) -> bool {
8000 match self.edit_prediction_settings {
8001 EditPredictionSettings::Disabled => false,
8002 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
8003 }
8004 }
8005
8006 pub fn edit_predictions_enabled(&self) -> bool {
8007 match self.edit_prediction_settings {
8008 EditPredictionSettings::Disabled => false,
8009 EditPredictionSettings::Enabled { .. } => true,
8010 }
8011 }
8012
8013 fn edit_prediction_requires_modifier(&self) -> bool {
8014 match self.edit_prediction_settings {
8015 EditPredictionSettings::Disabled => false,
8016 EditPredictionSettings::Enabled {
8017 preview_requires_modifier,
8018 ..
8019 } => preview_requires_modifier,
8020 }
8021 }
8022
8023 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
8024 if self.edit_prediction_provider.is_none() {
8025 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8026 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8027 return;
8028 }
8029
8030 let selection = self.selections.newest_anchor();
8031 let cursor = selection.head();
8032
8033 if let Some((buffer, cursor_buffer_position)) =
8034 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8035 {
8036 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8037 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8038 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8039 return;
8040 }
8041 self.edit_prediction_settings =
8042 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8043 }
8044 }
8045
8046 fn edit_prediction_settings_at_position(
8047 &self,
8048 buffer: &Entity<Buffer>,
8049 buffer_position: language::Anchor,
8050 cx: &App,
8051 ) -> EditPredictionSettings {
8052 if !self.mode.is_full()
8053 || !self.show_edit_predictions_override.unwrap_or(true)
8054 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
8055 {
8056 return EditPredictionSettings::Disabled;
8057 }
8058
8059 if !LanguageSettings::for_buffer(&buffer.read(cx), cx).show_edit_predictions {
8060 return EditPredictionSettings::Disabled;
8061 };
8062
8063 let by_provider = matches!(
8064 self.menu_edit_predictions_policy,
8065 MenuEditPredictionsPolicy::ByProvider
8066 );
8067
8068 let show_in_menu = by_provider
8069 && self
8070 .edit_prediction_provider
8071 .as_ref()
8072 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
8073
8074 let file = buffer.read(cx).file();
8075 let preview_requires_modifier =
8076 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
8077
8078 EditPredictionSettings::Enabled {
8079 show_in_menu,
8080 preview_requires_modifier,
8081 }
8082 }
8083
8084 fn should_show_edit_predictions(&self) -> bool {
8085 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
8086 }
8087
8088 pub fn edit_prediction_preview_is_active(&self) -> bool {
8089 matches!(
8090 self.edit_prediction_preview,
8091 EditPredictionPreview::Active { .. }
8092 )
8093 }
8094
8095 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
8096 let cursor = self.selections.newest_anchor().head();
8097 if let Some((buffer, cursor_position)) =
8098 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8099 {
8100 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
8101 } else {
8102 false
8103 }
8104 }
8105
8106 pub fn supports_minimap(&self, cx: &App) -> bool {
8107 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
8108 }
8109
8110 fn edit_predictions_enabled_in_buffer(
8111 &self,
8112 buffer: &Entity<Buffer>,
8113 buffer_position: language::Anchor,
8114 cx: &App,
8115 ) -> bool {
8116 maybe!({
8117 if self.read_only(cx) || self.leader_id.is_some() {
8118 return Some(false);
8119 }
8120 let provider = self.edit_prediction_provider()?;
8121 if !provider.is_enabled(buffer, buffer_position, cx) {
8122 return Some(false);
8123 }
8124 let buffer = buffer.read(cx);
8125 let Some(file) = buffer.file() else {
8126 return Some(true);
8127 };
8128 let settings = all_language_settings(Some(file), cx);
8129 Some(settings.edit_predictions_enabled_for_file(file, cx))
8130 })
8131 .unwrap_or(false)
8132 }
8133
8134 pub fn show_edit_prediction(
8135 &mut self,
8136 _: &ShowEditPrediction,
8137 window: &mut Window,
8138 cx: &mut Context<Self>,
8139 ) {
8140 if !self.has_active_edit_prediction() {
8141 self.refresh_edit_prediction(false, true, window, cx);
8142 return;
8143 }
8144
8145 self.update_visible_edit_prediction(window, cx);
8146 }
8147
8148 pub fn display_cursor_names(
8149 &mut self,
8150 _: &DisplayCursorNames,
8151 window: &mut Window,
8152 cx: &mut Context<Self>,
8153 ) {
8154 self.show_cursor_names(window, cx);
8155 }
8156
8157 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8158 self.show_cursor_names = true;
8159 cx.notify();
8160 cx.spawn_in(window, async move |this, cx| {
8161 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8162 this.update(cx, |this, cx| {
8163 this.show_cursor_names = false;
8164 cx.notify()
8165 })
8166 .ok()
8167 })
8168 .detach();
8169 }
8170
8171 pub fn accept_partial_edit_prediction(
8172 &mut self,
8173 granularity: EditPredictionGranularity,
8174 window: &mut Window,
8175 cx: &mut Context<Self>,
8176 ) {
8177 if self.show_edit_predictions_in_menu() {
8178 self.hide_context_menu(window, cx);
8179 }
8180
8181 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8182 return;
8183 };
8184
8185 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8186 return;
8187 }
8188
8189 match &active_edit_prediction.completion {
8190 EditPrediction::MoveWithin { target, .. } => {
8191 let target = *target;
8192
8193 if matches!(granularity, EditPredictionGranularity::Full) {
8194 if let Some(position_map) = &self.last_position_map {
8195 let target_row = target.to_display_point(&position_map.snapshot).row();
8196 let is_visible = position_map.visible_row_range.contains(&target_row);
8197
8198 if is_visible || !self.edit_prediction_requires_modifier() {
8199 self.unfold_ranges(&[target..target], true, false, cx);
8200 self.change_selections(
8201 SelectionEffects::scroll(Autoscroll::newest()),
8202 window,
8203 cx,
8204 |selections| {
8205 selections.select_anchor_ranges([target..target]);
8206 },
8207 );
8208 self.clear_row_highlights::<EditPredictionPreview>();
8209 self.edit_prediction_preview
8210 .set_previous_scroll_position(None);
8211 } else {
8212 // Highlight and request scroll
8213 self.edit_prediction_preview
8214 .set_previous_scroll_position(Some(
8215 position_map.snapshot.scroll_anchor,
8216 ));
8217 self.highlight_rows::<EditPredictionPreview>(
8218 target..target,
8219 cx.theme().colors().editor_highlighted_line_background,
8220 RowHighlightOptions {
8221 autoscroll: true,
8222 ..Default::default()
8223 },
8224 cx,
8225 );
8226 self.request_autoscroll(Autoscroll::fit(), cx);
8227 }
8228 }
8229 } else {
8230 self.change_selections(
8231 SelectionEffects::scroll(Autoscroll::newest()),
8232 window,
8233 cx,
8234 |selections| {
8235 selections.select_anchor_ranges([target..target]);
8236 },
8237 );
8238 }
8239 }
8240 EditPrediction::MoveOutside { snapshot, target } => {
8241 if let Some(workspace) = self.workspace() {
8242 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8243 .detach_and_log_err(cx);
8244 }
8245 }
8246 EditPrediction::Edit {
8247 edits,
8248 cursor_position,
8249 ..
8250 } => {
8251 self.report_edit_prediction_event(
8252 active_edit_prediction.completion_id.clone(),
8253 true,
8254 cx,
8255 );
8256
8257 match granularity {
8258 EditPredictionGranularity::Full => {
8259 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8260
8261 // Compute fallback cursor position BEFORE applying the edit,
8262 // so the anchor tracks through the edit correctly
8263 let fallback_cursor_target = {
8264 let snapshot = self.buffer.read(cx).snapshot(cx);
8265 edits.last().unwrap().0.end.bias_right(&snapshot)
8266 };
8267
8268 self.buffer.update(cx, |buffer, cx| {
8269 buffer.edit(edits.iter().cloned(), None, cx)
8270 });
8271
8272 if let Some(provider) = self.edit_prediction_provider() {
8273 provider.accept(cx);
8274 }
8275
8276 // Resolve cursor position after the edit is applied
8277 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8278 // The anchor tracks through the edit, then we add the offset
8279 let snapshot = self.buffer.read(cx).snapshot(cx);
8280 let base_offset = anchor.to_offset(&snapshot).0;
8281 let target_offset =
8282 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8283 snapshot.anchor_after(target_offset)
8284 } else {
8285 fallback_cursor_target
8286 };
8287
8288 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8289 s.select_anchor_ranges([cursor_target..cursor_target]);
8290 });
8291
8292 let selections = self.selections.disjoint_anchors_arc();
8293 if let Some(transaction_id_now) =
8294 self.buffer.read(cx).last_transaction_id(cx)
8295 {
8296 if transaction_id_prev != Some(transaction_id_now) {
8297 self.selection_history
8298 .insert_transaction(transaction_id_now, selections);
8299 }
8300 }
8301
8302 self.update_visible_edit_prediction(window, cx);
8303 if self.active_edit_prediction.is_none() {
8304 self.refresh_edit_prediction(true, true, window, cx);
8305 }
8306 cx.notify();
8307 }
8308 _ => {
8309 let snapshot = self.buffer.read(cx).snapshot(cx);
8310 let cursor_offset = self
8311 .selections
8312 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8313 .head();
8314
8315 let insertion = edits.iter().find_map(|(range, text)| {
8316 let range = range.to_offset(&snapshot);
8317 if range.is_empty() && range.start == cursor_offset {
8318 Some(text)
8319 } else {
8320 None
8321 }
8322 });
8323
8324 if let Some(text) = insertion {
8325 let text_to_insert = match granularity {
8326 EditPredictionGranularity::Word => {
8327 let mut partial = text
8328 .chars()
8329 .by_ref()
8330 .take_while(|c| c.is_alphabetic())
8331 .collect::<String>();
8332 if partial.is_empty() {
8333 partial = text
8334 .chars()
8335 .by_ref()
8336 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8337 .collect::<String>();
8338 }
8339 partial
8340 }
8341 EditPredictionGranularity::Line => {
8342 if let Some(line) = text.split_inclusive('\n').next() {
8343 line.to_string()
8344 } else {
8345 text.to_string()
8346 }
8347 }
8348 EditPredictionGranularity::Full => unreachable!(),
8349 };
8350
8351 cx.emit(EditorEvent::InputHandled {
8352 utf16_range_to_replace: None,
8353 text: text_to_insert.clone().into(),
8354 });
8355
8356 self.replace_selections(&text_to_insert, None, window, cx, false);
8357 self.refresh_edit_prediction(true, true, window, cx);
8358 cx.notify();
8359 } else {
8360 self.accept_partial_edit_prediction(
8361 EditPredictionGranularity::Full,
8362 window,
8363 cx,
8364 );
8365 }
8366 }
8367 }
8368 }
8369 }
8370 }
8371
8372 pub fn accept_next_word_edit_prediction(
8373 &mut self,
8374 _: &AcceptNextWordEditPrediction,
8375 window: &mut Window,
8376 cx: &mut Context<Self>,
8377 ) {
8378 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8379 }
8380
8381 pub fn accept_next_line_edit_prediction(
8382 &mut self,
8383 _: &AcceptNextLineEditPrediction,
8384 window: &mut Window,
8385 cx: &mut Context<Self>,
8386 ) {
8387 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8388 }
8389
8390 pub fn accept_edit_prediction(
8391 &mut self,
8392 _: &AcceptEditPrediction,
8393 window: &mut Window,
8394 cx: &mut Context<Self>,
8395 ) {
8396 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8397 }
8398
8399 fn discard_edit_prediction(
8400 &mut self,
8401 reason: EditPredictionDiscardReason,
8402 cx: &mut Context<Self>,
8403 ) -> bool {
8404 if reason == EditPredictionDiscardReason::Rejected {
8405 let completion_id = self
8406 .active_edit_prediction
8407 .as_ref()
8408 .and_then(|active_completion| active_completion.completion_id.clone());
8409
8410 self.report_edit_prediction_event(completion_id, false, cx);
8411 }
8412
8413 if let Some(provider) = self.edit_prediction_provider() {
8414 provider.discard(reason, cx);
8415 }
8416
8417 self.take_active_edit_prediction(reason == EditPredictionDiscardReason::Ignored, cx)
8418 }
8419
8420 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8421 let Some(provider) = self.edit_prediction_provider() else {
8422 return;
8423 };
8424
8425 let Some((_, buffer, _)) = self
8426 .buffer
8427 .read(cx)
8428 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8429 else {
8430 return;
8431 };
8432
8433 let extension = buffer
8434 .read(cx)
8435 .file()
8436 .and_then(|file| Some(file.path().extension()?.to_string()));
8437
8438 let event_type = match accepted {
8439 true => "Edit Prediction Accepted",
8440 false => "Edit Prediction Discarded",
8441 };
8442 telemetry::event!(
8443 event_type,
8444 provider = provider.name(),
8445 prediction_id = id,
8446 suggestion_accepted = accepted,
8447 file_extension = extension,
8448 );
8449 }
8450
8451 fn open_editor_at_anchor(
8452 snapshot: &language::BufferSnapshot,
8453 target: language::Anchor,
8454 workspace: &Entity<Workspace>,
8455 window: &mut Window,
8456 cx: &mut App,
8457 ) -> Task<Result<()>> {
8458 workspace.update(cx, |workspace, cx| {
8459 let path = snapshot.file().map(|file| file.full_path(cx));
8460 let Some(path) =
8461 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8462 else {
8463 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8464 };
8465 let target = text::ToPoint::to_point(&target, snapshot);
8466 let item = workspace.open_path(path, None, true, window, cx);
8467 window.spawn(cx, async move |cx| {
8468 let Some(editor) = item.await?.downcast::<Editor>() else {
8469 return Ok(());
8470 };
8471 editor
8472 .update_in(cx, |editor, window, cx| {
8473 editor.go_to_singleton_buffer_point(target, window, cx);
8474 })
8475 .ok();
8476 anyhow::Ok(())
8477 })
8478 })
8479 }
8480
8481 pub fn has_active_edit_prediction(&self) -> bool {
8482 self.active_edit_prediction.is_some()
8483 }
8484
8485 fn take_active_edit_prediction(
8486 &mut self,
8487 preserve_stale_in_menu: bool,
8488 cx: &mut Context<Self>,
8489 ) -> bool {
8490 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8491 if !preserve_stale_in_menu {
8492 self.stale_edit_prediction_in_menu = None;
8493 }
8494 return false;
8495 };
8496
8497 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8498 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8499 self.stale_edit_prediction_in_menu =
8500 preserve_stale_in_menu.then_some(active_edit_prediction);
8501 true
8502 }
8503
8504 /// Returns true when we're displaying the edit prediction popover below the cursor
8505 /// like we are not previewing and the LSP autocomplete menu is visible
8506 /// or we are in `when_holding_modifier` mode.
8507 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8508 if self.edit_prediction_preview_is_active()
8509 || !self.show_edit_predictions_in_menu()
8510 || !self.edit_predictions_enabled()
8511 {
8512 return false;
8513 }
8514
8515 if self.has_visible_completions_menu() {
8516 return true;
8517 }
8518
8519 has_completion && self.edit_prediction_requires_modifier()
8520 }
8521
8522 fn handle_modifiers_changed(
8523 &mut self,
8524 modifiers: Modifiers,
8525 position_map: &PositionMap,
8526 window: &mut Window,
8527 cx: &mut Context<Self>,
8528 ) {
8529 self.update_edit_prediction_settings(cx);
8530
8531 // Ensure that the edit prediction preview is updated, even when not
8532 // enabled, if there's an active edit prediction preview.
8533 if self.show_edit_predictions_in_menu()
8534 || self.edit_prediction_requires_modifier()
8535 || matches!(
8536 self.edit_prediction_preview,
8537 EditPredictionPreview::Active { .. }
8538 )
8539 {
8540 self.update_edit_prediction_preview(&modifiers, window, cx);
8541 }
8542
8543 self.update_selection_mode(&modifiers, position_map, window, cx);
8544
8545 let mouse_position = window.mouse_position();
8546 if !position_map.text_hitbox.is_hovered(window) {
8547 return;
8548 }
8549
8550 self.update_hovered_link(
8551 position_map.point_for_position(mouse_position),
8552 Some(mouse_position),
8553 &position_map.snapshot,
8554 modifiers,
8555 window,
8556 cx,
8557 )
8558 }
8559
8560 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8561 match EditorSettings::get_global(cx).multi_cursor_modifier {
8562 MultiCursorModifier::Alt => modifiers.secondary(),
8563 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8564 }
8565 }
8566
8567 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8568 match EditorSettings::get_global(cx).multi_cursor_modifier {
8569 MultiCursorModifier::Alt => modifiers.alt,
8570 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8571 }
8572 }
8573
8574 fn columnar_selection_mode(
8575 modifiers: &Modifiers,
8576 cx: &mut Context<Self>,
8577 ) -> Option<ColumnarMode> {
8578 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8579 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8580 Some(ColumnarMode::FromMouse)
8581 } else if Self::is_alt_pressed(modifiers, cx) {
8582 Some(ColumnarMode::FromSelection)
8583 } else {
8584 None
8585 }
8586 } else {
8587 None
8588 }
8589 }
8590
8591 fn update_selection_mode(
8592 &mut self,
8593 modifiers: &Modifiers,
8594 position_map: &PositionMap,
8595 window: &mut Window,
8596 cx: &mut Context<Self>,
8597 ) {
8598 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8599 return;
8600 };
8601 if self.selections.pending_anchor().is_none() {
8602 return;
8603 }
8604
8605 let mouse_position = window.mouse_position();
8606 let point_for_position = position_map.point_for_position(mouse_position);
8607 let position = point_for_position.previous_valid;
8608
8609 self.select(
8610 SelectPhase::BeginColumnar {
8611 position,
8612 reset: false,
8613 mode,
8614 goal_column: point_for_position.exact_unclipped.column(),
8615 },
8616 window,
8617 cx,
8618 );
8619 }
8620
8621 fn update_edit_prediction_preview(
8622 &mut self,
8623 modifiers: &Modifiers,
8624 window: &mut Window,
8625 cx: &mut Context<Self>,
8626 ) {
8627 let modifiers_held = self.edit_prediction_preview_modifiers_held(modifiers, window, cx);
8628
8629 if modifiers_held {
8630 if matches!(
8631 self.edit_prediction_preview,
8632 EditPredictionPreview::Inactive { .. }
8633 ) {
8634 self.edit_prediction_preview = EditPredictionPreview::Active {
8635 previous_scroll_position: None,
8636 since: Instant::now(),
8637 };
8638
8639 self.update_visible_edit_prediction(window, cx);
8640 cx.notify();
8641 }
8642 } else if let EditPredictionPreview::Active {
8643 previous_scroll_position,
8644 since,
8645 } = self.edit_prediction_preview
8646 {
8647 if let (Some(previous_scroll_position), Some(position_map)) =
8648 (previous_scroll_position, self.last_position_map.as_ref())
8649 {
8650 self.set_scroll_position(
8651 previous_scroll_position
8652 .scroll_position(&position_map.snapshot.display_snapshot),
8653 window,
8654 cx,
8655 );
8656 }
8657
8658 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8659 released_too_fast: since.elapsed() < Duration::from_millis(200),
8660 };
8661 self.clear_row_highlights::<EditPredictionPreview>();
8662 self.update_visible_edit_prediction(window, cx);
8663 cx.notify();
8664 }
8665 }
8666
8667 fn update_visible_edit_prediction(
8668 &mut self,
8669 _window: &mut Window,
8670 cx: &mut Context<Self>,
8671 ) -> Option<()> {
8672 if self.ime_transaction.is_some() {
8673 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8674 return None;
8675 }
8676
8677 let selection = self.selections.newest_anchor();
8678 let cursor = selection.head();
8679 let multibuffer = self.buffer.read(cx).snapshot(cx);
8680
8681 // Check project-level disable_ai setting for the current buffer
8682 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8683 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8684 return None;
8685 }
8686 }
8687 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8688 let excerpt_id = cursor.excerpt_id;
8689
8690 let show_in_menu = self.show_edit_predictions_in_menu();
8691 let completions_menu_has_precedence = !show_in_menu
8692 && (self.context_menu.borrow().is_some()
8693 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8694
8695 if completions_menu_has_precedence
8696 || !offset_selection.is_empty()
8697 || self
8698 .active_edit_prediction
8699 .as_ref()
8700 .is_some_and(|completion| {
8701 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8702 return false;
8703 };
8704 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8705 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8706 !invalidation_range.contains(&offset_selection.head())
8707 })
8708 {
8709 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8710 return None;
8711 }
8712
8713 self.take_active_edit_prediction(true, cx);
8714 let Some(provider) = self.edit_prediction_provider() else {
8715 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8716 return None;
8717 };
8718
8719 let (buffer, cursor_buffer_position) =
8720 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8721
8722 self.edit_prediction_settings =
8723 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8724
8725 self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor);
8726
8727 if self.in_leading_whitespace {
8728 let cursor_point = cursor.to_point(&multibuffer);
8729 let mut suggested_indent = None;
8730 multibuffer.suggested_indents_callback(
8731 cursor_point.row..cursor_point.row + 1,
8732 &mut |_, indent| {
8733 suggested_indent = Some(indent);
8734 ControlFlow::Break(())
8735 },
8736 cx,
8737 );
8738
8739 if let Some(indent) = suggested_indent
8740 && indent.len == cursor_point.column
8741 {
8742 self.in_leading_whitespace = false;
8743 }
8744 }
8745
8746 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8747
8748 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8749 {
8750 edit_prediction_types::EditPrediction::Local {
8751 id,
8752 edits,
8753 cursor_position,
8754 edit_preview,
8755 } => (id, edits, cursor_position, edit_preview),
8756 edit_prediction_types::EditPrediction::Jump {
8757 id,
8758 snapshot,
8759 target,
8760 } => {
8761 if let Some(provider) = &self.edit_prediction_provider {
8762 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8763 }
8764 self.stale_edit_prediction_in_menu = None;
8765 self.active_edit_prediction = Some(EditPredictionState {
8766 inlay_ids: vec![],
8767 completion: EditPrediction::MoveOutside { snapshot, target },
8768 completion_id: id,
8769 invalidation_range: None,
8770 });
8771 cx.notify();
8772 return Some(());
8773 }
8774 };
8775
8776 let edits = edits
8777 .into_iter()
8778 .flat_map(|(range, new_text)| {
8779 Some((
8780 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8781 new_text,
8782 ))
8783 })
8784 .collect::<Vec<_>>();
8785 if edits.is_empty() {
8786 return None;
8787 }
8788
8789 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8790 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8791 Some((anchor, predicted.offset))
8792 });
8793
8794 let first_edit_start = edits.first().unwrap().0.start;
8795 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8796 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8797
8798 let last_edit_end = edits.last().unwrap().0.end;
8799 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8800 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8801
8802 let cursor_row = cursor.to_point(&multibuffer).row;
8803
8804 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8805
8806 let mut inlay_ids = Vec::new();
8807 let invalidation_row_range;
8808 let move_invalidation_row_range = if cursor_row < edit_start_row {
8809 Some(cursor_row..edit_end_row)
8810 } else if cursor_row > edit_end_row {
8811 Some(edit_start_row..cursor_row)
8812 } else {
8813 None
8814 };
8815 let supports_jump = self
8816 .edit_prediction_provider
8817 .as_ref()
8818 .map(|provider| provider.provider.supports_jump_to_edit())
8819 .unwrap_or(true);
8820
8821 let is_move = supports_jump
8822 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8823 let completion = if is_move {
8824 if let Some(provider) = &self.edit_prediction_provider {
8825 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8826 }
8827 invalidation_row_range =
8828 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8829 let target = first_edit_start;
8830 EditPrediction::MoveWithin { target, snapshot }
8831 } else {
8832 let show_completions_in_menu = self.has_visible_completions_menu();
8833 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8834 && !self.edit_predictions_hidden_for_vim_mode;
8835
8836 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8837 if provider.show_tab_accept_marker() {
8838 EditDisplayMode::TabAccept
8839 } else {
8840 EditDisplayMode::Inline
8841 }
8842 } else {
8843 EditDisplayMode::DiffPopover
8844 };
8845
8846 let report_shown = match display_mode {
8847 EditDisplayMode::DiffPopover | EditDisplayMode::Inline => {
8848 show_completions_in_buffer || show_completions_in_menu
8849 }
8850 EditDisplayMode::TabAccept => {
8851 show_completions_in_menu || self.edit_prediction_preview_is_active()
8852 }
8853 };
8854
8855 if report_shown && let Some(provider) = &self.edit_prediction_provider {
8856 let suggestion_display_type = match display_mode {
8857 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8858 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8859 SuggestionDisplayType::GhostText
8860 }
8861 };
8862 provider.provider.did_show(suggestion_display_type, cx);
8863 }
8864
8865 if show_completions_in_buffer {
8866 if edits
8867 .iter()
8868 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8869 {
8870 let mut inlays = Vec::new();
8871 for (range, new_text) in &edits {
8872 let inlay = Inlay::edit_prediction(
8873 post_inc(&mut self.next_inlay_id),
8874 range.start,
8875 new_text.as_ref(),
8876 );
8877 inlay_ids.push(inlay.id);
8878 inlays.push(inlay);
8879 }
8880
8881 self.splice_inlays(&[], inlays, cx);
8882 } else {
8883 let background_color = cx.theme().status().deleted_background;
8884 self.highlight_text(
8885 HighlightKey::EditPredictionHighlight,
8886 edits.iter().map(|(range, _)| range.clone()).collect(),
8887 HighlightStyle {
8888 background_color: Some(background_color),
8889 ..Default::default()
8890 },
8891 cx,
8892 );
8893 }
8894 }
8895
8896 invalidation_row_range = edit_start_row..edit_end_row;
8897
8898 EditPrediction::Edit {
8899 edits,
8900 cursor_position,
8901 edit_preview,
8902 display_mode,
8903 snapshot,
8904 }
8905 };
8906
8907 let invalidation_range = multibuffer
8908 .anchor_before(Point::new(invalidation_row_range.start, 0))
8909 ..multibuffer.anchor_after(Point::new(
8910 invalidation_row_range.end,
8911 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8912 ));
8913
8914 self.stale_edit_prediction_in_menu = None;
8915 self.active_edit_prediction = Some(EditPredictionState {
8916 inlay_ids,
8917 completion,
8918 completion_id,
8919 invalidation_range: Some(invalidation_range),
8920 });
8921
8922 cx.notify();
8923
8924 Some(())
8925 }
8926
8927 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8928 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8929 }
8930
8931 /// Get all display points of breakpoints that will be rendered within editor
8932 ///
8933 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8934 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8935 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8936 fn active_breakpoints(
8937 &self,
8938 range: Range<DisplayRow>,
8939 window: &mut Window,
8940 cx: &mut Context<Self>,
8941 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8942 let mut breakpoint_display_points = HashMap::default();
8943
8944 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8945 return breakpoint_display_points;
8946 };
8947
8948 let snapshot = self.snapshot(window, cx);
8949
8950 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8951 let Some(project) = self.project() else {
8952 return breakpoint_display_points;
8953 };
8954
8955 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8956 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8957
8958 for (buffer_snapshot, range, excerpt_id) in
8959 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8960 {
8961 let Some(buffer) = project
8962 .read(cx)
8963 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8964 else {
8965 continue;
8966 };
8967 let breakpoints = breakpoint_store.read(cx).breakpoints(
8968 &buffer,
8969 Some(
8970 buffer_snapshot.anchor_before(range.start)
8971 ..buffer_snapshot.anchor_after(range.end),
8972 ),
8973 buffer_snapshot,
8974 cx,
8975 );
8976 for (breakpoint, state) in breakpoints {
8977 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8978 let position = multi_buffer_anchor
8979 .to_point(&multi_buffer_snapshot)
8980 .to_display_point(&snapshot);
8981
8982 breakpoint_display_points.insert(
8983 position.row(),
8984 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8985 );
8986 }
8987 }
8988
8989 breakpoint_display_points
8990 }
8991
8992 fn breakpoint_context_menu(
8993 &self,
8994 anchor: Anchor,
8995 window: &mut Window,
8996 cx: &mut Context<Self>,
8997 ) -> Entity<ui::ContextMenu> {
8998 let weak_editor = cx.weak_entity();
8999 let focus_handle = self.focus_handle(cx);
9000
9001 let row = self
9002 .buffer
9003 .read(cx)
9004 .snapshot(cx)
9005 .summary_for_anchor::<Point>(&anchor)
9006 .row;
9007
9008 let breakpoint = self
9009 .breakpoint_at_row(row, window, cx)
9010 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
9011
9012 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
9013 "Edit Log Breakpoint"
9014 } else {
9015 "Set Log Breakpoint"
9016 };
9017
9018 let condition_breakpoint_msg = if breakpoint
9019 .as_ref()
9020 .is_some_and(|bp| bp.1.condition.is_some())
9021 {
9022 "Edit Condition Breakpoint"
9023 } else {
9024 "Set Condition Breakpoint"
9025 };
9026
9027 let hit_condition_breakpoint_msg = if breakpoint
9028 .as_ref()
9029 .is_some_and(|bp| bp.1.hit_condition.is_some())
9030 {
9031 "Edit Hit Condition Breakpoint"
9032 } else {
9033 "Set Hit Condition Breakpoint"
9034 };
9035
9036 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
9037 "Unset Breakpoint"
9038 } else {
9039 "Set Breakpoint"
9040 };
9041
9042 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
9043
9044 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
9045 BreakpointState::Enabled => Some("Disable"),
9046 BreakpointState::Disabled => Some("Enable"),
9047 });
9048
9049 let (anchor, breakpoint) =
9050 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
9051
9052 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
9053 menu.on_blur_subscription(Subscription::new(|| {}))
9054 .context(focus_handle)
9055 .when(run_to_cursor, |this| {
9056 let weak_editor = weak_editor.clone();
9057 this.entry("Run to Cursor", None, move |window, cx| {
9058 weak_editor
9059 .update(cx, |editor, cx| {
9060 editor.change_selections(
9061 SelectionEffects::no_scroll(),
9062 window,
9063 cx,
9064 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
9065 );
9066 })
9067 .ok();
9068
9069 window.dispatch_action(Box::new(RunToCursor), cx);
9070 })
9071 .separator()
9072 })
9073 .when_some(toggle_state_msg, |this, msg| {
9074 this.entry(msg, None, {
9075 let weak_editor = weak_editor.clone();
9076 let breakpoint = breakpoint.clone();
9077 move |_window, cx| {
9078 weak_editor
9079 .update(cx, |this, cx| {
9080 this.edit_breakpoint_at_anchor(
9081 anchor,
9082 breakpoint.as_ref().clone(),
9083 BreakpointEditAction::InvertState,
9084 cx,
9085 );
9086 })
9087 .log_err();
9088 }
9089 })
9090 })
9091 .entry(set_breakpoint_msg, None, {
9092 let weak_editor = weak_editor.clone();
9093 let breakpoint = breakpoint.clone();
9094 move |_window, cx| {
9095 weak_editor
9096 .update(cx, |this, cx| {
9097 this.edit_breakpoint_at_anchor(
9098 anchor,
9099 breakpoint.as_ref().clone(),
9100 BreakpointEditAction::Toggle,
9101 cx,
9102 );
9103 })
9104 .log_err();
9105 }
9106 })
9107 .entry(log_breakpoint_msg, None, {
9108 let breakpoint = breakpoint.clone();
9109 let weak_editor = weak_editor.clone();
9110 move |window, cx| {
9111 weak_editor
9112 .update(cx, |this, cx| {
9113 this.add_edit_breakpoint_block(
9114 anchor,
9115 breakpoint.as_ref(),
9116 BreakpointPromptEditAction::Log,
9117 window,
9118 cx,
9119 );
9120 })
9121 .log_err();
9122 }
9123 })
9124 .entry(condition_breakpoint_msg, None, {
9125 let breakpoint = breakpoint.clone();
9126 let weak_editor = weak_editor.clone();
9127 move |window, cx| {
9128 weak_editor
9129 .update(cx, |this, cx| {
9130 this.add_edit_breakpoint_block(
9131 anchor,
9132 breakpoint.as_ref(),
9133 BreakpointPromptEditAction::Condition,
9134 window,
9135 cx,
9136 );
9137 })
9138 .log_err();
9139 }
9140 })
9141 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
9142 weak_editor
9143 .update(cx, |this, cx| {
9144 this.add_edit_breakpoint_block(
9145 anchor,
9146 breakpoint.as_ref(),
9147 BreakpointPromptEditAction::HitCondition,
9148 window,
9149 cx,
9150 );
9151 })
9152 .log_err();
9153 })
9154 })
9155 }
9156
9157 fn render_breakpoint(
9158 &self,
9159 position: Anchor,
9160 row: DisplayRow,
9161 breakpoint: &Breakpoint,
9162 state: Option<BreakpointSessionState>,
9163 cx: &mut Context<Self>,
9164 ) -> IconButton {
9165 let is_rejected = state.is_some_and(|s| !s.verified);
9166 // Is it a breakpoint that shows up when hovering over gutter?
9167 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9168 (false, false),
9169 |PhantomBreakpointIndicator {
9170 is_active,
9171 display_row,
9172 collides_with_existing_breakpoint,
9173 }| {
9174 (
9175 is_active && display_row == row,
9176 collides_with_existing_breakpoint,
9177 )
9178 },
9179 );
9180
9181 let (color, icon) = {
9182 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9183 (false, false) => ui::IconName::DebugBreakpoint,
9184 (true, false) => ui::IconName::DebugLogBreakpoint,
9185 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9186 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9187 };
9188
9189 let theme_colors = cx.theme().colors();
9190
9191 let color = if is_phantom {
9192 if collides_with_existing {
9193 Color::Custom(
9194 theme_colors
9195 .debugger_accent
9196 .blend(theme_colors.text.opacity(0.6)),
9197 )
9198 } else {
9199 Color::Hint
9200 }
9201 } else if is_rejected {
9202 Color::Disabled
9203 } else {
9204 Color::Debugger
9205 };
9206
9207 (color, icon)
9208 };
9209
9210 let breakpoint = Arc::from(breakpoint.clone());
9211
9212 let alt_as_text = gpui::Keystroke {
9213 modifiers: Modifiers::secondary_key(),
9214 ..Default::default()
9215 };
9216 let primary_action_text = if breakpoint.is_disabled() {
9217 "Enable breakpoint"
9218 } else if is_phantom && !collides_with_existing {
9219 "Set breakpoint"
9220 } else {
9221 "Unset breakpoint"
9222 };
9223 let focus_handle = self.focus_handle.clone();
9224
9225 let meta = if is_rejected {
9226 SharedString::from("No executable code is associated with this line.")
9227 } else if collides_with_existing && !breakpoint.is_disabled() {
9228 SharedString::from(format!(
9229 "{alt_as_text}-click to disable,\nright-click for more options."
9230 ))
9231 } else {
9232 SharedString::from("Right-click for more options.")
9233 };
9234 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9235 .icon_size(IconSize::XSmall)
9236 .size(ui::ButtonSize::None)
9237 .when(is_rejected, |this| {
9238 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9239 })
9240 .icon_color(color)
9241 .style(ButtonStyle::Transparent)
9242 .on_click(cx.listener({
9243 move |editor, event: &ClickEvent, window, cx| {
9244 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9245 BreakpointEditAction::InvertState
9246 } else {
9247 BreakpointEditAction::Toggle
9248 };
9249
9250 window.focus(&editor.focus_handle(cx), cx);
9251 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9252 editor.edit_breakpoint_at_anchor(
9253 position,
9254 breakpoint.as_ref().clone(),
9255 edit_action,
9256 cx,
9257 );
9258 }
9259 }))
9260 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9261 editor.set_breakpoint_context_menu(
9262 row,
9263 Some(position),
9264 event.position(),
9265 window,
9266 cx,
9267 );
9268 }))
9269 .tooltip(move |_window, cx| {
9270 Tooltip::with_meta_in(
9271 primary_action_text,
9272 Some(&ToggleBreakpoint),
9273 meta.clone(),
9274 &focus_handle,
9275 cx,
9276 )
9277 })
9278 }
9279
9280 fn build_tasks_context(
9281 project: &Entity<Project>,
9282 buffer: &Entity<Buffer>,
9283 buffer_row: u32,
9284 tasks: &Arc<RunnableTasks>,
9285 cx: &mut Context<Self>,
9286 ) -> Task<Option<task::TaskContext>> {
9287 let position = Point::new(buffer_row, tasks.column);
9288 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9289 let location = Location {
9290 buffer: buffer.clone(),
9291 range: range_start..range_start,
9292 };
9293 // Fill in the environmental variables from the tree-sitter captures
9294 let mut captured_task_variables = TaskVariables::default();
9295 for (capture_name, value) in tasks.extra_variables.clone() {
9296 captured_task_variables.insert(
9297 task::VariableName::Custom(capture_name.into()),
9298 value.clone(),
9299 );
9300 }
9301 project.update(cx, |project, cx| {
9302 project.task_store().update(cx, |task_store, cx| {
9303 task_store.task_context_for_location(captured_task_variables, location, cx)
9304 })
9305 })
9306 }
9307
9308 pub fn context_menu_visible(&self) -> bool {
9309 !self.edit_prediction_preview_is_active()
9310 && self
9311 .context_menu
9312 .borrow()
9313 .as_ref()
9314 .is_some_and(|menu| menu.visible())
9315 }
9316
9317 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9318 self.context_menu
9319 .borrow()
9320 .as_ref()
9321 .map(|menu| menu.origin())
9322 }
9323
9324 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9325 self.context_menu_options = Some(options);
9326 }
9327
9328 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9329 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9330
9331 fn render_edit_prediction_popover(
9332 &mut self,
9333 text_bounds: &Bounds<Pixels>,
9334 content_origin: gpui::Point<Pixels>,
9335 right_margin: Pixels,
9336 editor_snapshot: &EditorSnapshot,
9337 visible_row_range: Range<DisplayRow>,
9338 scroll_top: ScrollOffset,
9339 scroll_bottom: ScrollOffset,
9340 line_layouts: &[LineWithInvisibles],
9341 line_height: Pixels,
9342 scroll_position: gpui::Point<ScrollOffset>,
9343 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9344 newest_selection_head: Option<DisplayPoint>,
9345 editor_width: Pixels,
9346 style: &EditorStyle,
9347 window: &mut Window,
9348 cx: &mut App,
9349 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9350 if self.mode().is_minimap() {
9351 return None;
9352 }
9353 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9354
9355 if self.edit_prediction_visible_in_cursor_popover(true) {
9356 return None;
9357 }
9358
9359 match &active_edit_prediction.completion {
9360 EditPrediction::MoveWithin { target, .. } => {
9361 let target_display_point = target.to_display_point(editor_snapshot);
9362
9363 if self.edit_prediction_requires_modifier() {
9364 if !self.edit_prediction_preview_is_active() {
9365 return None;
9366 }
9367
9368 self.render_edit_prediction_modifier_jump_popover(
9369 text_bounds,
9370 content_origin,
9371 visible_row_range,
9372 line_layouts,
9373 line_height,
9374 scroll_pixel_position,
9375 newest_selection_head,
9376 target_display_point,
9377 window,
9378 cx,
9379 )
9380 } else {
9381 self.render_edit_prediction_eager_jump_popover(
9382 text_bounds,
9383 content_origin,
9384 editor_snapshot,
9385 visible_row_range,
9386 scroll_top,
9387 scroll_bottom,
9388 line_height,
9389 scroll_pixel_position,
9390 target_display_point,
9391 editor_width,
9392 window,
9393 cx,
9394 )
9395 }
9396 }
9397 EditPrediction::Edit {
9398 display_mode: EditDisplayMode::Inline,
9399 ..
9400 } => None,
9401 EditPrediction::Edit {
9402 display_mode: EditDisplayMode::TabAccept,
9403 edits,
9404 ..
9405 } => {
9406 let range = &edits.first()?.0;
9407 let target_display_point = range.end.to_display_point(editor_snapshot);
9408
9409 self.render_edit_prediction_end_of_line_popover(
9410 "Accept",
9411 editor_snapshot,
9412 visible_row_range,
9413 target_display_point,
9414 line_height,
9415 scroll_pixel_position,
9416 content_origin,
9417 editor_width,
9418 window,
9419 cx,
9420 )
9421 }
9422 EditPrediction::Edit {
9423 edits,
9424 edit_preview,
9425 display_mode: EditDisplayMode::DiffPopover,
9426 snapshot,
9427 ..
9428 } => self.render_edit_prediction_diff_popover(
9429 text_bounds,
9430 content_origin,
9431 right_margin,
9432 editor_snapshot,
9433 visible_row_range,
9434 line_layouts,
9435 line_height,
9436 scroll_position,
9437 scroll_pixel_position,
9438 newest_selection_head,
9439 editor_width,
9440 style,
9441 edits,
9442 edit_preview,
9443 snapshot,
9444 window,
9445 cx,
9446 ),
9447 EditPrediction::MoveOutside { snapshot, .. } => {
9448 let mut element = self
9449 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9450 .into_any();
9451
9452 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9453 let origin_x = text_bounds.size.width - size.width - px(30.);
9454 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9455 element.prepaint_at(origin, window, cx);
9456
9457 Some((element, origin))
9458 }
9459 }
9460 }
9461
9462 fn render_edit_prediction_modifier_jump_popover(
9463 &mut self,
9464 text_bounds: &Bounds<Pixels>,
9465 content_origin: gpui::Point<Pixels>,
9466 visible_row_range: Range<DisplayRow>,
9467 line_layouts: &[LineWithInvisibles],
9468 line_height: Pixels,
9469 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9470 newest_selection_head: Option<DisplayPoint>,
9471 target_display_point: DisplayPoint,
9472 window: &mut Window,
9473 cx: &mut App,
9474 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9475 let scrolled_content_origin =
9476 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9477
9478 const SCROLL_PADDING_Y: Pixels = px(12.);
9479
9480 if target_display_point.row() < visible_row_range.start {
9481 return self.render_edit_prediction_scroll_popover(
9482 &|_| SCROLL_PADDING_Y,
9483 IconName::ArrowUp,
9484 visible_row_range,
9485 line_layouts,
9486 newest_selection_head,
9487 scrolled_content_origin,
9488 window,
9489 cx,
9490 );
9491 } else if target_display_point.row() >= visible_row_range.end {
9492 return self.render_edit_prediction_scroll_popover(
9493 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9494 IconName::ArrowDown,
9495 visible_row_range,
9496 line_layouts,
9497 newest_selection_head,
9498 scrolled_content_origin,
9499 window,
9500 cx,
9501 );
9502 }
9503
9504 const POLE_WIDTH: Pixels = px(2.);
9505
9506 let line_layout =
9507 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9508 let target_column = target_display_point.column() as usize;
9509
9510 let target_x = line_layout.x_for_index(target_column);
9511 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9512 - scroll_pixel_position.y;
9513
9514 let flag_on_right = target_x < text_bounds.size.width / 2.;
9515
9516 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9517 border_color.l += 0.001;
9518
9519 let mut element = v_flex()
9520 .items_end()
9521 .when(flag_on_right, |el| el.items_start())
9522 .child(if flag_on_right {
9523 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9524 .rounded_bl(px(0.))
9525 .rounded_tl(px(0.))
9526 .border_l_2()
9527 .border_color(border_color)
9528 } else {
9529 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9530 .rounded_br(px(0.))
9531 .rounded_tr(px(0.))
9532 .border_r_2()
9533 .border_color(border_color)
9534 })
9535 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9536 .into_any();
9537
9538 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9539
9540 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9541 - point(
9542 if flag_on_right {
9543 POLE_WIDTH
9544 } else {
9545 size.width - POLE_WIDTH
9546 },
9547 size.height - line_height,
9548 );
9549
9550 origin.x = origin.x.max(content_origin.x);
9551
9552 element.prepaint_at(origin, window, cx);
9553
9554 Some((element, origin))
9555 }
9556
9557 fn render_edit_prediction_scroll_popover(
9558 &mut self,
9559 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9560 scroll_icon: IconName,
9561 visible_row_range: Range<DisplayRow>,
9562 line_layouts: &[LineWithInvisibles],
9563 newest_selection_head: Option<DisplayPoint>,
9564 scrolled_content_origin: gpui::Point<Pixels>,
9565 window: &mut Window,
9566 cx: &mut App,
9567 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9568 let mut element = self
9569 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9570 .into_any();
9571
9572 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9573
9574 let cursor = newest_selection_head?;
9575 let cursor_row_layout =
9576 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9577 let cursor_column = cursor.column() as usize;
9578
9579 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9580
9581 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9582
9583 element.prepaint_at(origin, window, cx);
9584 Some((element, origin))
9585 }
9586
9587 fn render_edit_prediction_eager_jump_popover(
9588 &mut self,
9589 text_bounds: &Bounds<Pixels>,
9590 content_origin: gpui::Point<Pixels>,
9591 editor_snapshot: &EditorSnapshot,
9592 visible_row_range: Range<DisplayRow>,
9593 scroll_top: ScrollOffset,
9594 scroll_bottom: ScrollOffset,
9595 line_height: Pixels,
9596 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9597 target_display_point: DisplayPoint,
9598 editor_width: Pixels,
9599 window: &mut Window,
9600 cx: &mut App,
9601 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9602 if target_display_point.row().as_f64() < scroll_top {
9603 let mut element = self
9604 .render_edit_prediction_line_popover(
9605 "Jump to Edit",
9606 Some(IconName::ArrowUp),
9607 window,
9608 cx,
9609 )
9610 .into_any();
9611
9612 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9613 let offset = point(
9614 (text_bounds.size.width - size.width) / 2.,
9615 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9616 );
9617
9618 let origin = text_bounds.origin + offset;
9619 element.prepaint_at(origin, window, cx);
9620 Some((element, origin))
9621 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9622 let mut element = self
9623 .render_edit_prediction_line_popover(
9624 "Jump to Edit",
9625 Some(IconName::ArrowDown),
9626 window,
9627 cx,
9628 )
9629 .into_any();
9630
9631 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9632 let offset = point(
9633 (text_bounds.size.width - size.width) / 2.,
9634 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9635 );
9636
9637 let origin = text_bounds.origin + offset;
9638 element.prepaint_at(origin, window, cx);
9639 Some((element, origin))
9640 } else {
9641 self.render_edit_prediction_end_of_line_popover(
9642 "Jump to Edit",
9643 editor_snapshot,
9644 visible_row_range,
9645 target_display_point,
9646 line_height,
9647 scroll_pixel_position,
9648 content_origin,
9649 editor_width,
9650 window,
9651 cx,
9652 )
9653 }
9654 }
9655
9656 fn render_edit_prediction_end_of_line_popover(
9657 self: &mut Editor,
9658 label: &'static str,
9659 editor_snapshot: &EditorSnapshot,
9660 visible_row_range: Range<DisplayRow>,
9661 target_display_point: DisplayPoint,
9662 line_height: Pixels,
9663 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9664 content_origin: gpui::Point<Pixels>,
9665 editor_width: Pixels,
9666 window: &mut Window,
9667 cx: &mut App,
9668 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9669 let target_line_end = DisplayPoint::new(
9670 target_display_point.row(),
9671 editor_snapshot.line_len(target_display_point.row()),
9672 );
9673
9674 let mut element = self
9675 .render_edit_prediction_line_popover(label, None, window, cx)
9676 .into_any();
9677
9678 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9679
9680 let line_origin =
9681 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9682
9683 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9684 let mut origin = start_point
9685 + line_origin
9686 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9687 origin.x = origin.x.max(content_origin.x);
9688
9689 let max_x = content_origin.x + editor_width - size.width;
9690
9691 if origin.x > max_x {
9692 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9693
9694 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9695 origin.y += offset;
9696 IconName::ArrowUp
9697 } else {
9698 origin.y -= offset;
9699 IconName::ArrowDown
9700 };
9701
9702 element = self
9703 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9704 .into_any();
9705
9706 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9707
9708 origin.x = content_origin.x + editor_width - size.width - px(2.);
9709 }
9710
9711 element.prepaint_at(origin, window, cx);
9712 Some((element, origin))
9713 }
9714
9715 fn render_edit_prediction_diff_popover(
9716 self: &Editor,
9717 text_bounds: &Bounds<Pixels>,
9718 content_origin: gpui::Point<Pixels>,
9719 right_margin: Pixels,
9720 editor_snapshot: &EditorSnapshot,
9721 visible_row_range: Range<DisplayRow>,
9722 line_layouts: &[LineWithInvisibles],
9723 line_height: Pixels,
9724 scroll_position: gpui::Point<ScrollOffset>,
9725 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9726 newest_selection_head: Option<DisplayPoint>,
9727 editor_width: Pixels,
9728 style: &EditorStyle,
9729 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9730 edit_preview: &Option<language::EditPreview>,
9731 snapshot: &language::BufferSnapshot,
9732 window: &mut Window,
9733 cx: &mut App,
9734 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9735 let edit_start = edits
9736 .first()
9737 .unwrap()
9738 .0
9739 .start
9740 .to_display_point(editor_snapshot);
9741 let edit_end = edits
9742 .last()
9743 .unwrap()
9744 .0
9745 .end
9746 .to_display_point(editor_snapshot);
9747
9748 let is_visible = visible_row_range.contains(&edit_start.row())
9749 || visible_row_range.contains(&edit_end.row());
9750 if !is_visible {
9751 return None;
9752 }
9753
9754 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9755 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9756 } else {
9757 // Fallback for providers without edit_preview
9758 crate::edit_prediction_fallback_text(edits, cx)
9759 };
9760
9761 let styled_text = highlighted_edits.to_styled_text(&style.text);
9762 let line_count = highlighted_edits.text.lines().count();
9763
9764 const BORDER_WIDTH: Pixels = px(1.);
9765
9766 let keybind = self.render_edit_prediction_keybind(window, cx);
9767 let has_keybind = keybind.is_some();
9768
9769 let mut element = h_flex()
9770 .items_start()
9771 .child(
9772 h_flex()
9773 .bg(cx.theme().colors().editor_background)
9774 .border(BORDER_WIDTH)
9775 .shadow_xs()
9776 .border_color(cx.theme().colors().border)
9777 .rounded_l_lg()
9778 .when(line_count > 1, |el| el.rounded_br_lg())
9779 .pr_1()
9780 .child(styled_text),
9781 )
9782 .child(
9783 h_flex()
9784 .h(line_height + BORDER_WIDTH * 2.)
9785 .px_1p5()
9786 .gap_1()
9787 // Workaround: For some reason, there's a gap if we don't do this
9788 .ml(-BORDER_WIDTH)
9789 .shadow(vec![gpui::BoxShadow {
9790 color: gpui::black().opacity(0.05),
9791 offset: point(px(1.), px(1.)),
9792 blur_radius: px(2.),
9793 spread_radius: px(0.),
9794 }])
9795 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9796 .border(BORDER_WIDTH)
9797 .border_color(cx.theme().colors().border)
9798 .rounded_r_lg()
9799 .id("edit_prediction_diff_popover_keybind")
9800 .when(!has_keybind, |el| {
9801 let status_colors = cx.theme().status();
9802
9803 el.bg(status_colors.error_background)
9804 .border_color(status_colors.error.opacity(0.6))
9805 .child(Icon::new(IconName::Info).color(Color::Error))
9806 .cursor_default()
9807 .hoverable_tooltip(move |_window, cx| {
9808 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9809 })
9810 })
9811 .children(keybind),
9812 )
9813 .into_any();
9814
9815 let longest_row =
9816 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9817 let longest_line_width = if visible_row_range.contains(&longest_row) {
9818 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9819 } else {
9820 layout_line(
9821 longest_row,
9822 editor_snapshot,
9823 style,
9824 editor_width,
9825 |_| false,
9826 window,
9827 cx,
9828 )
9829 .width
9830 };
9831
9832 let viewport_bounds =
9833 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9834 right: -right_margin,
9835 ..Default::default()
9836 });
9837
9838 let x_after_longest = Pixels::from(
9839 ScrollPixelOffset::from(
9840 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9841 ) - scroll_pixel_position.x,
9842 );
9843
9844 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9845
9846 // Fully visible if it can be displayed within the window (allow overlapping other
9847 // panes). However, this is only allowed if the popover starts within text_bounds.
9848 let can_position_to_the_right = x_after_longest < text_bounds.right()
9849 && x_after_longest + element_bounds.width < viewport_bounds.right();
9850
9851 let mut origin = if can_position_to_the_right {
9852 point(
9853 x_after_longest,
9854 text_bounds.origin.y
9855 + Pixels::from(
9856 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9857 - scroll_pixel_position.y,
9858 ),
9859 )
9860 } else {
9861 let cursor_row = newest_selection_head.map(|head| head.row());
9862 let above_edit = edit_start
9863 .row()
9864 .0
9865 .checked_sub(line_count as u32)
9866 .map(DisplayRow);
9867 let below_edit = Some(edit_end.row() + 1);
9868 let above_cursor =
9869 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9870 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9871
9872 // Place the edit popover adjacent to the edit if there is a location
9873 // available that is onscreen and does not obscure the cursor. Otherwise,
9874 // place it adjacent to the cursor.
9875 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9876 .into_iter()
9877 .flatten()
9878 .find(|&start_row| {
9879 let end_row = start_row + line_count as u32;
9880 visible_row_range.contains(&start_row)
9881 && visible_row_range.contains(&end_row)
9882 && cursor_row
9883 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9884 })?;
9885
9886 content_origin
9887 + point(
9888 Pixels::from(-scroll_pixel_position.x),
9889 Pixels::from(
9890 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9891 ),
9892 )
9893 };
9894
9895 origin.x -= BORDER_WIDTH;
9896
9897 window.with_content_mask(
9898 Some(gpui::ContentMask {
9899 bounds: *text_bounds,
9900 }),
9901 |window| {
9902 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9903 },
9904 );
9905
9906 // Do not return an element, since it will already be drawn due to defer_draw.
9907 None
9908 }
9909
9910 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9911 px(30.)
9912 }
9913
9914 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9915 if self.read_only(cx) {
9916 cx.theme().players().read_only()
9917 } else {
9918 self.style.as_ref().unwrap().local_player
9919 }
9920 }
9921
9922 fn render_edit_prediction_inline_keystroke(
9923 &self,
9924 keystroke: &gpui::KeybindingKeystroke,
9925 modifiers_color: Color,
9926 cx: &App,
9927 ) -> AnyElement {
9928 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9929
9930 h_flex()
9931 .px_0p5()
9932 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9933 .font(
9934 theme_settings::ThemeSettings::get_global(cx)
9935 .buffer_font
9936 .clone(),
9937 )
9938 .text_size(TextSize::XSmall.rems(cx))
9939 .child(h_flex().children(ui::render_modifiers(
9940 keystroke.modifiers(),
9941 PlatformStyle::platform(),
9942 Some(modifiers_color),
9943 Some(IconSize::XSmall.rems().into()),
9944 true,
9945 )))
9946 .when(is_platform_style_mac, |parent| {
9947 parent.child(keystroke.key().to_string())
9948 })
9949 .when(!is_platform_style_mac, |parent| {
9950 parent.child(
9951 Key::new(ui::utils::capitalize(keystroke.key()), Some(Color::Default))
9952 .size(Some(IconSize::XSmall.rems().into())),
9953 )
9954 })
9955 .into_any()
9956 }
9957
9958 fn render_edit_prediction_popover_keystroke(
9959 &self,
9960 keystroke: &gpui::KeybindingKeystroke,
9961 color: Color,
9962 cx: &App,
9963 ) -> AnyElement {
9964 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9965
9966 if keystroke.modifiers().modified() {
9967 h_flex()
9968 .font(
9969 theme_settings::ThemeSettings::get_global(cx)
9970 .buffer_font
9971 .clone(),
9972 )
9973 .when(is_platform_style_mac, |parent| parent.gap_1())
9974 .child(h_flex().children(ui::render_modifiers(
9975 keystroke.modifiers(),
9976 PlatformStyle::platform(),
9977 Some(color),
9978 None,
9979 false,
9980 )))
9981 .into_any()
9982 } else {
9983 Key::new(ui::utils::capitalize(keystroke.key()), Some(color))
9984 .size(Some(IconSize::XSmall.rems().into()))
9985 .into_any_element()
9986 }
9987 }
9988
9989 fn render_edit_prediction_keybind(
9990 &self,
9991 window: &mut Window,
9992 cx: &mut App,
9993 ) -> Option<AnyElement> {
9994 let keybind_display =
9995 self.edit_prediction_keybind_display(EditPredictionKeybindSurface::Inline, window, cx);
9996 let keystroke = keybind_display.displayed_keystroke.as_ref()?;
9997
9998 let modifiers_color = if *keystroke.modifiers() == window.modifiers() {
9999 Color::Accent
10000 } else {
10001 Color::Muted
10002 };
10003
10004 Some(self.render_edit_prediction_inline_keystroke(keystroke, modifiers_color, cx))
10005 }
10006
10007 fn render_edit_prediction_line_popover(
10008 &self,
10009 label: impl Into<SharedString>,
10010 icon: Option<IconName>,
10011 window: &mut Window,
10012 cx: &mut App,
10013 ) -> Stateful<Div> {
10014 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
10015
10016 let keybind = self.render_edit_prediction_keybind(window, cx);
10017 let has_keybind = keybind.is_some();
10018 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10019
10020 h_flex()
10021 .id("ep-line-popover")
10022 .py_0p5()
10023 .pl_1()
10024 .pr(padding_right)
10025 .gap_1()
10026 .rounded_md()
10027 .border_1()
10028 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10029 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10030 .shadow_xs()
10031 .when(!has_keybind, |el| {
10032 let status_colors = cx.theme().status();
10033
10034 el.bg(status_colors.error_background)
10035 .border_color(status_colors.error.opacity(0.6))
10036 .pl_2()
10037 .child(Icon::new(icons.error).color(Color::Error))
10038 .cursor_default()
10039 .hoverable_tooltip(move |_window, cx| {
10040 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10041 })
10042 })
10043 .children(keybind)
10044 .child(
10045 Label::new(label)
10046 .size(LabelSize::Small)
10047 .when(!has_keybind, |el| {
10048 el.color(cx.theme().status().error.into()).strikethrough()
10049 }),
10050 )
10051 .when(!has_keybind, |el| {
10052 el.child(
10053 h_flex().ml_1().child(
10054 Icon::new(IconName::Info)
10055 .size(IconSize::Small)
10056 .color(cx.theme().status().error.into()),
10057 ),
10058 )
10059 })
10060 .when_some(icon, |element, icon| {
10061 element.child(
10062 div()
10063 .mt(px(1.5))
10064 .child(Icon::new(icon).size(IconSize::Small)),
10065 )
10066 })
10067 }
10068
10069 fn render_edit_prediction_jump_outside_popover(
10070 &self,
10071 snapshot: &BufferSnapshot,
10072 window: &mut Window,
10073 cx: &mut App,
10074 ) -> Stateful<Div> {
10075 let keybind = self.render_edit_prediction_keybind(window, cx);
10076 let has_keybind = keybind.is_some();
10077 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10078
10079 let file_name = snapshot
10080 .file()
10081 .map(|file| SharedString::new(file.file_name(cx)))
10082 .unwrap_or(SharedString::new_static("untitled"));
10083
10084 h_flex()
10085 .id("ep-jump-outside-popover")
10086 .py_1()
10087 .px_2()
10088 .gap_1()
10089 .rounded_md()
10090 .border_1()
10091 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10092 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10093 .shadow_xs()
10094 .when(!has_keybind, |el| {
10095 let status_colors = cx.theme().status();
10096
10097 el.bg(status_colors.error_background)
10098 .border_color(status_colors.error.opacity(0.6))
10099 .pl_2()
10100 .child(Icon::new(icons.error).color(Color::Error))
10101 .cursor_default()
10102 .hoverable_tooltip(move |_window, cx| {
10103 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10104 })
10105 })
10106 .children(keybind)
10107 .child(
10108 Label::new(file_name)
10109 .size(LabelSize::Small)
10110 .buffer_font(cx)
10111 .when(!has_keybind, |el| {
10112 el.color(cx.theme().status().error.into()).strikethrough()
10113 }),
10114 )
10115 .when(!has_keybind, |el| {
10116 el.child(
10117 h_flex().ml_1().child(
10118 Icon::new(IconName::Info)
10119 .size(IconSize::Small)
10120 .color(cx.theme().status().error.into()),
10121 ),
10122 )
10123 })
10124 .child(
10125 div()
10126 .mt(px(1.5))
10127 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10128 )
10129 }
10130
10131 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10132 let accent_color = cx.theme().colors().text_accent;
10133 let editor_bg_color = cx.theme().colors().editor_background;
10134 editor_bg_color.blend(accent_color.opacity(0.1))
10135 }
10136
10137 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10138 let accent_color = cx.theme().colors().text_accent;
10139 let editor_bg_color = cx.theme().colors().editor_background;
10140 editor_bg_color.blend(accent_color.opacity(0.6))
10141 }
10142 fn get_prediction_provider_icons(
10143 provider: &Option<RegisteredEditPredictionDelegate>,
10144 cx: &App,
10145 ) -> edit_prediction_types::EditPredictionIconSet {
10146 match provider {
10147 Some(provider) => provider.provider.icons(cx),
10148 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10149 }
10150 }
10151
10152 fn render_edit_prediction_cursor_popover(
10153 &self,
10154 min_width: Pixels,
10155 max_width: Pixels,
10156 cursor_point: Point,
10157 style: &EditorStyle,
10158 window: &mut Window,
10159 cx: &mut Context<Editor>,
10160 ) -> Option<AnyElement> {
10161 let provider = self.edit_prediction_provider.as_ref()?;
10162 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10163
10164 let is_refreshing = provider.provider.is_refreshing(cx);
10165
10166 fn pending_completion_container(icon: IconName) -> Div {
10167 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10168 }
10169
10170 let completion = match &self.active_edit_prediction {
10171 Some(prediction) => {
10172 if !self.has_visible_completions_menu() {
10173 const RADIUS: Pixels = px(6.);
10174 const BORDER_WIDTH: Pixels = px(1.);
10175 let keybind_display = self.edit_prediction_keybind_display(
10176 EditPredictionKeybindSurface::CursorPopoverCompact,
10177 window,
10178 cx,
10179 );
10180
10181 return Some(
10182 h_flex()
10183 .elevation_2(cx)
10184 .border(BORDER_WIDTH)
10185 .border_color(cx.theme().colors().border)
10186 .when(keybind_display.missing_accept_keystroke, |el| {
10187 el.border_color(cx.theme().status().error)
10188 })
10189 .rounded(RADIUS)
10190 .rounded_tl(px(0.))
10191 .overflow_hidden()
10192 .child(div().px_1p5().child(match &prediction.completion {
10193 EditPrediction::MoveWithin { target, snapshot } => {
10194 use text::ToPoint as _;
10195 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10196 {
10197 Icon::new(icons.down)
10198 } else {
10199 Icon::new(icons.up)
10200 }
10201 }
10202 EditPrediction::MoveOutside { .. } => {
10203 // TODO [zeta2] custom icon for external jump?
10204 Icon::new(icons.base)
10205 }
10206 EditPrediction::Edit { .. } => Icon::new(icons.base),
10207 }))
10208 .child(
10209 h_flex()
10210 .gap_1()
10211 .py_1()
10212 .px_2()
10213 .rounded_r(RADIUS - BORDER_WIDTH)
10214 .border_l_1()
10215 .border_color(cx.theme().colors().border)
10216 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10217 .when(keybind_display.show_hold_label, |el| {
10218 el.child(
10219 Label::new("Hold")
10220 .size(LabelSize::Small)
10221 .when(
10222 keybind_display.missing_accept_keystroke,
10223 |el| el.strikethrough(),
10224 )
10225 .line_height_style(LineHeightStyle::UiLabel),
10226 )
10227 })
10228 .id("edit_prediction_cursor_popover_keybind")
10229 .when(keybind_display.missing_accept_keystroke, |el| {
10230 let status_colors = cx.theme().status();
10231
10232 el.bg(status_colors.error_background)
10233 .border_color(status_colors.error.opacity(0.6))
10234 .child(Icon::new(IconName::Info).color(Color::Error))
10235 .cursor_default()
10236 .hoverable_tooltip(move |_window, cx| {
10237 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10238 .into()
10239 })
10240 })
10241 .when_some(
10242 keybind_display.displayed_keystroke.as_ref(),
10243 |el, compact_keystroke| {
10244 el.child(self.render_edit_prediction_popover_keystroke(
10245 compact_keystroke,
10246 Color::Default,
10247 cx,
10248 ))
10249 },
10250 ),
10251 )
10252 .into_any(),
10253 );
10254 }
10255
10256 self.render_edit_prediction_cursor_popover_preview(
10257 prediction,
10258 cursor_point,
10259 style,
10260 cx,
10261 )?
10262 }
10263
10264 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10265 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10266 stale_completion,
10267 cursor_point,
10268 style,
10269 cx,
10270 )?,
10271
10272 None => pending_completion_container(icons.base)
10273 .child(Label::new("...").size(LabelSize::Small)),
10274 },
10275
10276 None => pending_completion_container(icons.base)
10277 .child(Label::new("...").size(LabelSize::Small)),
10278 };
10279
10280 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10281 completion
10282 .with_animation(
10283 "loading-completion",
10284 Animation::new(Duration::from_secs(2))
10285 .repeat()
10286 .with_easing(pulsating_between(0.4, 0.8)),
10287 |label, delta| label.opacity(delta),
10288 )
10289 .into_any_element()
10290 } else {
10291 completion.into_any_element()
10292 };
10293
10294 let has_completion = self.active_edit_prediction.is_some();
10295 let keybind_display = self.edit_prediction_keybind_display(
10296 EditPredictionKeybindSurface::CursorPopoverExpanded,
10297 window,
10298 cx,
10299 );
10300
10301 Some(
10302 h_flex()
10303 .min_w(min_width)
10304 .max_w(max_width)
10305 .flex_1()
10306 .elevation_2(cx)
10307 .border_color(cx.theme().colors().border)
10308 .child(
10309 div()
10310 .flex_1()
10311 .py_1()
10312 .px_2()
10313 .overflow_hidden()
10314 .child(completion),
10315 )
10316 .when_some(
10317 keybind_display.displayed_keystroke.as_ref(),
10318 |el, keystroke| {
10319 let key_color = if !has_completion {
10320 Color::Muted
10321 } else {
10322 Color::Default
10323 };
10324
10325 if keybind_display.action == EditPredictionKeybindAction::Preview {
10326 el.child(
10327 h_flex()
10328 .h_full()
10329 .border_l_1()
10330 .rounded_r_lg()
10331 .border_color(cx.theme().colors().border)
10332 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10333 .gap_1()
10334 .py_1()
10335 .px_2()
10336 .child(self.render_edit_prediction_popover_keystroke(
10337 keystroke, key_color, cx,
10338 ))
10339 .child(Label::new("Preview").into_any_element())
10340 .opacity(if has_completion { 1.0 } else { 0.4 }),
10341 )
10342 } else {
10343 el.child(
10344 h_flex()
10345 .h_full()
10346 .border_l_1()
10347 .rounded_r_lg()
10348 .border_color(cx.theme().colors().border)
10349 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10350 .gap_1()
10351 .py_1()
10352 .px_2()
10353 .child(self.render_edit_prediction_popover_keystroke(
10354 keystroke, key_color, cx,
10355 ))
10356 .opacity(if has_completion { 1.0 } else { 0.4 }),
10357 )
10358 }
10359 },
10360 )
10361 .into_any(),
10362 )
10363 }
10364
10365 fn render_edit_prediction_cursor_popover_preview(
10366 &self,
10367 completion: &EditPredictionState,
10368 cursor_point: Point,
10369 style: &EditorStyle,
10370 cx: &mut Context<Editor>,
10371 ) -> Option<Div> {
10372 use text::ToPoint as _;
10373
10374 fn render_relative_row_jump(
10375 prefix: impl Into<String>,
10376 current_row: u32,
10377 target_row: u32,
10378 ) -> Div {
10379 let (row_diff, arrow) = if target_row < current_row {
10380 (current_row - target_row, IconName::ArrowUp)
10381 } else {
10382 (target_row - current_row, IconName::ArrowDown)
10383 };
10384
10385 h_flex()
10386 .child(
10387 Label::new(format!("{}{}", prefix.into(), row_diff))
10388 .color(Color::Muted)
10389 .size(LabelSize::Small),
10390 )
10391 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10392 }
10393
10394 let supports_jump = self
10395 .edit_prediction_provider
10396 .as_ref()
10397 .map(|provider| provider.provider.supports_jump_to_edit())
10398 .unwrap_or(true);
10399
10400 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10401
10402 match &completion.completion {
10403 EditPrediction::MoveWithin {
10404 target, snapshot, ..
10405 } => {
10406 if !supports_jump {
10407 return None;
10408 }
10409
10410 Some(
10411 h_flex()
10412 .px_2()
10413 .gap_2()
10414 .flex_1()
10415 .child(
10416 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10417 Icon::new(icons.down)
10418 } else {
10419 Icon::new(icons.up)
10420 },
10421 )
10422 .child(Label::new("Jump to Edit")),
10423 )
10424 }
10425 EditPrediction::MoveOutside { snapshot, .. } => {
10426 let file_name = snapshot
10427 .file()
10428 .map(|file| file.file_name(cx))
10429 .unwrap_or("untitled");
10430 Some(
10431 h_flex()
10432 .px_2()
10433 .gap_2()
10434 .flex_1()
10435 .child(Icon::new(icons.base))
10436 .child(Label::new(format!("Jump to {file_name}"))),
10437 )
10438 }
10439 EditPrediction::Edit {
10440 edits,
10441 edit_preview,
10442 snapshot,
10443 ..
10444 } => {
10445 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10446
10447 let (highlighted_edits, has_more_lines) =
10448 if let Some(edit_preview) = edit_preview.as_ref() {
10449 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10450 .first_line_preview()
10451 } else {
10452 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10453 };
10454
10455 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10456 .with_default_highlights(&style.text, highlighted_edits.highlights);
10457
10458 let preview = h_flex()
10459 .gap_1()
10460 .min_w_16()
10461 .child(styled_text)
10462 .when(has_more_lines, |parent| parent.child("…"));
10463
10464 let left = if supports_jump && first_edit_row != cursor_point.row {
10465 render_relative_row_jump("", cursor_point.row, first_edit_row)
10466 .into_any_element()
10467 } else {
10468 Icon::new(icons.base).into_any_element()
10469 };
10470
10471 Some(
10472 h_flex()
10473 .h_full()
10474 .flex_1()
10475 .gap_2()
10476 .pr_1()
10477 .overflow_x_hidden()
10478 .font(
10479 theme_settings::ThemeSettings::get_global(cx)
10480 .buffer_font
10481 .clone(),
10482 )
10483 .child(left)
10484 .child(preview),
10485 )
10486 }
10487 }
10488 }
10489
10490 pub fn render_context_menu(
10491 &mut self,
10492 max_height_in_lines: u32,
10493 window: &mut Window,
10494 cx: &mut Context<Editor>,
10495 ) -> Option<AnyElement> {
10496 let menu = self.context_menu.borrow();
10497 let menu = menu.as_ref()?;
10498 if !menu.visible() {
10499 return None;
10500 };
10501 self.style
10502 .as_ref()
10503 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10504 }
10505
10506 fn render_context_menu_aside(
10507 &mut self,
10508 max_size: Size<Pixels>,
10509 window: &mut Window,
10510 cx: &mut Context<Editor>,
10511 ) -> Option<AnyElement> {
10512 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10513 if menu.visible() {
10514 menu.render_aside(max_size, window, cx)
10515 } else {
10516 None
10517 }
10518 })
10519 }
10520
10521 fn hide_context_menu(
10522 &mut self,
10523 window: &mut Window,
10524 cx: &mut Context<Self>,
10525 ) -> Option<CodeContextMenu> {
10526 cx.notify();
10527 self.completion_tasks.clear();
10528 let context_menu = self.context_menu.borrow_mut().take();
10529 self.stale_edit_prediction_in_menu.take();
10530 self.update_visible_edit_prediction(window, cx);
10531 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10532 && let Some(completion_provider) = &self.completion_provider
10533 {
10534 completion_provider.selection_changed(None, window, cx);
10535 }
10536 context_menu
10537 }
10538
10539 fn show_snippet_choices(
10540 &mut self,
10541 choices: &Vec<String>,
10542 selection: Range<Anchor>,
10543 cx: &mut Context<Self>,
10544 ) {
10545 let Some((_, buffer, _)) = self
10546 .buffer()
10547 .read(cx)
10548 .excerpt_containing(selection.start, cx)
10549 else {
10550 return;
10551 };
10552 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10553 else {
10554 return;
10555 };
10556 if buffer != end_buffer {
10557 log::error!("expected anchor range to have matching buffer IDs");
10558 return;
10559 }
10560
10561 let id = post_inc(&mut self.next_completion_id);
10562 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10563 let mut context_menu = self.context_menu.borrow_mut();
10564 let old_menu = context_menu.take();
10565 *context_menu = Some(CodeContextMenu::Completions(
10566 CompletionsMenu::new_snippet_choices(
10567 id,
10568 true,
10569 choices,
10570 selection,
10571 buffer,
10572 old_menu.map(|menu| menu.primary_scroll_handle()),
10573 snippet_sort_order,
10574 ),
10575 ));
10576 }
10577
10578 pub fn insert_snippet(
10579 &mut self,
10580 insertion_ranges: &[Range<MultiBufferOffset>],
10581 snippet: Snippet,
10582 window: &mut Window,
10583 cx: &mut Context<Self>,
10584 ) -> Result<()> {
10585 struct Tabstop<T> {
10586 is_end_tabstop: bool,
10587 ranges: Vec<Range<T>>,
10588 choices: Option<Vec<String>>,
10589 }
10590
10591 let tabstops = self.buffer.update(cx, |buffer, cx| {
10592 let snippet_text: Arc<str> = snippet.text.clone().into();
10593 let edits = insertion_ranges
10594 .iter()
10595 .cloned()
10596 .map(|range| (range, snippet_text.clone()));
10597 let autoindent_mode = AutoindentMode::Block {
10598 original_indent_columns: Vec::new(),
10599 };
10600 buffer.edit(edits, Some(autoindent_mode), cx);
10601
10602 let snapshot = &*buffer.read(cx);
10603 let snippet = &snippet;
10604 snippet
10605 .tabstops
10606 .iter()
10607 .map(|tabstop| {
10608 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10609 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10610 });
10611 let mut tabstop_ranges = tabstop
10612 .ranges
10613 .iter()
10614 .flat_map(|tabstop_range| {
10615 let mut delta = 0_isize;
10616 insertion_ranges.iter().map(move |insertion_range| {
10617 let insertion_start = insertion_range.start + delta;
10618 delta += snippet.text.len() as isize
10619 - (insertion_range.end - insertion_range.start) as isize;
10620
10621 let start =
10622 (insertion_start + tabstop_range.start).min(snapshot.len());
10623 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10624 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10625 })
10626 })
10627 .collect::<Vec<_>>();
10628 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10629
10630 Tabstop {
10631 is_end_tabstop,
10632 ranges: tabstop_ranges,
10633 choices: tabstop.choices.clone(),
10634 }
10635 })
10636 .collect::<Vec<_>>()
10637 });
10638 if let Some(tabstop) = tabstops.first() {
10639 self.change_selections(Default::default(), window, cx, |s| {
10640 // Reverse order so that the first range is the newest created selection.
10641 // Completions will use it and autoscroll will prioritize it.
10642 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10643 });
10644
10645 if let Some(choices) = &tabstop.choices
10646 && let Some(selection) = tabstop.ranges.first()
10647 {
10648 self.show_snippet_choices(choices, selection.clone(), cx)
10649 }
10650
10651 // If we're already at the last tabstop and it's at the end of the snippet,
10652 // we're done, we don't need to keep the state around.
10653 if !tabstop.is_end_tabstop {
10654 let choices = tabstops
10655 .iter()
10656 .map(|tabstop| tabstop.choices.clone())
10657 .collect();
10658
10659 let ranges = tabstops
10660 .into_iter()
10661 .map(|tabstop| tabstop.ranges)
10662 .collect::<Vec<_>>();
10663
10664 self.snippet_stack.push(SnippetState {
10665 active_index: 0,
10666 ranges,
10667 choices,
10668 });
10669 }
10670
10671 // Check whether the just-entered snippet ends with an auto-closable bracket.
10672 if self.autoclose_regions.is_empty() {
10673 let snapshot = self.buffer.read(cx).snapshot(cx);
10674 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10675 let selection_head = selection.head();
10676 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10677 continue;
10678 };
10679
10680 let mut bracket_pair = None;
10681 let max_lookup_length = scope
10682 .brackets()
10683 .map(|(pair, _)| {
10684 pair.start
10685 .as_str()
10686 .chars()
10687 .count()
10688 .max(pair.end.as_str().chars().count())
10689 })
10690 .max();
10691 if let Some(max_lookup_length) = max_lookup_length {
10692 let next_text = snapshot
10693 .chars_at(selection_head)
10694 .take(max_lookup_length)
10695 .collect::<String>();
10696 let prev_text = snapshot
10697 .reversed_chars_at(selection_head)
10698 .take(max_lookup_length)
10699 .collect::<String>();
10700
10701 for (pair, enabled) in scope.brackets() {
10702 if enabled
10703 && pair.close
10704 && prev_text.starts_with(pair.start.as_str())
10705 && next_text.starts_with(pair.end.as_str())
10706 {
10707 bracket_pair = Some(pair.clone());
10708 break;
10709 }
10710 }
10711 }
10712
10713 if let Some(pair) = bracket_pair {
10714 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10715 let autoclose_enabled =
10716 self.use_autoclose && snapshot_settings.use_autoclose;
10717 if autoclose_enabled {
10718 let start = snapshot.anchor_after(selection_head);
10719 let end = snapshot.anchor_after(selection_head);
10720 self.autoclose_regions.push(AutocloseRegion {
10721 selection_id: selection.id,
10722 range: start..end,
10723 pair,
10724 });
10725 }
10726 }
10727 }
10728 }
10729 }
10730 Ok(())
10731 }
10732
10733 pub fn move_to_next_snippet_tabstop(
10734 &mut self,
10735 window: &mut Window,
10736 cx: &mut Context<Self>,
10737 ) -> bool {
10738 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10739 }
10740
10741 pub fn move_to_prev_snippet_tabstop(
10742 &mut self,
10743 window: &mut Window,
10744 cx: &mut Context<Self>,
10745 ) -> bool {
10746 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10747 }
10748
10749 pub fn move_to_snippet_tabstop(
10750 &mut self,
10751 bias: Bias,
10752 window: &mut Window,
10753 cx: &mut Context<Self>,
10754 ) -> bool {
10755 if let Some(mut snippet) = self.snippet_stack.pop() {
10756 match bias {
10757 Bias::Left => {
10758 if snippet.active_index > 0 {
10759 snippet.active_index -= 1;
10760 } else {
10761 self.snippet_stack.push(snippet);
10762 return false;
10763 }
10764 }
10765 Bias::Right => {
10766 if snippet.active_index + 1 < snippet.ranges.len() {
10767 snippet.active_index += 1;
10768 } else {
10769 self.snippet_stack.push(snippet);
10770 return false;
10771 }
10772 }
10773 }
10774 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10775 self.change_selections(Default::default(), window, cx, |s| {
10776 // Reverse order so that the first range is the newest created selection.
10777 // Completions will use it and autoscroll will prioritize it.
10778 s.select_ranges(current_ranges.iter().rev().cloned())
10779 });
10780
10781 if let Some(choices) = &snippet.choices[snippet.active_index]
10782 && let Some(selection) = current_ranges.first()
10783 {
10784 self.show_snippet_choices(choices, selection.clone(), cx);
10785 }
10786
10787 // If snippet state is not at the last tabstop, push it back on the stack
10788 if snippet.active_index + 1 < snippet.ranges.len() {
10789 self.snippet_stack.push(snippet);
10790 }
10791 return true;
10792 }
10793 }
10794
10795 false
10796 }
10797
10798 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10799 self.transact(window, cx, |this, window, cx| {
10800 this.select_all(&SelectAll, window, cx);
10801 this.insert("", window, cx);
10802 });
10803 }
10804
10805 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10806 if self.read_only(cx) {
10807 return;
10808 }
10809 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10810 self.transact(window, cx, |this, window, cx| {
10811 this.select_autoclose_pair(window, cx);
10812
10813 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10814
10815 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10816 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10817 for selection in &mut selections {
10818 if selection.is_empty() {
10819 let old_head = selection.head();
10820 let mut new_head =
10821 movement::left(&display_map, old_head.to_display_point(&display_map))
10822 .to_point(&display_map);
10823 if let Some((buffer, line_buffer_range)) = display_map
10824 .buffer_snapshot()
10825 .buffer_line_for_row(MultiBufferRow(old_head.row))
10826 {
10827 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10828 let indent_len = match indent_size.kind {
10829 IndentKind::Space => {
10830 buffer.settings_at(line_buffer_range.start, cx).tab_size
10831 }
10832 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10833 };
10834 if old_head.column <= indent_size.len && old_head.column > 0 {
10835 let indent_len = indent_len.get();
10836 new_head = cmp::min(
10837 new_head,
10838 MultiBufferPoint::new(
10839 old_head.row,
10840 ((old_head.column - 1) / indent_len) * indent_len,
10841 ),
10842 );
10843 }
10844 }
10845
10846 selection.set_head(new_head, SelectionGoal::None);
10847 }
10848 }
10849
10850 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10851 this.insert("", window, cx);
10852 linked_edits.apply_with_left_expansion(cx);
10853 this.refresh_edit_prediction(true, false, window, cx);
10854 refresh_linked_ranges(this, window, cx);
10855 });
10856 }
10857
10858 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10859 if self.read_only(cx) {
10860 return;
10861 }
10862 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10863 self.transact(window, cx, |this, window, cx| {
10864 this.change_selections(Default::default(), window, cx, |s| {
10865 s.move_with(&mut |map, selection| {
10866 if selection.is_empty() {
10867 let cursor = movement::right(map, selection.head());
10868 selection.end = cursor;
10869 selection.reversed = true;
10870 selection.goal = SelectionGoal::None;
10871 }
10872 })
10873 });
10874 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10875 this.insert("", window, cx);
10876 linked_edits.apply(cx);
10877 this.refresh_edit_prediction(true, false, window, cx);
10878 refresh_linked_ranges(this, window, cx);
10879 });
10880 }
10881
10882 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10883 if self.mode.is_single_line() {
10884 cx.propagate();
10885 return;
10886 }
10887
10888 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10889 if self.move_to_prev_snippet_tabstop(window, cx) {
10890 return;
10891 }
10892 self.outdent(&Outdent, window, cx);
10893 }
10894
10895 pub fn next_snippet_tabstop(
10896 &mut self,
10897 _: &NextSnippetTabstop,
10898 window: &mut Window,
10899 cx: &mut Context<Self>,
10900 ) {
10901 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10902 cx.propagate();
10903 return;
10904 }
10905
10906 if self.move_to_next_snippet_tabstop(window, cx) {
10907 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10908 return;
10909 }
10910 cx.propagate();
10911 }
10912
10913 pub fn previous_snippet_tabstop(
10914 &mut self,
10915 _: &PreviousSnippetTabstop,
10916 window: &mut Window,
10917 cx: &mut Context<Self>,
10918 ) {
10919 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10920 cx.propagate();
10921 return;
10922 }
10923
10924 if self.move_to_prev_snippet_tabstop(window, cx) {
10925 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10926 return;
10927 }
10928 cx.propagate();
10929 }
10930
10931 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10932 if self.mode.is_single_line() {
10933 cx.propagate();
10934 return;
10935 }
10936
10937 if self.move_to_next_snippet_tabstop(window, cx) {
10938 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10939 return;
10940 }
10941 if self.read_only(cx) {
10942 return;
10943 }
10944 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10945 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10946 let buffer = self.buffer.read(cx);
10947 let snapshot = buffer.snapshot(cx);
10948 let rows_iter = selections.iter().map(|s| s.head().row);
10949 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10950
10951 let has_some_cursor_in_whitespace = selections
10952 .iter()
10953 .filter(|selection| selection.is_empty())
10954 .any(|selection| {
10955 let cursor = selection.head();
10956 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10957 cursor.column < current_indent.len
10958 });
10959
10960 let mut edits = Vec::new();
10961 let mut prev_edited_row = 0;
10962 let mut row_delta = 0;
10963 for selection in &mut selections {
10964 if selection.start.row != prev_edited_row {
10965 row_delta = 0;
10966 }
10967 prev_edited_row = selection.end.row;
10968
10969 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10970 if selection.is_empty() {
10971 let cursor = selection.head();
10972 let settings = buffer.language_settings_at(cursor, cx);
10973 if settings.indent_list_on_tab {
10974 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10975 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10976 row_delta = Self::indent_selection(
10977 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10978 );
10979 continue;
10980 }
10981 }
10982 }
10983 }
10984
10985 // If the selection is non-empty, then increase the indentation of the selected lines.
10986 if !selection.is_empty() {
10987 row_delta =
10988 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10989 continue;
10990 }
10991
10992 let cursor = selection.head();
10993 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10994 if let Some(suggested_indent) =
10995 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10996 {
10997 // Don't do anything if already at suggested indent
10998 // and there is any other cursor which is not
10999 if has_some_cursor_in_whitespace
11000 && cursor.column == current_indent.len
11001 && current_indent.len == suggested_indent.len
11002 {
11003 continue;
11004 }
11005
11006 // Adjust line and move cursor to suggested indent
11007 // if cursor is not at suggested indent
11008 if cursor.column < suggested_indent.len
11009 && cursor.column <= current_indent.len
11010 && current_indent.len <= suggested_indent.len
11011 {
11012 selection.start = Point::new(cursor.row, suggested_indent.len);
11013 selection.end = selection.start;
11014 if row_delta == 0 {
11015 edits.extend(Buffer::edit_for_indent_size_adjustment(
11016 cursor.row,
11017 current_indent,
11018 suggested_indent,
11019 ));
11020 row_delta = suggested_indent.len - current_indent.len;
11021 }
11022 continue;
11023 }
11024
11025 // If current indent is more than suggested indent
11026 // only move cursor to current indent and skip indent
11027 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
11028 selection.start = Point::new(cursor.row, current_indent.len);
11029 selection.end = selection.start;
11030 continue;
11031 }
11032 }
11033
11034 // Otherwise, insert a hard or soft tab.
11035 let settings = buffer.language_settings_at(cursor, cx);
11036 let tab_size = if settings.hard_tabs {
11037 IndentSize::tab()
11038 } else {
11039 let tab_size = settings.tab_size.get();
11040 let indent_remainder = snapshot
11041 .text_for_range(Point::new(cursor.row, 0)..cursor)
11042 .flat_map(str::chars)
11043 .fold(row_delta % tab_size, |counter: u32, c| {
11044 if c == '\t' {
11045 0
11046 } else {
11047 (counter + 1) % tab_size
11048 }
11049 });
11050
11051 let chars_to_next_tab_stop = tab_size - indent_remainder;
11052 IndentSize::spaces(chars_to_next_tab_stop)
11053 };
11054 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
11055 selection.end = selection.start;
11056 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11057 row_delta += tab_size.len;
11058 }
11059
11060 self.transact(window, cx, |this, window, cx| {
11061 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11062 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11063 this.refresh_edit_prediction(true, false, window, cx);
11064 });
11065 }
11066
11067 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11068 if self.read_only(cx) {
11069 return;
11070 }
11071 if self.mode.is_single_line() {
11072 cx.propagate();
11073 return;
11074 }
11075
11076 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11077 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11078 let mut prev_edited_row = 0;
11079 let mut row_delta = 0;
11080 let mut edits = Vec::new();
11081 let buffer = self.buffer.read(cx);
11082 let snapshot = buffer.snapshot(cx);
11083 for selection in &mut selections {
11084 if selection.start.row != prev_edited_row {
11085 row_delta = 0;
11086 }
11087 prev_edited_row = selection.end.row;
11088
11089 row_delta =
11090 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11091 }
11092
11093 self.transact(window, cx, |this, window, cx| {
11094 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11095 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11096 });
11097 }
11098
11099 fn indent_selection(
11100 buffer: &MultiBuffer,
11101 snapshot: &MultiBufferSnapshot,
11102 selection: &mut Selection<Point>,
11103 edits: &mut Vec<(Range<Point>, String)>,
11104 delta_for_start_row: u32,
11105 cx: &App,
11106 ) -> u32 {
11107 let settings = buffer.language_settings_at(selection.start, cx);
11108 let tab_size = settings.tab_size.get();
11109 let indent_kind = if settings.hard_tabs {
11110 IndentKind::Tab
11111 } else {
11112 IndentKind::Space
11113 };
11114 let mut start_row = selection.start.row;
11115 let mut end_row = selection.end.row + 1;
11116
11117 // If a selection ends at the beginning of a line, don't indent
11118 // that last line.
11119 if selection.end.column == 0 && selection.end.row > selection.start.row {
11120 end_row -= 1;
11121 }
11122
11123 // Avoid re-indenting a row that has already been indented by a
11124 // previous selection, but still update this selection's column
11125 // to reflect that indentation.
11126 if delta_for_start_row > 0 {
11127 start_row += 1;
11128 selection.start.column += delta_for_start_row;
11129 if selection.end.row == selection.start.row {
11130 selection.end.column += delta_for_start_row;
11131 }
11132 }
11133
11134 let mut delta_for_end_row = 0;
11135 let has_multiple_rows = start_row + 1 != end_row;
11136 for row in start_row..end_row {
11137 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11138 let indent_delta = match (current_indent.kind, indent_kind) {
11139 (IndentKind::Space, IndentKind::Space) => {
11140 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11141 IndentSize::spaces(columns_to_next_tab_stop)
11142 }
11143 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11144 (_, IndentKind::Tab) => IndentSize::tab(),
11145 };
11146
11147 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11148 0
11149 } else {
11150 selection.start.column
11151 };
11152 let row_start = Point::new(row, start);
11153 edits.push((
11154 row_start..row_start,
11155 indent_delta.chars().collect::<String>(),
11156 ));
11157
11158 // Update this selection's endpoints to reflect the indentation.
11159 if row == selection.start.row {
11160 selection.start.column += indent_delta.len;
11161 }
11162 if row == selection.end.row {
11163 selection.end.column += indent_delta.len;
11164 delta_for_end_row = indent_delta.len;
11165 }
11166 }
11167
11168 if selection.start.row == selection.end.row {
11169 delta_for_start_row + delta_for_end_row
11170 } else {
11171 delta_for_end_row
11172 }
11173 }
11174
11175 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11176 if self.read_only(cx) {
11177 return;
11178 }
11179 if self.mode.is_single_line() {
11180 cx.propagate();
11181 return;
11182 }
11183
11184 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11185 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11186 let selections = self.selections.all::<Point>(&display_map);
11187 let mut deletion_ranges = Vec::new();
11188 let mut last_outdent = None;
11189 {
11190 let buffer = self.buffer.read(cx);
11191 let snapshot = buffer.snapshot(cx);
11192 for selection in &selections {
11193 let settings = buffer.language_settings_at(selection.start, cx);
11194 let tab_size = settings.tab_size.get();
11195 let mut rows = selection.spanned_rows(false, &display_map);
11196
11197 // Avoid re-outdenting a row that has already been outdented by a
11198 // previous selection.
11199 if let Some(last_row) = last_outdent
11200 && last_row == rows.start
11201 {
11202 rows.start = rows.start.next_row();
11203 }
11204 let has_multiple_rows = rows.len() > 1;
11205 for row in rows.iter_rows() {
11206 let indent_size = snapshot.indent_size_for_line(row);
11207 if indent_size.len > 0 {
11208 let deletion_len = match indent_size.kind {
11209 IndentKind::Space => {
11210 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11211 if columns_to_prev_tab_stop == 0 {
11212 tab_size
11213 } else {
11214 columns_to_prev_tab_stop
11215 }
11216 }
11217 IndentKind::Tab => 1,
11218 };
11219 let start = if has_multiple_rows
11220 || deletion_len > selection.start.column
11221 || indent_size.len < selection.start.column
11222 {
11223 0
11224 } else {
11225 selection.start.column - deletion_len
11226 };
11227 deletion_ranges.push(
11228 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11229 );
11230 last_outdent = Some(row);
11231 }
11232 }
11233 }
11234 }
11235
11236 self.transact(window, cx, |this, window, cx| {
11237 this.buffer.update(cx, |buffer, cx| {
11238 let empty_str: Arc<str> = Arc::default();
11239 buffer.edit(
11240 deletion_ranges
11241 .into_iter()
11242 .map(|range| (range, empty_str.clone())),
11243 None,
11244 cx,
11245 );
11246 });
11247 let selections = this
11248 .selections
11249 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11250 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11251 });
11252 }
11253
11254 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11255 if self.read_only(cx) {
11256 return;
11257 }
11258 if self.mode.is_single_line() {
11259 cx.propagate();
11260 return;
11261 }
11262
11263 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11264 let selections = self
11265 .selections
11266 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11267 .into_iter()
11268 .map(|s| s.range());
11269
11270 self.transact(window, cx, |this, window, cx| {
11271 this.buffer.update(cx, |buffer, cx| {
11272 buffer.autoindent_ranges(selections, cx);
11273 });
11274 let selections = this
11275 .selections
11276 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11277 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11278 });
11279 }
11280
11281 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11282 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11283 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11284 let selections = self.selections.all::<Point>(&display_map);
11285
11286 let mut new_cursors = Vec::new();
11287 let mut edit_ranges = Vec::new();
11288 let mut selections = selections.iter().peekable();
11289 while let Some(selection) = selections.next() {
11290 let mut rows = selection.spanned_rows(false, &display_map);
11291
11292 // Accumulate contiguous regions of rows that we want to delete.
11293 while let Some(next_selection) = selections.peek() {
11294 let next_rows = next_selection.spanned_rows(false, &display_map);
11295 if next_rows.start <= rows.end {
11296 rows.end = next_rows.end;
11297 selections.next().unwrap();
11298 } else {
11299 break;
11300 }
11301 }
11302
11303 let buffer = display_map.buffer_snapshot();
11304 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11305 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11306 // If there's a line after the range, delete the \n from the end of the row range
11307 (
11308 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11309 rows.end,
11310 )
11311 } else {
11312 // If there isn't a line after the range, delete the \n from the line before the
11313 // start of the row range
11314 edit_start = edit_start.saturating_sub_usize(1);
11315 (buffer.len(), rows.start.previous_row())
11316 };
11317
11318 let text_layout_details = self.text_layout_details(window, cx);
11319 let x = display_map.x_for_display_point(
11320 selection.head().to_display_point(&display_map),
11321 &text_layout_details,
11322 );
11323 let row = Point::new(target_row.0, 0)
11324 .to_display_point(&display_map)
11325 .row();
11326 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11327
11328 new_cursors.push((
11329 selection.id,
11330 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11331 SelectionGoal::None,
11332 ));
11333 edit_ranges.push(edit_start..edit_end);
11334 }
11335
11336 self.transact(window, cx, |this, window, cx| {
11337 let buffer = this.buffer.update(cx, |buffer, cx| {
11338 let empty_str: Arc<str> = Arc::default();
11339 buffer.edit(
11340 edit_ranges
11341 .into_iter()
11342 .map(|range| (range, empty_str.clone())),
11343 None,
11344 cx,
11345 );
11346 buffer.snapshot(cx)
11347 });
11348 let new_selections = new_cursors
11349 .into_iter()
11350 .map(|(id, cursor, goal)| {
11351 let cursor = cursor.to_point(&buffer);
11352 Selection {
11353 id,
11354 start: cursor,
11355 end: cursor,
11356 reversed: false,
11357 goal,
11358 }
11359 })
11360 .collect();
11361
11362 this.change_selections(Default::default(), window, cx, |s| {
11363 s.select(new_selections);
11364 });
11365 });
11366 }
11367
11368 pub fn join_lines_impl(
11369 &mut self,
11370 insert_whitespace: bool,
11371 window: &mut Window,
11372 cx: &mut Context<Self>,
11373 ) {
11374 if self.read_only(cx) {
11375 return;
11376 }
11377 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11378 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11379 let start = MultiBufferRow(selection.start.row);
11380 // Treat single line selections as if they include the next line. Otherwise this action
11381 // would do nothing for single line selections individual cursors.
11382 let end = if selection.start.row == selection.end.row {
11383 MultiBufferRow(selection.start.row + 1)
11384 } else if selection.end.column == 0 {
11385 // If the selection ends at the start of a line, it's logically at the end of the
11386 // previous line (plus its newline).
11387 // Don't include the end line unless there's only one line selected.
11388 if selection.start.row + 1 == selection.end.row {
11389 MultiBufferRow(selection.end.row)
11390 } else {
11391 MultiBufferRow(selection.end.row - 1)
11392 }
11393 } else {
11394 MultiBufferRow(selection.end.row)
11395 };
11396
11397 if let Some(last_row_range) = row_ranges.last_mut()
11398 && start <= last_row_range.end
11399 {
11400 last_row_range.end = end;
11401 continue;
11402 }
11403 row_ranges.push(start..end);
11404 }
11405
11406 let snapshot = self.buffer.read(cx).snapshot(cx);
11407 let mut cursor_positions = Vec::new();
11408 for row_range in &row_ranges {
11409 let anchor = snapshot.anchor_before(Point::new(
11410 row_range.end.previous_row().0,
11411 snapshot.line_len(row_range.end.previous_row()),
11412 ));
11413 cursor_positions.push(anchor..anchor);
11414 }
11415
11416 self.transact(window, cx, |this, window, cx| {
11417 for row_range in row_ranges.into_iter().rev() {
11418 for row in row_range.iter_rows().rev() {
11419 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11420 let next_line_row = row.next_row();
11421 let indent = snapshot.indent_size_for_line(next_line_row);
11422 let mut join_start_column = indent.len;
11423
11424 if let Some(language_scope) =
11425 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11426 {
11427 let line_end =
11428 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11429 let line_text_after_indent = snapshot
11430 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11431 .collect::<String>();
11432
11433 if !line_text_after_indent.is_empty() {
11434 let block_prefix = language_scope
11435 .block_comment()
11436 .map(|c| c.prefix.as_ref())
11437 .filter(|p| !p.is_empty());
11438 let doc_prefix = language_scope
11439 .documentation_comment()
11440 .map(|c| c.prefix.as_ref())
11441 .filter(|p| !p.is_empty());
11442 let all_prefixes = language_scope
11443 .line_comment_prefixes()
11444 .iter()
11445 .map(|p| p.as_ref())
11446 .chain(block_prefix)
11447 .chain(doc_prefix)
11448 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11449
11450 let mut longest_prefix_len = None;
11451 for prefix in all_prefixes {
11452 let trimmed = prefix.trim_end();
11453 if line_text_after_indent.starts_with(trimmed) {
11454 let candidate_len =
11455 if line_text_after_indent.starts_with(prefix) {
11456 prefix.len()
11457 } else {
11458 trimmed.len()
11459 };
11460 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11461 longest_prefix_len = Some(candidate_len);
11462 }
11463 }
11464 }
11465
11466 if let Some(prefix_len) = longest_prefix_len {
11467 join_start_column =
11468 join_start_column.saturating_add(prefix_len as u32);
11469 }
11470 }
11471 }
11472
11473 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11474
11475 let replace = if snapshot.line_len(next_line_row) > join_start_column
11476 && insert_whitespace
11477 {
11478 " "
11479 } else {
11480 ""
11481 };
11482
11483 this.buffer.update(cx, |buffer, cx| {
11484 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11485 });
11486 }
11487 }
11488
11489 this.change_selections(Default::default(), window, cx, |s| {
11490 s.select_anchor_ranges(cursor_positions)
11491 });
11492 });
11493 }
11494
11495 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11496 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11497 self.join_lines_impl(true, window, cx);
11498 }
11499
11500 pub fn sort_lines_case_sensitive(
11501 &mut self,
11502 _: &SortLinesCaseSensitive,
11503 window: &mut Window,
11504 cx: &mut Context<Self>,
11505 ) {
11506 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11507 }
11508
11509 pub fn sort_lines_by_length(
11510 &mut self,
11511 _: &SortLinesByLength,
11512 window: &mut Window,
11513 cx: &mut Context<Self>,
11514 ) {
11515 self.manipulate_immutable_lines(window, cx, |lines| {
11516 lines.sort_by_key(|&line| line.chars().count())
11517 })
11518 }
11519
11520 pub fn sort_lines_case_insensitive(
11521 &mut self,
11522 _: &SortLinesCaseInsensitive,
11523 window: &mut Window,
11524 cx: &mut Context<Self>,
11525 ) {
11526 self.manipulate_immutable_lines(window, cx, |lines| {
11527 lines.sort_by_key(|line| line.to_lowercase())
11528 })
11529 }
11530
11531 pub fn unique_lines_case_insensitive(
11532 &mut self,
11533 _: &UniqueLinesCaseInsensitive,
11534 window: &mut Window,
11535 cx: &mut Context<Self>,
11536 ) {
11537 self.manipulate_immutable_lines(window, cx, |lines| {
11538 let mut seen = HashSet::default();
11539 lines.retain(|line| seen.insert(line.to_lowercase()));
11540 })
11541 }
11542
11543 pub fn unique_lines_case_sensitive(
11544 &mut self,
11545 _: &UniqueLinesCaseSensitive,
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));
11552 })
11553 }
11554
11555 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11556 let snapshot = self.buffer.read(cx).snapshot(cx);
11557 for selection in self.selections.disjoint_anchors_arc().iter() {
11558 if snapshot
11559 .language_at(selection.start)
11560 .and_then(|lang| lang.config().wrap_characters.as_ref())
11561 .is_some()
11562 {
11563 return true;
11564 }
11565 }
11566 false
11567 }
11568
11569 fn wrap_selections_in_tag(
11570 &mut self,
11571 _: &WrapSelectionsInTag,
11572 window: &mut Window,
11573 cx: &mut Context<Self>,
11574 ) {
11575 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11576
11577 let snapshot = self.buffer.read(cx).snapshot(cx);
11578
11579 let mut edits = Vec::new();
11580 let mut boundaries = Vec::new();
11581
11582 for selection in self
11583 .selections
11584 .all_adjusted(&self.display_snapshot(cx))
11585 .iter()
11586 {
11587 let Some(wrap_config) = snapshot
11588 .language_at(selection.start)
11589 .and_then(|lang| lang.config().wrap_characters.clone())
11590 else {
11591 continue;
11592 };
11593
11594 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11595 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11596
11597 let start_before = snapshot.anchor_before(selection.start);
11598 let end_after = snapshot.anchor_after(selection.end);
11599
11600 edits.push((start_before..start_before, open_tag));
11601 edits.push((end_after..end_after, close_tag));
11602
11603 boundaries.push((
11604 start_before,
11605 end_after,
11606 wrap_config.start_prefix.len(),
11607 wrap_config.end_suffix.len(),
11608 ));
11609 }
11610
11611 if edits.is_empty() {
11612 return;
11613 }
11614
11615 self.transact(window, cx, |this, window, cx| {
11616 let buffer = this.buffer.update(cx, |buffer, cx| {
11617 buffer.edit(edits, None, cx);
11618 buffer.snapshot(cx)
11619 });
11620
11621 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11622 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11623 boundaries.into_iter()
11624 {
11625 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11626 let close_offset = end_after
11627 .to_offset(&buffer)
11628 .saturating_sub_usize(end_suffix_len);
11629 new_selections.push(open_offset..open_offset);
11630 new_selections.push(close_offset..close_offset);
11631 }
11632
11633 this.change_selections(Default::default(), window, cx, |s| {
11634 s.select_ranges(new_selections);
11635 });
11636
11637 this.request_autoscroll(Autoscroll::fit(), cx);
11638 });
11639 }
11640
11641 pub fn toggle_read_only(
11642 &mut self,
11643 _: &workspace::ToggleReadOnlyFile,
11644 _: &mut Window,
11645 cx: &mut Context<Self>,
11646 ) {
11647 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11648 buffer.update(cx, |buffer, cx| {
11649 buffer.set_capability(
11650 match buffer.capability() {
11651 Capability::ReadWrite => Capability::Read,
11652 Capability::Read => Capability::ReadWrite,
11653 Capability::ReadOnly => Capability::ReadOnly,
11654 },
11655 cx,
11656 );
11657 })
11658 }
11659 }
11660
11661 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11662 let Some(project) = self.project.clone() else {
11663 return;
11664 };
11665 let task = self.reload(project, window, cx);
11666 self.detach_and_notify_err(task, window, cx);
11667 }
11668
11669 pub fn restore_file(
11670 &mut self,
11671 _: &::git::RestoreFile,
11672 window: &mut Window,
11673 cx: &mut Context<Self>,
11674 ) {
11675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11676 let mut buffer_ids = HashSet::default();
11677 let snapshot = self.buffer().read(cx).snapshot(cx);
11678 for selection in self
11679 .selections
11680 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11681 {
11682 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11683 }
11684
11685 let buffer = self.buffer().read(cx);
11686 let ranges = buffer_ids
11687 .into_iter()
11688 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11689 .collect::<Vec<_>>();
11690
11691 self.restore_hunks_in_ranges(ranges, window, cx);
11692 }
11693
11694 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11695 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11696 let selections = self
11697 .selections
11698 .all(&self.display_snapshot(cx))
11699 .into_iter()
11700 .map(|s| s.range())
11701 .collect();
11702 self.restore_hunks_in_ranges(selections, window, cx);
11703 }
11704
11705 /// Restores the diff hunks in the editor's selections and moves the cursor
11706 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11707 /// not all diff hunks are expanded.
11708 pub fn restore_and_next(
11709 &mut self,
11710 _: &::git::RestoreAndNext,
11711 window: &mut Window,
11712 cx: &mut Context<Self>,
11713 ) {
11714 let selections = self
11715 .selections
11716 .all(&self.display_snapshot(cx))
11717 .into_iter()
11718 .map(|selection| selection.range())
11719 .collect();
11720
11721 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11722 self.restore_hunks_in_ranges(selections, window, cx);
11723
11724 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11725 let wrap_around = !all_diff_hunks_expanded;
11726 let snapshot = self.snapshot(window, cx);
11727 let position = self
11728 .selections
11729 .newest::<Point>(&snapshot.display_snapshot)
11730 .head();
11731
11732 self.go_to_hunk_before_or_after_position(
11733 &snapshot,
11734 position,
11735 Direction::Next,
11736 wrap_around,
11737 window,
11738 cx,
11739 );
11740 }
11741
11742 pub fn restore_hunks_in_ranges(
11743 &mut self,
11744 ranges: Vec<Range<Point>>,
11745 window: &mut Window,
11746 cx: &mut Context<Editor>,
11747 ) {
11748 if self.delegate_stage_and_restore {
11749 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11750 if !hunks.is_empty() {
11751 cx.emit(EditorEvent::RestoreRequested { hunks });
11752 }
11753 return;
11754 }
11755 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11756 self.transact(window, cx, |editor, window, cx| {
11757 editor.restore_diff_hunks(hunks, cx);
11758 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11759 selections.refresh()
11760 });
11761 });
11762 }
11763
11764 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11765 let mut revert_changes = HashMap::default();
11766 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11767 for (buffer_id, hunks) in &chunk_by {
11768 let hunks = hunks.collect::<Vec<_>>();
11769 for hunk in &hunks {
11770 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11771 }
11772 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11773 }
11774 if !revert_changes.is_empty() {
11775 self.buffer().update(cx, |multi_buffer, cx| {
11776 for (buffer_id, changes) in revert_changes {
11777 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11778 buffer.update(cx, |buffer, cx| {
11779 buffer.edit(
11780 changes
11781 .into_iter()
11782 .map(|(range, text)| (range, text.to_string())),
11783 None,
11784 cx,
11785 );
11786 });
11787 }
11788 }
11789 });
11790 }
11791 }
11792
11793 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11794 if let Some(status) = self
11795 .addons
11796 .iter()
11797 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11798 {
11799 return Some(status);
11800 }
11801 self.project
11802 .as_ref()?
11803 .read(cx)
11804 .status_for_buffer_id(buffer_id, cx)
11805 }
11806
11807 pub fn open_active_item_in_terminal(
11808 &mut self,
11809 _: &OpenInTerminal,
11810 window: &mut Window,
11811 cx: &mut Context<Self>,
11812 ) {
11813 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11814 let project_path = buffer.read(cx).project_path(cx)?;
11815 let project = self.project()?.read(cx);
11816 let entry = project.entry_for_path(&project_path, cx)?;
11817 let parent = match &entry.canonical_path {
11818 Some(canonical_path) => canonical_path.to_path_buf(),
11819 None => project.absolute_path(&project_path, cx)?,
11820 }
11821 .parent()?
11822 .to_path_buf();
11823 Some(parent)
11824 }) {
11825 window.dispatch_action(
11826 OpenTerminal {
11827 working_directory,
11828 local: false,
11829 }
11830 .boxed_clone(),
11831 cx,
11832 );
11833 }
11834 }
11835
11836 fn set_breakpoint_context_menu(
11837 &mut self,
11838 display_row: DisplayRow,
11839 position: Option<Anchor>,
11840 clicked_point: gpui::Point<Pixels>,
11841 window: &mut Window,
11842 cx: &mut Context<Self>,
11843 ) {
11844 let source = self
11845 .buffer
11846 .read(cx)
11847 .snapshot(cx)
11848 .anchor_before(Point::new(display_row.0, 0u32));
11849
11850 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11851
11852 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11853 self,
11854 source,
11855 clicked_point,
11856 context_menu,
11857 window,
11858 cx,
11859 );
11860 }
11861
11862 fn add_edit_breakpoint_block(
11863 &mut self,
11864 anchor: Anchor,
11865 breakpoint: &Breakpoint,
11866 edit_action: BreakpointPromptEditAction,
11867 window: &mut Window,
11868 cx: &mut Context<Self>,
11869 ) {
11870 let weak_editor = cx.weak_entity();
11871 let bp_prompt = cx.new(|cx| {
11872 BreakpointPromptEditor::new(
11873 weak_editor,
11874 anchor,
11875 breakpoint.clone(),
11876 edit_action,
11877 window,
11878 cx,
11879 )
11880 });
11881
11882 let height = bp_prompt.update(cx, |this, cx| {
11883 this.prompt
11884 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11885 });
11886 let cloned_prompt = bp_prompt.clone();
11887 let blocks = vec![BlockProperties {
11888 style: BlockStyle::Sticky,
11889 placement: BlockPlacement::Above(anchor),
11890 height: Some(height),
11891 render: Arc::new(move |cx| {
11892 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11893 cloned_prompt.clone().into_any_element()
11894 }),
11895 priority: 0,
11896 }];
11897
11898 let focus_handle = bp_prompt.focus_handle(cx);
11899 window.focus(&focus_handle, cx);
11900
11901 let block_ids = self.insert_blocks(blocks, None, cx);
11902 bp_prompt.update(cx, |prompt, _| {
11903 prompt.add_block_ids(block_ids);
11904 });
11905 }
11906
11907 pub(crate) fn breakpoint_at_row(
11908 &self,
11909 row: u32,
11910 window: &mut Window,
11911 cx: &mut Context<Self>,
11912 ) -> Option<(Anchor, Breakpoint)> {
11913 let snapshot = self.snapshot(window, cx);
11914 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11915
11916 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11917 }
11918
11919 pub(crate) fn breakpoint_at_anchor(
11920 &self,
11921 breakpoint_position: Anchor,
11922 snapshot: &EditorSnapshot,
11923 cx: &mut Context<Self>,
11924 ) -> Option<(Anchor, Breakpoint)> {
11925 let buffer = self
11926 .buffer
11927 .read(cx)
11928 .buffer_for_anchor(breakpoint_position, cx)?;
11929
11930 let enclosing_excerpt = breakpoint_position.excerpt_id;
11931 let buffer_snapshot = buffer.read(cx).snapshot();
11932
11933 let row = buffer_snapshot
11934 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11935 .row;
11936
11937 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11938 let anchor_end = snapshot
11939 .buffer_snapshot()
11940 .anchor_after(Point::new(row, line_len));
11941
11942 self.breakpoint_store
11943 .as_ref()?
11944 .read_with(cx, |breakpoint_store, cx| {
11945 breakpoint_store
11946 .breakpoints(
11947 &buffer,
11948 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11949 &buffer_snapshot,
11950 cx,
11951 )
11952 .next()
11953 .and_then(|(bp, _)| {
11954 let breakpoint_row = buffer_snapshot
11955 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11956 .row;
11957
11958 if breakpoint_row == row {
11959 snapshot
11960 .buffer_snapshot()
11961 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11962 .map(|position| (position, bp.bp.clone()))
11963 } else {
11964 None
11965 }
11966 })
11967 })
11968 }
11969
11970 pub fn edit_log_breakpoint(
11971 &mut self,
11972 _: &EditLogBreakpoint,
11973 window: &mut Window,
11974 cx: &mut Context<Self>,
11975 ) {
11976 if self.breakpoint_store.is_none() {
11977 return;
11978 }
11979
11980 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11981 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11982 message: None,
11983 state: BreakpointState::Enabled,
11984 condition: None,
11985 hit_condition: None,
11986 });
11987
11988 self.add_edit_breakpoint_block(
11989 anchor,
11990 &breakpoint,
11991 BreakpointPromptEditAction::Log,
11992 window,
11993 cx,
11994 );
11995 }
11996 }
11997
11998 fn breakpoints_at_cursors(
11999 &self,
12000 window: &mut Window,
12001 cx: &mut Context<Self>,
12002 ) -> Vec<(Anchor, Option<Breakpoint>)> {
12003 let snapshot = self.snapshot(window, cx);
12004 let cursors = self
12005 .selections
12006 .disjoint_anchors_arc()
12007 .iter()
12008 .map(|selection| {
12009 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
12010
12011 let breakpoint_position = self
12012 .breakpoint_at_row(cursor_position.row, window, cx)
12013 .map(|bp| bp.0)
12014 .unwrap_or_else(|| {
12015 snapshot
12016 .display_snapshot
12017 .buffer_snapshot()
12018 .anchor_after(Point::new(cursor_position.row, 0))
12019 });
12020
12021 let breakpoint = self
12022 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
12023 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
12024
12025 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
12026 })
12027 // 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.
12028 .collect::<HashMap<Anchor, _>>();
12029
12030 cursors.into_iter().collect()
12031 }
12032
12033 pub fn enable_breakpoint(
12034 &mut self,
12035 _: &crate::actions::EnableBreakpoint,
12036 window: &mut Window,
12037 cx: &mut Context<Self>,
12038 ) {
12039 if self.breakpoint_store.is_none() {
12040 return;
12041 }
12042
12043 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12044 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
12045 continue;
12046 };
12047 self.edit_breakpoint_at_anchor(
12048 anchor,
12049 breakpoint,
12050 BreakpointEditAction::InvertState,
12051 cx,
12052 );
12053 }
12054 }
12055
12056 pub fn align_selections(
12057 &mut self,
12058 _: &crate::actions::AlignSelections,
12059 window: &mut Window,
12060 cx: &mut Context<Self>,
12061 ) {
12062 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12063
12064 let display_snapshot = self.display_snapshot(cx);
12065
12066 struct CursorData {
12067 anchor: Anchor,
12068 point: Point,
12069 }
12070 let cursor_data: Vec<CursorData> = self
12071 .selections
12072 .disjoint_anchors()
12073 .iter()
12074 .map(|selection| {
12075 let anchor = if selection.reversed {
12076 selection.head()
12077 } else {
12078 selection.tail()
12079 };
12080 CursorData {
12081 anchor: anchor,
12082 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
12083 }
12084 })
12085 .collect();
12086
12087 let rows_anchors_count: Vec<usize> = cursor_data
12088 .iter()
12089 .map(|cursor| cursor.point.row)
12090 .chunk_by(|&row| row)
12091 .into_iter()
12092 .map(|(_, group)| group.count())
12093 .collect();
12094 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
12095 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
12096 let mut edits = Vec::new();
12097
12098 for column_idx in 0..max_columns {
12099 let mut cursor_index = 0;
12100
12101 // Calculate target_column => position that the selections will go
12102 let mut target_column = 0;
12103 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12104 // Skip rows that don't have this column
12105 if column_idx >= *cursor_count {
12106 cursor_index += cursor_count;
12107 continue;
12108 }
12109
12110 let point = &cursor_data[cursor_index + column_idx].point;
12111 let adjusted_column = point.column + rows_column_offset[row_idx];
12112 if adjusted_column > target_column {
12113 target_column = adjusted_column;
12114 }
12115 cursor_index += cursor_count;
12116 }
12117
12118 // Collect edits for this column
12119 cursor_index = 0;
12120 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12121 // Skip rows that don't have this column
12122 if column_idx >= *cursor_count {
12123 cursor_index += *cursor_count;
12124 continue;
12125 }
12126
12127 let point = &cursor_data[cursor_index + column_idx].point;
12128 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
12129 if spaces_needed > 0 {
12130 let anchor = cursor_data[cursor_index + column_idx]
12131 .anchor
12132 .bias_left(&display_snapshot);
12133 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
12134 }
12135 rows_column_offset[row_idx] += spaces_needed;
12136
12137 cursor_index += *cursor_count;
12138 }
12139 }
12140
12141 if !edits.is_empty() {
12142 self.transact(window, cx, |editor, _window, cx| {
12143 editor.edit(edits, cx);
12144 });
12145 }
12146 }
12147
12148 pub fn disable_breakpoint(
12149 &mut self,
12150 _: &crate::actions::DisableBreakpoint,
12151 window: &mut Window,
12152 cx: &mut Context<Self>,
12153 ) {
12154 if self.breakpoint_store.is_none() {
12155 return;
12156 }
12157
12158 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12159 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12160 continue;
12161 };
12162 self.edit_breakpoint_at_anchor(
12163 anchor,
12164 breakpoint,
12165 BreakpointEditAction::InvertState,
12166 cx,
12167 );
12168 }
12169 }
12170
12171 pub fn toggle_breakpoint(
12172 &mut self,
12173 _: &crate::actions::ToggleBreakpoint,
12174 window: &mut Window,
12175 cx: &mut Context<Self>,
12176 ) {
12177 if self.breakpoint_store.is_none() {
12178 return;
12179 }
12180
12181 let snapshot = self.snapshot(window, cx);
12182 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12183 if self.gutter_breakpoint_indicator.0.is_some() {
12184 let display_row = anchor
12185 .to_point(snapshot.buffer_snapshot())
12186 .to_display_point(&snapshot.display_snapshot)
12187 .row();
12188 self.update_breakpoint_collision_on_toggle(
12189 display_row,
12190 &BreakpointEditAction::Toggle,
12191 );
12192 }
12193
12194 if let Some(breakpoint) = breakpoint {
12195 self.edit_breakpoint_at_anchor(
12196 anchor,
12197 breakpoint,
12198 BreakpointEditAction::Toggle,
12199 cx,
12200 );
12201 } else {
12202 self.edit_breakpoint_at_anchor(
12203 anchor,
12204 Breakpoint::new_standard(),
12205 BreakpointEditAction::Toggle,
12206 cx,
12207 );
12208 }
12209 }
12210 }
12211
12212 fn update_breakpoint_collision_on_toggle(
12213 &mut self,
12214 display_row: DisplayRow,
12215 edit_action: &BreakpointEditAction,
12216 ) {
12217 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12218 if breakpoint_indicator.display_row == display_row
12219 && matches!(edit_action, BreakpointEditAction::Toggle)
12220 {
12221 breakpoint_indicator.collides_with_existing_breakpoint =
12222 !breakpoint_indicator.collides_with_existing_breakpoint;
12223 }
12224 }
12225 }
12226
12227 pub fn edit_breakpoint_at_anchor(
12228 &mut self,
12229 breakpoint_position: Anchor,
12230 breakpoint: Breakpoint,
12231 edit_action: BreakpointEditAction,
12232 cx: &mut Context<Self>,
12233 ) {
12234 let Some(breakpoint_store) = &self.breakpoint_store else {
12235 return;
12236 };
12237
12238 let Some(buffer) = self
12239 .buffer
12240 .read(cx)
12241 .buffer_for_anchor(breakpoint_position, cx)
12242 else {
12243 return;
12244 };
12245
12246 breakpoint_store.update(cx, |breakpoint_store, cx| {
12247 breakpoint_store.toggle_breakpoint(
12248 buffer,
12249 BreakpointWithPosition {
12250 position: breakpoint_position.text_anchor,
12251 bp: breakpoint,
12252 },
12253 edit_action,
12254 cx,
12255 );
12256 });
12257
12258 cx.notify();
12259 }
12260
12261 #[cfg(any(test, feature = "test-support"))]
12262 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12263 self.breakpoint_store.clone()
12264 }
12265
12266 pub fn prepare_restore_change(
12267 &self,
12268 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12269 hunk: &MultiBufferDiffHunk,
12270 cx: &mut App,
12271 ) -> Option<()> {
12272 if hunk.is_created_file() {
12273 return None;
12274 }
12275 let buffer = self.buffer.read(cx);
12276 let diff = buffer.diff_for(hunk.buffer_id)?;
12277 let buffer = buffer.buffer(hunk.buffer_id)?;
12278 let buffer = buffer.read(cx);
12279 let original_text = diff
12280 .read(cx)
12281 .base_text(cx)
12282 .as_rope()
12283 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12284 let buffer_snapshot = buffer.snapshot();
12285 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12286 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12287 probe
12288 .0
12289 .start
12290 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12291 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12292 }) {
12293 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12294 Some(())
12295 } else {
12296 None
12297 }
12298 }
12299
12300 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12301 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12302 }
12303
12304 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12305 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12306 }
12307
12308 pub fn rotate_selections_forward(
12309 &mut self,
12310 _: &RotateSelectionsForward,
12311 window: &mut Window,
12312 cx: &mut Context<Self>,
12313 ) {
12314 self.rotate_selections(window, cx, false)
12315 }
12316
12317 pub fn rotate_selections_backward(
12318 &mut self,
12319 _: &RotateSelectionsBackward,
12320 window: &mut Window,
12321 cx: &mut Context<Self>,
12322 ) {
12323 self.rotate_selections(window, cx, true)
12324 }
12325
12326 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12327 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12328 let display_snapshot = self.display_snapshot(cx);
12329 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12330
12331 if selections.len() < 2 {
12332 return;
12333 }
12334
12335 let (edits, new_selections) = {
12336 let buffer = self.buffer.read(cx).read(cx);
12337 let has_selections = selections.iter().any(|s| !s.is_empty());
12338 if has_selections {
12339 let mut selected_texts: Vec<String> = selections
12340 .iter()
12341 .map(|selection| {
12342 buffer
12343 .text_for_range(selection.start..selection.end)
12344 .collect()
12345 })
12346 .collect();
12347
12348 if reverse {
12349 selected_texts.rotate_left(1);
12350 } else {
12351 selected_texts.rotate_right(1);
12352 }
12353
12354 let mut offset_delta: i64 = 0;
12355 let mut new_selections = Vec::new();
12356 let edits: Vec<_> = selections
12357 .iter()
12358 .zip(selected_texts.iter())
12359 .map(|(selection, new_text)| {
12360 let old_len = (selection.end.0 - selection.start.0) as i64;
12361 let new_len = new_text.len() as i64;
12362 let adjusted_start =
12363 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12364 let adjusted_end =
12365 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12366
12367 new_selections.push(Selection {
12368 id: selection.id,
12369 start: adjusted_start,
12370 end: adjusted_end,
12371 reversed: selection.reversed,
12372 goal: selection.goal,
12373 });
12374
12375 offset_delta += new_len - old_len;
12376 (selection.start..selection.end, new_text.clone())
12377 })
12378 .collect();
12379 (edits, new_selections)
12380 } else {
12381 let mut all_rows: Vec<u32> = selections
12382 .iter()
12383 .map(|selection| buffer.offset_to_point(selection.start).row)
12384 .collect();
12385 all_rows.sort_unstable();
12386 all_rows.dedup();
12387
12388 if all_rows.len() < 2 {
12389 return;
12390 }
12391
12392 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12393 .iter()
12394 .map(|&row| {
12395 let start = Point::new(row, 0);
12396 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12397 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12398 })
12399 .collect();
12400
12401 let mut line_texts: Vec<String> = line_ranges
12402 .iter()
12403 .map(|range| buffer.text_for_range(range.clone()).collect())
12404 .collect();
12405
12406 if reverse {
12407 line_texts.rotate_left(1);
12408 } else {
12409 line_texts.rotate_right(1);
12410 }
12411
12412 let edits = line_ranges
12413 .iter()
12414 .zip(line_texts.iter())
12415 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12416 .collect();
12417
12418 let num_rows = all_rows.len();
12419 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12420 .iter()
12421 .enumerate()
12422 .map(|(i, &row)| (row, i))
12423 .collect();
12424
12425 // Compute new line start offsets after rotation (handles CRLF)
12426 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12427 let first_line_start = line_ranges[0].start.0;
12428 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12429 for text in line_texts.iter().take(num_rows - 1) {
12430 let prev_start = *new_line_starts.last().unwrap();
12431 new_line_starts.push(prev_start + text.len() + newline_len);
12432 }
12433
12434 let new_selections = selections
12435 .iter()
12436 .map(|selection| {
12437 let point = buffer.offset_to_point(selection.start);
12438 let old_index = row_to_index[&point.row];
12439 let new_index = if reverse {
12440 (old_index + num_rows - 1) % num_rows
12441 } else {
12442 (old_index + 1) % num_rows
12443 };
12444 let new_offset =
12445 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12446 Selection {
12447 id: selection.id,
12448 start: new_offset,
12449 end: new_offset,
12450 reversed: selection.reversed,
12451 goal: selection.goal,
12452 }
12453 })
12454 .collect();
12455
12456 (edits, new_selections)
12457 }
12458 };
12459
12460 self.transact(window, cx, |this, window, cx| {
12461 this.buffer.update(cx, |buffer, cx| {
12462 buffer.edit(edits, None, cx);
12463 });
12464 this.change_selections(Default::default(), window, cx, |s| {
12465 s.select(new_selections);
12466 });
12467 });
12468 }
12469
12470 fn manipulate_lines<M>(
12471 &mut self,
12472 window: &mut Window,
12473 cx: &mut Context<Self>,
12474 mut manipulate: M,
12475 ) where
12476 M: FnMut(&str) -> LineManipulationResult,
12477 {
12478 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12479
12480 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12481 let buffer = self.buffer.read(cx).snapshot(cx);
12482
12483 let mut edits = Vec::new();
12484
12485 let selections = self.selections.all::<Point>(&display_map);
12486 let mut selections = selections.iter().peekable();
12487 let mut contiguous_row_selections = Vec::new();
12488 let mut new_selections = Vec::new();
12489 let mut added_lines = 0;
12490 let mut removed_lines = 0;
12491
12492 while let Some(selection) = selections.next() {
12493 let (start_row, end_row) = consume_contiguous_rows(
12494 &mut contiguous_row_selections,
12495 selection,
12496 &display_map,
12497 &mut selections,
12498 );
12499
12500 let start_point = Point::new(start_row.0, 0);
12501 let end_point = Point::new(
12502 end_row.previous_row().0,
12503 buffer.line_len(end_row.previous_row()),
12504 );
12505 let text = buffer
12506 .text_for_range(start_point..end_point)
12507 .collect::<String>();
12508
12509 let LineManipulationResult {
12510 new_text,
12511 line_count_before,
12512 line_count_after,
12513 } = manipulate(&text);
12514
12515 edits.push((start_point..end_point, new_text));
12516
12517 // Selections must change based on added and removed line count
12518 let start_row =
12519 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12520 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12521 new_selections.push(Selection {
12522 id: selection.id,
12523 start: start_row,
12524 end: end_row,
12525 goal: SelectionGoal::None,
12526 reversed: selection.reversed,
12527 });
12528
12529 if line_count_after > line_count_before {
12530 added_lines += line_count_after - line_count_before;
12531 } else if line_count_before > line_count_after {
12532 removed_lines += line_count_before - line_count_after;
12533 }
12534 }
12535
12536 self.transact(window, cx, |this, window, cx| {
12537 let buffer = this.buffer.update(cx, |buffer, cx| {
12538 buffer.edit(edits, None, cx);
12539 buffer.snapshot(cx)
12540 });
12541
12542 // Recalculate offsets on newly edited buffer
12543 let new_selections = new_selections
12544 .iter()
12545 .map(|s| {
12546 let start_point = Point::new(s.start.0, 0);
12547 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12548 Selection {
12549 id: s.id,
12550 start: buffer.point_to_offset(start_point),
12551 end: buffer.point_to_offset(end_point),
12552 goal: s.goal,
12553 reversed: s.reversed,
12554 }
12555 })
12556 .collect();
12557
12558 this.change_selections(Default::default(), window, cx, |s| {
12559 s.select(new_selections);
12560 });
12561
12562 this.request_autoscroll(Autoscroll::fit(), cx);
12563 });
12564 }
12565
12566 fn manipulate_immutable_lines<Fn>(
12567 &mut self,
12568 window: &mut Window,
12569 cx: &mut Context<Self>,
12570 mut callback: Fn,
12571 ) where
12572 Fn: FnMut(&mut Vec<&str>),
12573 {
12574 self.manipulate_lines(window, cx, |text| {
12575 let mut lines: Vec<&str> = text.split('\n').collect();
12576 let line_count_before = lines.len();
12577
12578 callback(&mut lines);
12579
12580 LineManipulationResult {
12581 new_text: lines.join("\n"),
12582 line_count_before,
12583 line_count_after: lines.len(),
12584 }
12585 });
12586 }
12587
12588 fn manipulate_mutable_lines<Fn>(
12589 &mut self,
12590 window: &mut Window,
12591 cx: &mut Context<Self>,
12592 mut callback: Fn,
12593 ) where
12594 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12595 {
12596 self.manipulate_lines(window, cx, |text| {
12597 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12598 let line_count_before = lines.len();
12599
12600 callback(&mut lines);
12601
12602 LineManipulationResult {
12603 new_text: lines.join("\n"),
12604 line_count_before,
12605 line_count_after: lines.len(),
12606 }
12607 });
12608 }
12609
12610 pub fn convert_indentation_to_spaces(
12611 &mut self,
12612 _: &ConvertIndentationToSpaces,
12613 window: &mut Window,
12614 cx: &mut Context<Self>,
12615 ) {
12616 let settings = self.buffer.read(cx).language_settings(cx);
12617 let tab_size = settings.tab_size.get() as usize;
12618
12619 self.manipulate_mutable_lines(window, cx, |lines| {
12620 // Allocates a reasonably sized scratch buffer once for the whole loop
12621 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12622 // Avoids recomputing spaces that could be inserted many times
12623 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12624 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12625 .collect();
12626
12627 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12628 let mut chars = line.as_ref().chars();
12629 let mut col = 0;
12630 let mut changed = false;
12631
12632 for ch in chars.by_ref() {
12633 match ch {
12634 ' ' => {
12635 reindented_line.push(' ');
12636 col += 1;
12637 }
12638 '\t' => {
12639 // \t are converted to spaces depending on the current column
12640 let spaces_len = tab_size - (col % tab_size);
12641 reindented_line.extend(&space_cache[spaces_len - 1]);
12642 col += spaces_len;
12643 changed = true;
12644 }
12645 _ => {
12646 // If we dont append before break, the character is consumed
12647 reindented_line.push(ch);
12648 break;
12649 }
12650 }
12651 }
12652
12653 if !changed {
12654 reindented_line.clear();
12655 continue;
12656 }
12657 // Append the rest of the line and replace old reference with new one
12658 reindented_line.extend(chars);
12659 *line = Cow::Owned(reindented_line.clone());
12660 reindented_line.clear();
12661 }
12662 });
12663 }
12664
12665 pub fn convert_indentation_to_tabs(
12666 &mut self,
12667 _: &ConvertIndentationToTabs,
12668 window: &mut Window,
12669 cx: &mut Context<Self>,
12670 ) {
12671 let settings = self.buffer.read(cx).language_settings(cx);
12672 let tab_size = settings.tab_size.get() as usize;
12673
12674 self.manipulate_mutable_lines(window, cx, |lines| {
12675 // Allocates a reasonably sized buffer once for the whole loop
12676 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12677 // Avoids recomputing spaces that could be inserted many times
12678 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12679 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12680 .collect();
12681
12682 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12683 let mut chars = line.chars();
12684 let mut spaces_count = 0;
12685 let mut first_non_indent_char = None;
12686 let mut changed = false;
12687
12688 for ch in chars.by_ref() {
12689 match ch {
12690 ' ' => {
12691 // Keep track of spaces. Append \t when we reach tab_size
12692 spaces_count += 1;
12693 changed = true;
12694 if spaces_count == tab_size {
12695 reindented_line.push('\t');
12696 spaces_count = 0;
12697 }
12698 }
12699 '\t' => {
12700 reindented_line.push('\t');
12701 spaces_count = 0;
12702 }
12703 _ => {
12704 // Dont append it yet, we might have remaining spaces
12705 first_non_indent_char = Some(ch);
12706 break;
12707 }
12708 }
12709 }
12710
12711 if !changed {
12712 reindented_line.clear();
12713 continue;
12714 }
12715 // Remaining spaces that didn't make a full tab stop
12716 if spaces_count > 0 {
12717 reindented_line.extend(&space_cache[spaces_count - 1]);
12718 }
12719 // If we consume an extra character that was not indentation, add it back
12720 if let Some(extra_char) = first_non_indent_char {
12721 reindented_line.push(extra_char);
12722 }
12723 // Append the rest of the line and replace old reference with new one
12724 reindented_line.extend(chars);
12725 *line = Cow::Owned(reindented_line.clone());
12726 reindented_line.clear();
12727 }
12728 });
12729 }
12730
12731 pub fn convert_to_upper_case(
12732 &mut self,
12733 _: &ConvertToUpperCase,
12734 window: &mut Window,
12735 cx: &mut Context<Self>,
12736 ) {
12737 self.manipulate_text(window, cx, |text| text.to_uppercase())
12738 }
12739
12740 pub fn convert_to_lower_case(
12741 &mut self,
12742 _: &ConvertToLowerCase,
12743 window: &mut Window,
12744 cx: &mut Context<Self>,
12745 ) {
12746 self.manipulate_text(window, cx, |text| text.to_lowercase())
12747 }
12748
12749 pub fn convert_to_title_case(
12750 &mut self,
12751 _: &ConvertToTitleCase,
12752 window: &mut Window,
12753 cx: &mut Context<Self>,
12754 ) {
12755 self.manipulate_text(window, cx, |text| {
12756 Self::convert_text_case(text, Case::Title)
12757 })
12758 }
12759
12760 pub fn convert_to_snake_case(
12761 &mut self,
12762 _: &ConvertToSnakeCase,
12763 window: &mut Window,
12764 cx: &mut Context<Self>,
12765 ) {
12766 self.manipulate_text(window, cx, |text| {
12767 Self::convert_text_case(text, Case::Snake)
12768 })
12769 }
12770
12771 pub fn convert_to_kebab_case(
12772 &mut self,
12773 _: &ConvertToKebabCase,
12774 window: &mut Window,
12775 cx: &mut Context<Self>,
12776 ) {
12777 self.manipulate_text(window, cx, |text| {
12778 Self::convert_text_case(text, Case::Kebab)
12779 })
12780 }
12781
12782 pub fn convert_to_upper_camel_case(
12783 &mut self,
12784 _: &ConvertToUpperCamelCase,
12785 window: &mut Window,
12786 cx: &mut Context<Self>,
12787 ) {
12788 self.manipulate_text(window, cx, |text| {
12789 Self::convert_text_case(text, Case::UpperCamel)
12790 })
12791 }
12792
12793 pub fn convert_to_lower_camel_case(
12794 &mut self,
12795 _: &ConvertToLowerCamelCase,
12796 window: &mut Window,
12797 cx: &mut Context<Self>,
12798 ) {
12799 self.manipulate_text(window, cx, |text| {
12800 Self::convert_text_case(text, Case::Camel)
12801 })
12802 }
12803
12804 pub fn convert_to_opposite_case(
12805 &mut self,
12806 _: &ConvertToOppositeCase,
12807 window: &mut Window,
12808 cx: &mut Context<Self>,
12809 ) {
12810 self.manipulate_text(window, cx, |text| {
12811 text.chars()
12812 .fold(String::with_capacity(text.len()), |mut t, c| {
12813 if c.is_uppercase() {
12814 t.extend(c.to_lowercase());
12815 } else {
12816 t.extend(c.to_uppercase());
12817 }
12818 t
12819 })
12820 })
12821 }
12822
12823 pub fn convert_to_sentence_case(
12824 &mut self,
12825 _: &ConvertToSentenceCase,
12826 window: &mut Window,
12827 cx: &mut Context<Self>,
12828 ) {
12829 self.manipulate_text(window, cx, |text| {
12830 Self::convert_text_case(text, Case::Sentence)
12831 })
12832 }
12833
12834 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12835 self.manipulate_text(window, cx, |text| {
12836 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12837 if has_upper_case_characters {
12838 text.to_lowercase()
12839 } else {
12840 text.to_uppercase()
12841 }
12842 })
12843 }
12844
12845 pub fn convert_to_rot13(
12846 &mut self,
12847 _: &ConvertToRot13,
12848 window: &mut Window,
12849 cx: &mut Context<Self>,
12850 ) {
12851 self.manipulate_text(window, cx, |text| {
12852 text.chars()
12853 .map(|c| match c {
12854 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12855 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12856 _ => c,
12857 })
12858 .collect()
12859 })
12860 }
12861
12862 fn convert_text_case(text: &str, case: Case) -> String {
12863 text.lines()
12864 .map(|line| {
12865 let trimmed_start = line.trim_start();
12866 let leading = &line[..line.len() - trimmed_start.len()];
12867 let trimmed = trimmed_start.trim_end();
12868 let trailing = &trimmed_start[trimmed.len()..];
12869 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12870 })
12871 .join("\n")
12872 }
12873
12874 pub fn convert_to_rot47(
12875 &mut self,
12876 _: &ConvertToRot47,
12877 window: &mut Window,
12878 cx: &mut Context<Self>,
12879 ) {
12880 self.manipulate_text(window, cx, |text| {
12881 text.chars()
12882 .map(|c| {
12883 let code_point = c as u32;
12884 if code_point >= 33 && code_point <= 126 {
12885 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12886 }
12887 c
12888 })
12889 .collect()
12890 })
12891 }
12892
12893 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12894 where
12895 Fn: FnMut(&str) -> String,
12896 {
12897 let buffer = self.buffer.read(cx).snapshot(cx);
12898
12899 let mut new_selections = Vec::new();
12900 let mut edits = Vec::new();
12901 let mut selection_adjustment = 0isize;
12902
12903 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12904 let selection_is_empty = selection.is_empty();
12905
12906 let (start, end) = if selection_is_empty {
12907 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12908 (word_range.start, word_range.end)
12909 } else {
12910 (
12911 buffer.point_to_offset(selection.start),
12912 buffer.point_to_offset(selection.end),
12913 )
12914 };
12915
12916 let text = buffer.text_for_range(start..end).collect::<String>();
12917 let old_length = text.len() as isize;
12918 let text = callback(&text);
12919
12920 new_selections.push(Selection {
12921 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12922 end: MultiBufferOffset(
12923 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12924 ),
12925 goal: SelectionGoal::None,
12926 id: selection.id,
12927 reversed: selection.reversed,
12928 });
12929
12930 selection_adjustment += old_length - text.len() as isize;
12931
12932 edits.push((start..end, text));
12933 }
12934
12935 self.transact(window, cx, |this, window, cx| {
12936 this.buffer.update(cx, |buffer, cx| {
12937 buffer.edit(edits, None, cx);
12938 });
12939
12940 this.change_selections(Default::default(), window, cx, |s| {
12941 s.select(new_selections);
12942 });
12943
12944 this.request_autoscroll(Autoscroll::fit(), cx);
12945 });
12946 }
12947
12948 pub fn move_selection_on_drop(
12949 &mut self,
12950 selection: &Selection<Anchor>,
12951 target: DisplayPoint,
12952 is_cut: bool,
12953 window: &mut Window,
12954 cx: &mut Context<Self>,
12955 ) {
12956 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12957 let buffer = display_map.buffer_snapshot();
12958 let mut edits = Vec::new();
12959 let insert_point = display_map
12960 .clip_point(target, Bias::Left)
12961 .to_point(&display_map);
12962 let text = buffer
12963 .text_for_range(selection.start..selection.end)
12964 .collect::<String>();
12965 if is_cut {
12966 edits.push(((selection.start..selection.end), String::new()));
12967 }
12968 let insert_anchor = buffer.anchor_before(insert_point);
12969 edits.push(((insert_anchor..insert_anchor), text));
12970 let last_edit_start = insert_anchor.bias_left(buffer);
12971 let last_edit_end = insert_anchor.bias_right(buffer);
12972 self.transact(window, cx, |this, window, cx| {
12973 this.buffer.update(cx, |buffer, cx| {
12974 buffer.edit(edits, None, cx);
12975 });
12976 this.change_selections(Default::default(), window, cx, |s| {
12977 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12978 });
12979 });
12980 }
12981
12982 pub fn clear_selection_drag_state(&mut self) {
12983 self.selection_drag_state = SelectionDragState::None;
12984 }
12985
12986 pub fn duplicate(
12987 &mut self,
12988 upwards: bool,
12989 whole_lines: bool,
12990 window: &mut Window,
12991 cx: &mut Context<Self>,
12992 ) {
12993 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12994
12995 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12996 let buffer = display_map.buffer_snapshot();
12997 let selections = self.selections.all::<Point>(&display_map);
12998
12999 let mut edits = Vec::new();
13000 let mut selections_iter = selections.iter().peekable();
13001 while let Some(selection) = selections_iter.next() {
13002 let mut rows = selection.spanned_rows(false, &display_map);
13003 // duplicate line-wise
13004 if whole_lines || selection.start == selection.end {
13005 // Avoid duplicating the same lines twice.
13006 while let Some(next_selection) = selections_iter.peek() {
13007 let next_rows = next_selection.spanned_rows(false, &display_map);
13008 if next_rows.start < rows.end {
13009 rows.end = next_rows.end;
13010 selections_iter.next().unwrap();
13011 } else {
13012 break;
13013 }
13014 }
13015
13016 // Copy the text from the selected row region and splice it either at the start
13017 // or end of the region.
13018 let start = Point::new(rows.start.0, 0);
13019 let end = Point::new(
13020 rows.end.previous_row().0,
13021 buffer.line_len(rows.end.previous_row()),
13022 );
13023
13024 let mut text = buffer.text_for_range(start..end).collect::<String>();
13025
13026 let insert_location = if upwards {
13027 // When duplicating upward, we need to insert before the current line.
13028 // If we're on the last line and it doesn't end with a newline,
13029 // we need to add a newline before the duplicated content.
13030 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
13031 && buffer.max_point().column > 0
13032 && !text.ends_with('\n');
13033
13034 if needs_leading_newline {
13035 text.insert(0, '\n');
13036 end
13037 } else {
13038 text.push('\n');
13039 Point::new(rows.start.0, 0)
13040 }
13041 } else {
13042 text.push('\n');
13043 start
13044 };
13045 edits.push((insert_location..insert_location, text));
13046 } else {
13047 // duplicate character-wise
13048 let start = selection.start;
13049 let end = selection.end;
13050 let text = buffer.text_for_range(start..end).collect::<String>();
13051 edits.push((selection.end..selection.end, text));
13052 }
13053 }
13054
13055 self.transact(window, cx, |this, window, cx| {
13056 this.buffer.update(cx, |buffer, cx| {
13057 buffer.edit(edits, None, cx);
13058 });
13059
13060 // When duplicating upward with whole lines, move the cursor to the duplicated line
13061 if upwards && whole_lines {
13062 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
13063
13064 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13065 let mut new_ranges = Vec::new();
13066 let selections = s.all::<Point>(&display_map);
13067 let mut selections_iter = selections.iter().peekable();
13068
13069 while let Some(first_selection) = selections_iter.next() {
13070 // Group contiguous selections together to find the total row span
13071 let mut group_selections = vec![first_selection];
13072 let mut rows = first_selection.spanned_rows(false, &display_map);
13073
13074 while let Some(next_selection) = selections_iter.peek() {
13075 let next_rows = next_selection.spanned_rows(false, &display_map);
13076 if next_rows.start < rows.end {
13077 rows.end = next_rows.end;
13078 group_selections.push(selections_iter.next().unwrap());
13079 } else {
13080 break;
13081 }
13082 }
13083
13084 let row_count = rows.end.0 - rows.start.0;
13085
13086 // Move all selections in this group up by the total number of duplicated rows
13087 for selection in group_selections {
13088 let new_start = Point::new(
13089 selection.start.row.saturating_sub(row_count),
13090 selection.start.column,
13091 );
13092
13093 let new_end = Point::new(
13094 selection.end.row.saturating_sub(row_count),
13095 selection.end.column,
13096 );
13097
13098 new_ranges.push(new_start..new_end);
13099 }
13100 }
13101
13102 s.select_ranges(new_ranges);
13103 });
13104 }
13105
13106 this.request_autoscroll(Autoscroll::fit(), cx);
13107 });
13108 }
13109
13110 pub fn duplicate_line_up(
13111 &mut self,
13112 _: &DuplicateLineUp,
13113 window: &mut Window,
13114 cx: &mut Context<Self>,
13115 ) {
13116 self.duplicate(true, true, window, cx);
13117 }
13118
13119 pub fn duplicate_line_down(
13120 &mut self,
13121 _: &DuplicateLineDown,
13122 window: &mut Window,
13123 cx: &mut Context<Self>,
13124 ) {
13125 self.duplicate(false, true, window, cx);
13126 }
13127
13128 pub fn duplicate_selection(
13129 &mut self,
13130 _: &DuplicateSelection,
13131 window: &mut Window,
13132 cx: &mut Context<Self>,
13133 ) {
13134 self.duplicate(false, false, window, cx);
13135 }
13136
13137 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13138 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13139 if self.mode.is_single_line() {
13140 cx.propagate();
13141 return;
13142 }
13143
13144 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13145 let buffer = self.buffer.read(cx).snapshot(cx);
13146
13147 let mut edits = Vec::new();
13148 let mut unfold_ranges = Vec::new();
13149 let mut refold_creases = Vec::new();
13150
13151 let selections = self.selections.all::<Point>(&display_map);
13152 let mut selections = selections.iter().peekable();
13153 let mut contiguous_row_selections = Vec::new();
13154 let mut new_selections = Vec::new();
13155
13156 while let Some(selection) = selections.next() {
13157 // Find all the selections that span a contiguous row range
13158 let (start_row, end_row) = consume_contiguous_rows(
13159 &mut contiguous_row_selections,
13160 selection,
13161 &display_map,
13162 &mut selections,
13163 );
13164
13165 // Move the text spanned by the row range to be before the line preceding the row range
13166 if start_row.0 > 0 {
13167 let range_to_move = Point::new(
13168 start_row.previous_row().0,
13169 buffer.line_len(start_row.previous_row()),
13170 )
13171 ..Point::new(
13172 end_row.previous_row().0,
13173 buffer.line_len(end_row.previous_row()),
13174 );
13175 let insertion_point = display_map
13176 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13177 .0;
13178
13179 // Don't move lines across excerpts
13180 if buffer
13181 .excerpt_containing(insertion_point..range_to_move.end)
13182 .is_some()
13183 {
13184 let text = buffer
13185 .text_for_range(range_to_move.clone())
13186 .flat_map(|s| s.chars())
13187 .skip(1)
13188 .chain(['\n'])
13189 .collect::<String>();
13190
13191 edits.push((
13192 buffer.anchor_after(range_to_move.start)
13193 ..buffer.anchor_before(range_to_move.end),
13194 String::new(),
13195 ));
13196 let insertion_anchor = buffer.anchor_after(insertion_point);
13197 edits.push((insertion_anchor..insertion_anchor, text));
13198
13199 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13200
13201 // Move selections up
13202 new_selections.extend(contiguous_row_selections.drain(..).map(
13203 |mut selection| {
13204 selection.start.row -= row_delta;
13205 selection.end.row -= row_delta;
13206 selection
13207 },
13208 ));
13209
13210 // Move folds up
13211 unfold_ranges.push(range_to_move.clone());
13212 for fold in display_map.folds_in_range(
13213 buffer.anchor_before(range_to_move.start)
13214 ..buffer.anchor_after(range_to_move.end),
13215 ) {
13216 let mut start = fold.range.start.to_point(&buffer);
13217 let mut end = fold.range.end.to_point(&buffer);
13218 start.row -= row_delta;
13219 end.row -= row_delta;
13220 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13221 }
13222 }
13223 }
13224
13225 // If we didn't move line(s), preserve the existing selections
13226 new_selections.append(&mut contiguous_row_selections);
13227 }
13228
13229 self.transact(window, cx, |this, window, cx| {
13230 this.unfold_ranges(&unfold_ranges, true, true, cx);
13231 this.buffer.update(cx, |buffer, cx| {
13232 for (range, text) in edits {
13233 buffer.edit([(range, text)], None, cx);
13234 }
13235 });
13236 this.fold_creases(refold_creases, true, window, cx);
13237 this.change_selections(Default::default(), window, cx, |s| {
13238 s.select(new_selections);
13239 })
13240 });
13241 }
13242
13243 pub fn move_line_down(
13244 &mut self,
13245 _: &MoveLineDown,
13246 window: &mut Window,
13247 cx: &mut Context<Self>,
13248 ) {
13249 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13250 if self.mode.is_single_line() {
13251 cx.propagate();
13252 return;
13253 }
13254
13255 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13256 let buffer = self.buffer.read(cx).snapshot(cx);
13257
13258 let mut edits = Vec::new();
13259 let mut unfold_ranges = Vec::new();
13260 let mut refold_creases = Vec::new();
13261
13262 let selections = self.selections.all::<Point>(&display_map);
13263 let mut selections = selections.iter().peekable();
13264 let mut contiguous_row_selections = Vec::new();
13265 let mut new_selections = Vec::new();
13266
13267 while let Some(selection) = selections.next() {
13268 // Find all the selections that span a contiguous row range
13269 let (start_row, end_row) = consume_contiguous_rows(
13270 &mut contiguous_row_selections,
13271 selection,
13272 &display_map,
13273 &mut selections,
13274 );
13275
13276 // Move the text spanned by the row range to be after the last line of the row range
13277 if end_row.0 <= buffer.max_point().row {
13278 let range_to_move =
13279 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13280 let insertion_point = display_map
13281 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13282 .0;
13283
13284 // Don't move lines across excerpt boundaries
13285 if buffer
13286 .excerpt_containing(range_to_move.start..insertion_point)
13287 .is_some()
13288 {
13289 let mut text = String::from("\n");
13290 text.extend(buffer.text_for_range(range_to_move.clone()));
13291 text.pop(); // Drop trailing newline
13292 edits.push((
13293 buffer.anchor_after(range_to_move.start)
13294 ..buffer.anchor_before(range_to_move.end),
13295 String::new(),
13296 ));
13297 let insertion_anchor = buffer.anchor_after(insertion_point);
13298 edits.push((insertion_anchor..insertion_anchor, text));
13299
13300 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13301
13302 // Move selections down
13303 new_selections.extend(contiguous_row_selections.drain(..).map(
13304 |mut selection| {
13305 selection.start.row += row_delta;
13306 selection.end.row += row_delta;
13307 selection
13308 },
13309 ));
13310
13311 // Move folds down
13312 unfold_ranges.push(range_to_move.clone());
13313 for fold in display_map.folds_in_range(
13314 buffer.anchor_before(range_to_move.start)
13315 ..buffer.anchor_after(range_to_move.end),
13316 ) {
13317 let mut start = fold.range.start.to_point(&buffer);
13318 let mut end = fold.range.end.to_point(&buffer);
13319 start.row += row_delta;
13320 end.row += row_delta;
13321 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13322 }
13323 }
13324 }
13325
13326 // If we didn't move line(s), preserve the existing selections
13327 new_selections.append(&mut contiguous_row_selections);
13328 }
13329
13330 self.transact(window, cx, |this, window, cx| {
13331 this.unfold_ranges(&unfold_ranges, true, true, cx);
13332 this.buffer.update(cx, |buffer, cx| {
13333 for (range, text) in edits {
13334 buffer.edit([(range, text)], None, cx);
13335 }
13336 });
13337 this.fold_creases(refold_creases, true, window, cx);
13338 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13339 });
13340 }
13341
13342 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13343 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13344 let text_layout_details = &self.text_layout_details(window, cx);
13345 self.transact(window, cx, |this, window, cx| {
13346 let edits = this.change_selections(Default::default(), window, cx, |s| {
13347 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13348 s.move_with(&mut |display_map, selection| {
13349 if !selection.is_empty() {
13350 return;
13351 }
13352
13353 let mut head = selection.head();
13354 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13355 if head.column() == display_map.line_len(head.row()) {
13356 transpose_offset = display_map
13357 .buffer_snapshot()
13358 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13359 }
13360
13361 if transpose_offset == MultiBufferOffset(0) {
13362 return;
13363 }
13364
13365 *head.column_mut() += 1;
13366 head = display_map.clip_point(head, Bias::Right);
13367 let goal = SelectionGoal::HorizontalPosition(
13368 display_map
13369 .x_for_display_point(head, text_layout_details)
13370 .into(),
13371 );
13372 selection.collapse_to(head, goal);
13373
13374 let transpose_start = display_map
13375 .buffer_snapshot()
13376 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13377 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13378 let transpose_end = display_map
13379 .buffer_snapshot()
13380 .clip_offset(transpose_offset + 1usize, Bias::Right);
13381 if let Some(ch) = display_map
13382 .buffer_snapshot()
13383 .chars_at(transpose_start)
13384 .next()
13385 {
13386 edits.push((transpose_start..transpose_offset, String::new()));
13387 edits.push((transpose_end..transpose_end, ch.to_string()));
13388 }
13389 }
13390 });
13391 edits
13392 });
13393 this.buffer
13394 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13395 let selections = this
13396 .selections
13397 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13398 this.change_selections(Default::default(), window, cx, |s| {
13399 s.select(selections);
13400 });
13401 });
13402 }
13403
13404 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13405 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13406 if self.mode.is_single_line() {
13407 cx.propagate();
13408 return;
13409 }
13410
13411 self.rewrap_impl(RewrapOptions::default(), cx)
13412 }
13413
13414 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13415 let buffer = self.buffer.read(cx).snapshot(cx);
13416 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13417
13418 #[derive(Clone, Debug, PartialEq)]
13419 enum CommentFormat {
13420 /// single line comment, with prefix for line
13421 Line(String),
13422 /// single line within a block comment, with prefix for line
13423 BlockLine(String),
13424 /// a single line of a block comment that includes the initial delimiter
13425 BlockCommentWithStart(BlockCommentConfig),
13426 /// a single line of a block comment that includes the ending delimiter
13427 BlockCommentWithEnd(BlockCommentConfig),
13428 }
13429
13430 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13431 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13432 let language_settings = buffer.language_settings_at(selection.head(), cx);
13433 let language_scope = buffer.language_scope_at(selection.head());
13434
13435 let indent_and_prefix_for_row =
13436 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13437 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13438 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13439 &language_scope
13440 {
13441 let indent_end = Point::new(row, indent.len);
13442 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13443 let line_text_after_indent = buffer
13444 .text_for_range(indent_end..line_end)
13445 .collect::<String>();
13446
13447 let is_within_comment_override = buffer
13448 .language_scope_at(indent_end)
13449 .is_some_and(|scope| scope.override_name() == Some("comment"));
13450 let comment_delimiters = if is_within_comment_override {
13451 // we are within a comment syntax node, but we don't
13452 // yet know what kind of comment: block, doc or line
13453 match (
13454 language_scope.documentation_comment(),
13455 language_scope.block_comment(),
13456 ) {
13457 (Some(config), _) | (_, Some(config))
13458 if buffer.contains_str_at(indent_end, &config.start) =>
13459 {
13460 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13461 }
13462 (Some(config), _) | (_, Some(config))
13463 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13464 {
13465 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13466 }
13467 (Some(config), _) | (_, Some(config))
13468 if buffer.contains_str_at(indent_end, &config.prefix) =>
13469 {
13470 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13471 }
13472 (_, _) => language_scope
13473 .line_comment_prefixes()
13474 .iter()
13475 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13476 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13477 }
13478 } else {
13479 // we not in an overridden comment node, but we may
13480 // be within a non-overridden line comment node
13481 language_scope
13482 .line_comment_prefixes()
13483 .iter()
13484 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13485 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13486 };
13487
13488 let rewrap_prefix = language_scope
13489 .rewrap_prefixes()
13490 .iter()
13491 .find_map(|prefix_regex| {
13492 prefix_regex.find(&line_text_after_indent).map(|mat| {
13493 if mat.start() == 0 {
13494 Some(mat.as_str().to_string())
13495 } else {
13496 None
13497 }
13498 })
13499 })
13500 .flatten();
13501 (comment_delimiters, rewrap_prefix)
13502 } else {
13503 (None, None)
13504 };
13505 (indent, comment_prefix, rewrap_prefix)
13506 };
13507
13508 let mut start_row = selection.start.row;
13509 let mut end_row = selection.end.row;
13510
13511 if selection.is_empty() {
13512 let cursor_row = selection.start.row;
13513
13514 let (mut indent_size, comment_prefix, _) = indent_and_prefix_for_row(cursor_row);
13515 let line_prefix = match &comment_prefix {
13516 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13517 Some(prefix.as_str())
13518 }
13519 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13520 prefix, ..
13521 })) => Some(prefix.as_ref()),
13522 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13523 start: _,
13524 end: _,
13525 prefix,
13526 tab_size,
13527 })) => {
13528 indent_size.len += tab_size;
13529 Some(prefix.as_ref())
13530 }
13531 None => None,
13532 };
13533 let indent_prefix = indent_size.chars().collect::<String>();
13534 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13535
13536 'expand_upwards: while start_row > 0 {
13537 let prev_row = start_row - 1;
13538 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13539 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13540 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13541 {
13542 start_row = prev_row;
13543 } else {
13544 break 'expand_upwards;
13545 }
13546 }
13547
13548 'expand_downwards: while end_row < buffer.max_point().row {
13549 let next_row = end_row + 1;
13550 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13551 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13552 && !buffer.is_line_blank(MultiBufferRow(next_row))
13553 {
13554 end_row = next_row;
13555 } else {
13556 break 'expand_downwards;
13557 }
13558 }
13559 }
13560
13561 let mut non_blank_rows_iter = (start_row..=end_row)
13562 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13563 .peekable();
13564
13565 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13566 row
13567 } else {
13568 return Vec::new();
13569 };
13570
13571 let mut ranges = Vec::new();
13572
13573 let mut current_range_start = first_row;
13574 let mut prev_row = first_row;
13575 let (
13576 mut current_range_indent,
13577 mut current_range_comment_delimiters,
13578 mut current_range_rewrap_prefix,
13579 ) = indent_and_prefix_for_row(first_row);
13580
13581 for row in non_blank_rows_iter.skip(1) {
13582 let has_paragraph_break = row > prev_row + 1;
13583
13584 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13585 indent_and_prefix_for_row(row);
13586
13587 let has_indent_change = row_indent != current_range_indent;
13588 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13589
13590 let has_boundary_change = has_comment_change
13591 || row_rewrap_prefix.is_some()
13592 || (has_indent_change && current_range_comment_delimiters.is_some());
13593
13594 if has_paragraph_break || has_boundary_change {
13595 ranges.push((
13596 language_settings.clone(),
13597 Point::new(current_range_start, 0)
13598 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13599 current_range_indent,
13600 current_range_comment_delimiters.clone(),
13601 current_range_rewrap_prefix.clone(),
13602 ));
13603 current_range_start = row;
13604 current_range_indent = row_indent;
13605 current_range_comment_delimiters = row_comment_delimiters;
13606 current_range_rewrap_prefix = row_rewrap_prefix;
13607 }
13608 prev_row = row;
13609 }
13610
13611 ranges.push((
13612 language_settings.clone(),
13613 Point::new(current_range_start, 0)
13614 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13615 current_range_indent,
13616 current_range_comment_delimiters,
13617 current_range_rewrap_prefix,
13618 ));
13619
13620 ranges
13621 });
13622
13623 let mut edits = Vec::new();
13624 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13625
13626 for (language_settings, wrap_range, mut indent_size, comment_prefix, rewrap_prefix) in
13627 wrap_ranges
13628 {
13629 let start_row = wrap_range.start.row;
13630 let end_row = wrap_range.end.row;
13631
13632 // Skip selections that overlap with a range that has already been rewrapped.
13633 let selection_range = start_row..end_row;
13634 if rewrapped_row_ranges
13635 .iter()
13636 .any(|range| range.overlaps(&selection_range))
13637 {
13638 continue;
13639 }
13640
13641 let tab_size = language_settings.tab_size;
13642
13643 let (line_prefix, inside_comment) = match &comment_prefix {
13644 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13645 (Some(prefix.as_str()), true)
13646 }
13647 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13648 (Some(prefix.as_ref()), true)
13649 }
13650 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13651 start: _,
13652 end: _,
13653 prefix,
13654 tab_size,
13655 })) => {
13656 indent_size.len += tab_size;
13657 (Some(prefix.as_ref()), true)
13658 }
13659 None => (None, false),
13660 };
13661 let indent_prefix = indent_size.chars().collect::<String>();
13662 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13663
13664 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13665 RewrapBehavior::InComments => inside_comment,
13666 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13667 RewrapBehavior::Anywhere => true,
13668 };
13669
13670 let should_rewrap = options.override_language_settings
13671 || allow_rewrap_based_on_language
13672 || self.hard_wrap.is_some();
13673 if !should_rewrap {
13674 continue;
13675 }
13676
13677 let start = Point::new(start_row, 0);
13678 let start_offset = ToOffset::to_offset(&start, &buffer);
13679 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13680 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13681 let mut first_line_delimiter = None;
13682 let mut last_line_delimiter = None;
13683 let Some(lines_without_prefixes) = selection_text
13684 .lines()
13685 .enumerate()
13686 .map(|(ix, line)| {
13687 let line_trimmed = line.trim_start();
13688 if rewrap_prefix.is_some() && ix > 0 {
13689 Ok(line_trimmed)
13690 } else if let Some(
13691 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13692 start,
13693 prefix,
13694 end,
13695 tab_size,
13696 })
13697 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13698 start,
13699 prefix,
13700 end,
13701 tab_size,
13702 }),
13703 ) = &comment_prefix
13704 {
13705 let line_trimmed = line_trimmed
13706 .strip_prefix(start.as_ref())
13707 .map(|s| {
13708 let mut indent_size = indent_size;
13709 indent_size.len -= tab_size;
13710 let indent_prefix: String = indent_size.chars().collect();
13711 first_line_delimiter = Some((indent_prefix, start));
13712 s.trim_start()
13713 })
13714 .unwrap_or(line_trimmed);
13715 let line_trimmed = line_trimmed
13716 .strip_suffix(end.as_ref())
13717 .map(|s| {
13718 last_line_delimiter = Some(end);
13719 s.trim_end()
13720 })
13721 .unwrap_or(line_trimmed);
13722 let line_trimmed = line_trimmed
13723 .strip_prefix(prefix.as_ref())
13724 .unwrap_or(line_trimmed);
13725 Ok(line_trimmed)
13726 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13727 line_trimmed.strip_prefix(prefix).with_context(|| {
13728 format!("line did not start with prefix {prefix:?}: {line:?}")
13729 })
13730 } else {
13731 line_trimmed
13732 .strip_prefix(&line_prefix.trim_start())
13733 .with_context(|| {
13734 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13735 })
13736 }
13737 })
13738 .collect::<Result<Vec<_>, _>>()
13739 .log_err()
13740 else {
13741 continue;
13742 };
13743
13744 let wrap_column = options.line_length.or(self.hard_wrap).unwrap_or_else(|| {
13745 buffer
13746 .language_settings_at(Point::new(start_row, 0), cx)
13747 .preferred_line_length as usize
13748 });
13749
13750 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13751 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13752 } else {
13753 line_prefix.clone()
13754 };
13755
13756 let wrapped_text = {
13757 let mut wrapped_text = wrap_with_prefix(
13758 line_prefix,
13759 subsequent_lines_prefix,
13760 lines_without_prefixes.join("\n"),
13761 wrap_column,
13762 tab_size,
13763 options.preserve_existing_whitespace,
13764 );
13765
13766 if let Some((indent, delimiter)) = first_line_delimiter {
13767 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13768 }
13769 if let Some(last_line) = last_line_delimiter {
13770 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13771 }
13772
13773 wrapped_text
13774 };
13775
13776 // TODO: should always use char-based diff while still supporting cursor behavior that
13777 // matches vim.
13778 let mut diff_options = DiffOptions::default();
13779 if options.override_language_settings {
13780 diff_options.max_word_diff_len = 0;
13781 diff_options.max_word_diff_line_count = 0;
13782 } else {
13783 diff_options.max_word_diff_len = usize::MAX;
13784 diff_options.max_word_diff_line_count = usize::MAX;
13785 }
13786
13787 for (old_range, new_text) in
13788 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13789 {
13790 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13791 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13792 edits.push((edit_start..edit_end, new_text));
13793 }
13794
13795 rewrapped_row_ranges.push(start_row..=end_row);
13796 }
13797
13798 self.buffer
13799 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13800 }
13801
13802 pub fn cut_common(
13803 &mut self,
13804 cut_no_selection_line: bool,
13805 window: &mut Window,
13806 cx: &mut Context<Self>,
13807 ) -> ClipboardItem {
13808 let mut text = String::new();
13809 let buffer = self.buffer.read(cx).snapshot(cx);
13810 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13811 let mut clipboard_selections = Vec::with_capacity(selections.len());
13812 {
13813 let max_point = buffer.max_point();
13814 let mut is_first = true;
13815 let mut prev_selection_was_entire_line = false;
13816 for selection in &mut selections {
13817 let is_entire_line =
13818 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13819 if is_entire_line {
13820 selection.start = Point::new(selection.start.row, 0);
13821 if !selection.is_empty() && selection.end.column == 0 {
13822 selection.end = cmp::min(max_point, selection.end);
13823 } else {
13824 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13825 }
13826 selection.goal = SelectionGoal::None;
13827 }
13828 if is_first {
13829 is_first = false;
13830 } else if !prev_selection_was_entire_line {
13831 text += "\n";
13832 }
13833 prev_selection_was_entire_line = is_entire_line;
13834 let mut len = 0;
13835 for chunk in buffer.text_for_range(selection.start..selection.end) {
13836 text.push_str(chunk);
13837 len += chunk.len();
13838 }
13839
13840 clipboard_selections.push(ClipboardSelection::for_buffer(
13841 len,
13842 is_entire_line,
13843 selection.range(),
13844 &buffer,
13845 self.project.as_ref(),
13846 cx,
13847 ));
13848 }
13849 }
13850
13851 self.transact(window, cx, |this, window, cx| {
13852 this.change_selections(Default::default(), window, cx, |s| {
13853 s.select(selections);
13854 });
13855 this.insert("", window, cx);
13856 });
13857 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13858 }
13859
13860 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13861 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13862 let item = self.cut_common(true, window, cx);
13863 cx.write_to_clipboard(item);
13864 }
13865
13866 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13867 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13868 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13869 s.move_with(&mut |snapshot, sel| {
13870 if sel.is_empty() {
13871 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13872 }
13873 if sel.is_empty() {
13874 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13875 }
13876 });
13877 });
13878 let item = self.cut_common(false, window, cx);
13879 cx.set_global(KillRing(item))
13880 }
13881
13882 pub fn kill_ring_yank(
13883 &mut self,
13884 _: &KillRingYank,
13885 window: &mut Window,
13886 cx: &mut Context<Self>,
13887 ) {
13888 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13889 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13890 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13891 (kill_ring.text().to_string(), kill_ring.metadata_json())
13892 } else {
13893 return;
13894 }
13895 } else {
13896 return;
13897 };
13898 self.do_paste(&text, metadata, false, window, cx);
13899 }
13900
13901 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13902 self.do_copy(true, cx);
13903 }
13904
13905 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13906 self.do_copy(false, cx);
13907 }
13908
13909 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13910 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13911 let buffer = self.buffer.read(cx).read(cx);
13912 let mut text = String::new();
13913 let mut clipboard_selections = Vec::with_capacity(selections.len());
13914
13915 let max_point = buffer.max_point();
13916 let mut is_first = true;
13917 for selection in &selections {
13918 let mut start = selection.start;
13919 let mut end = selection.end;
13920 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13921 let mut add_trailing_newline = false;
13922 if is_entire_line {
13923 start = Point::new(start.row, 0);
13924 let next_line_start = Point::new(end.row + 1, 0);
13925 if next_line_start <= max_point {
13926 end = next_line_start;
13927 } else {
13928 // We're on the last line without a trailing newline.
13929 // Copy to the end of the line and add a newline afterwards.
13930 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13931 add_trailing_newline = true;
13932 }
13933 }
13934
13935 let mut trimmed_selections = Vec::new();
13936 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13937 let row = MultiBufferRow(start.row);
13938 let first_indent = buffer.indent_size_for_line(row);
13939 if first_indent.len == 0 || start.column > first_indent.len {
13940 trimmed_selections.push(start..end);
13941 } else {
13942 trimmed_selections.push(
13943 Point::new(row.0, first_indent.len)
13944 ..Point::new(row.0, buffer.line_len(row)),
13945 );
13946 for row in start.row + 1..=end.row {
13947 let mut line_len = buffer.line_len(MultiBufferRow(row));
13948 if row == end.row {
13949 line_len = end.column;
13950 }
13951 if line_len == 0 {
13952 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13953 continue;
13954 }
13955 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13956 if row_indent_size.len >= first_indent.len {
13957 trimmed_selections
13958 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13959 } else {
13960 trimmed_selections.clear();
13961 trimmed_selections.push(start..end);
13962 break;
13963 }
13964 }
13965 }
13966 } else {
13967 trimmed_selections.push(start..end);
13968 }
13969
13970 let is_multiline_trim = trimmed_selections.len() > 1;
13971 let mut selection_len: usize = 0;
13972 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13973
13974 for trimmed_range in trimmed_selections {
13975 if is_first {
13976 is_first = false;
13977 } else if is_multiline_trim || !prev_selection_was_entire_line {
13978 text.push('\n');
13979 if is_multiline_trim {
13980 selection_len += 1;
13981 }
13982 }
13983 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13984 text.push_str(chunk);
13985 selection_len += chunk.len();
13986 }
13987 if add_trailing_newline {
13988 text.push('\n');
13989 selection_len += 1;
13990 }
13991 }
13992
13993 clipboard_selections.push(ClipboardSelection::for_buffer(
13994 selection_len,
13995 is_entire_line,
13996 start..end,
13997 &buffer,
13998 self.project.as_ref(),
13999 cx,
14000 ));
14001 }
14002
14003 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
14004 text,
14005 clipboard_selections,
14006 ));
14007 }
14008
14009 pub fn do_paste(
14010 &mut self,
14011 text: &String,
14012 clipboard_selections: Option<Vec<ClipboardSelection>>,
14013 handle_entire_lines: bool,
14014 window: &mut Window,
14015 cx: &mut Context<Self>,
14016 ) {
14017 if self.read_only(cx) {
14018 return;
14019 }
14020
14021 self.finalize_last_transaction(cx);
14022
14023 let clipboard_text = Cow::Borrowed(text.as_str());
14024
14025 self.transact(window, cx, |this, window, cx| {
14026 let had_active_edit_prediction = this.has_active_edit_prediction();
14027 let display_map = this.display_snapshot(cx);
14028 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
14029 let cursor_offset = this
14030 .selections
14031 .last::<MultiBufferOffset>(&display_map)
14032 .head();
14033
14034 if let Some(mut clipboard_selections) = clipboard_selections {
14035 let all_selections_were_entire_line =
14036 clipboard_selections.iter().all(|s| s.is_entire_line);
14037 let first_selection_indent_column =
14038 clipboard_selections.first().map(|s| s.first_line_indent);
14039 if clipboard_selections.len() != old_selections.len() {
14040 clipboard_selections.drain(..);
14041 }
14042 let mut auto_indent_on_paste = true;
14043
14044 this.buffer.update(cx, |buffer, cx| {
14045 let snapshot = buffer.read(cx);
14046 auto_indent_on_paste = snapshot
14047 .language_settings_at(cursor_offset, cx)
14048 .auto_indent_on_paste;
14049
14050 let mut start_offset = 0;
14051 let mut edits = Vec::new();
14052 let mut original_indent_columns = Vec::new();
14053 for (ix, selection) in old_selections.iter().enumerate() {
14054 let to_insert;
14055 let entire_line;
14056 let original_indent_column;
14057 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
14058 let end_offset = start_offset + clipboard_selection.len;
14059 to_insert = &clipboard_text[start_offset..end_offset];
14060 entire_line = clipboard_selection.is_entire_line;
14061 start_offset = if entire_line {
14062 end_offset
14063 } else {
14064 end_offset + 1
14065 };
14066 original_indent_column = Some(clipboard_selection.first_line_indent);
14067 } else {
14068 to_insert = &*clipboard_text;
14069 entire_line = all_selections_were_entire_line;
14070 original_indent_column = first_selection_indent_column
14071 }
14072
14073 let (range, to_insert) =
14074 if selection.is_empty() && handle_entire_lines && entire_line {
14075 // If the corresponding selection was empty when this slice of the
14076 // clipboard text was written, then the entire line containing the
14077 // selection was copied. If this selection is also currently empty,
14078 // then paste the line before the current line of the buffer.
14079 let column = selection.start.to_point(&snapshot).column as usize;
14080 let line_start = selection.start - column;
14081 (line_start..line_start, Cow::Borrowed(to_insert))
14082 } else {
14083 let language = snapshot.language_at(selection.head());
14084 let range = selection.range();
14085 if let Some(language) = language
14086 && language.name() == "Markdown"
14087 {
14088 edit_for_markdown_paste(
14089 &snapshot,
14090 range,
14091 to_insert,
14092 url::Url::parse(to_insert).ok(),
14093 )
14094 } else {
14095 (range, Cow::Borrowed(to_insert))
14096 }
14097 };
14098
14099 edits.push((range, to_insert));
14100 original_indent_columns.push(original_indent_column);
14101 }
14102 drop(snapshot);
14103
14104 buffer.edit(
14105 edits,
14106 if auto_indent_on_paste {
14107 Some(AutoindentMode::Block {
14108 original_indent_columns,
14109 })
14110 } else {
14111 None
14112 },
14113 cx,
14114 );
14115 });
14116
14117 let selections = this
14118 .selections
14119 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
14120 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14121 } else {
14122 let url = url::Url::parse(&clipboard_text).ok();
14123
14124 let auto_indent_mode = if !clipboard_text.is_empty() {
14125 Some(AutoindentMode::Block {
14126 original_indent_columns: Vec::new(),
14127 })
14128 } else {
14129 None
14130 };
14131
14132 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
14133 let snapshot = buffer.snapshot(cx);
14134
14135 let anchors = old_selections
14136 .iter()
14137 .map(|s| {
14138 let anchor = snapshot.anchor_after(s.head());
14139 s.map(|_| anchor)
14140 })
14141 .collect::<Vec<_>>();
14142
14143 let mut edits = Vec::new();
14144
14145 // When pasting text without metadata (e.g. copied from an
14146 // external editor using multiple cursors) and the number of
14147 // lines matches the number of selections, distribute one
14148 // line per cursor instead of pasting the whole text at each.
14149 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14150 let distribute_lines =
14151 old_selections.len() > 1 && lines.len() == old_selections.len();
14152
14153 for (ix, selection) in old_selections.iter().enumerate() {
14154 let language = snapshot.language_at(selection.head());
14155 let range = selection.range();
14156
14157 let text_for_cursor: &str = if distribute_lines {
14158 lines[ix]
14159 } else {
14160 &clipboard_text
14161 };
14162
14163 let (edit_range, edit_text) = if let Some(language) = language
14164 && language.name() == "Markdown"
14165 {
14166 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14167 } else {
14168 (range, Cow::Borrowed(text_for_cursor))
14169 };
14170
14171 edits.push((edit_range, edit_text));
14172 }
14173
14174 drop(snapshot);
14175 buffer.edit(edits, auto_indent_mode, cx);
14176
14177 anchors
14178 });
14179
14180 this.change_selections(Default::default(), window, cx, |s| {
14181 s.select_anchors(selection_anchors);
14182 });
14183 }
14184
14185 // 🤔 | .. | show_in_menu |
14186 // | .. | true true
14187 // | had_edit_prediction | false true
14188
14189 let trigger_in_words =
14190 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14191
14192 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14193 });
14194 }
14195
14196 pub fn diff_clipboard_with_selection(
14197 &mut self,
14198 _: &DiffClipboardWithSelection,
14199 window: &mut Window,
14200 cx: &mut Context<Self>,
14201 ) {
14202 let selections = self
14203 .selections
14204 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14205
14206 if selections.is_empty() {
14207 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14208 return;
14209 };
14210
14211 let clipboard_text = cx.read_from_clipboard().and_then(|item| {
14212 item.entries().iter().find_map(|entry| match entry {
14213 ClipboardEntry::String(text) => Some(text.text().to_string()),
14214 _ => None,
14215 })
14216 });
14217
14218 let Some(clipboard_text) = clipboard_text else {
14219 log::warn!("Clipboard doesn't contain text.");
14220 return;
14221 };
14222
14223 window.dispatch_action(
14224 Box::new(DiffClipboardWithSelectionData {
14225 clipboard_text,
14226 editor: cx.entity(),
14227 }),
14228 cx,
14229 );
14230 }
14231
14232 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14233 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14234 if let Some(item) = cx.read_from_clipboard() {
14235 let clipboard_string = item.entries().iter().find_map(|entry| match entry {
14236 ClipboardEntry::String(s) => Some(s),
14237 _ => None,
14238 });
14239 match clipboard_string {
14240 Some(clipboard_string) => self.do_paste(
14241 clipboard_string.text(),
14242 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14243 true,
14244 window,
14245 cx,
14246 ),
14247 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14248 }
14249 }
14250 }
14251
14252 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14253 if self.read_only(cx) {
14254 return;
14255 }
14256
14257 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14258
14259 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14260 if let Some((selections, _)) =
14261 self.selection_history.transaction(transaction_id).cloned()
14262 {
14263 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14264 s.select_anchors(selections.to_vec());
14265 });
14266 } else {
14267 log::error!(
14268 "No entry in selection_history found for undo. \
14269 This may correspond to a bug where undo does not update the selection. \
14270 If this is occurring, please add details to \
14271 https://github.com/zed-industries/zed/issues/22692"
14272 );
14273 }
14274 self.request_autoscroll(Autoscroll::fit(), cx);
14275 self.unmark_text(window, cx);
14276 self.refresh_edit_prediction(true, false, window, cx);
14277 cx.emit(EditorEvent::Edited { transaction_id });
14278 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14279 }
14280 }
14281
14282 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14283 if self.read_only(cx) {
14284 return;
14285 }
14286
14287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14288
14289 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14290 if let Some((_, Some(selections))) =
14291 self.selection_history.transaction(transaction_id).cloned()
14292 {
14293 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14294 s.select_anchors(selections.to_vec());
14295 });
14296 } else {
14297 log::error!(
14298 "No entry in selection_history found for redo. \
14299 This may correspond to a bug where undo does not update the selection. \
14300 If this is occurring, please add details to \
14301 https://github.com/zed-industries/zed/issues/22692"
14302 );
14303 }
14304 self.request_autoscroll(Autoscroll::fit(), cx);
14305 self.unmark_text(window, cx);
14306 self.refresh_edit_prediction(true, false, window, cx);
14307 cx.emit(EditorEvent::Edited { transaction_id });
14308 }
14309 }
14310
14311 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14312 self.buffer
14313 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14314 }
14315
14316 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14317 self.buffer
14318 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14319 }
14320
14321 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14322 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14323 self.change_selections(Default::default(), window, cx, |s| {
14324 s.move_with(&mut |map, selection| {
14325 let cursor = if selection.is_empty() {
14326 movement::left(map, selection.start)
14327 } else {
14328 selection.start
14329 };
14330 selection.collapse_to(cursor, SelectionGoal::None);
14331 });
14332 })
14333 }
14334
14335 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14337 self.change_selections(Default::default(), window, cx, |s| {
14338 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14339 })
14340 }
14341
14342 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14343 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14344 self.change_selections(Default::default(), window, cx, |s| {
14345 s.move_with(&mut |map, selection| {
14346 let cursor = if selection.is_empty() {
14347 movement::right(map, selection.end)
14348 } else {
14349 selection.end
14350 };
14351 selection.collapse_to(cursor, SelectionGoal::None)
14352 });
14353 })
14354 }
14355
14356 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14358 self.change_selections(Default::default(), window, cx, |s| {
14359 s.move_heads_with(&mut |map, head, _| {
14360 (movement::right(map, head), SelectionGoal::None)
14361 });
14362 });
14363 }
14364
14365 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14366 if self.take_rename(true, window, cx).is_some() {
14367 return;
14368 }
14369
14370 if self.mode.is_single_line() {
14371 cx.propagate();
14372 return;
14373 }
14374
14375 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14376
14377 let text_layout_details = &self.text_layout_details(window, cx);
14378 let selection_count = self.selections.count();
14379 let first_selection = self.selections.first_anchor();
14380
14381 self.change_selections(Default::default(), window, cx, |s| {
14382 s.move_with(&mut |map, selection| {
14383 if !selection.is_empty() {
14384 selection.goal = SelectionGoal::None;
14385 }
14386 let (cursor, goal) = movement::up(
14387 map,
14388 selection.start,
14389 selection.goal,
14390 false,
14391 text_layout_details,
14392 );
14393 selection.collapse_to(cursor, goal);
14394 });
14395 });
14396
14397 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14398 {
14399 cx.propagate();
14400 }
14401 }
14402
14403 pub fn move_up_by_lines(
14404 &mut self,
14405 action: &MoveUpByLines,
14406 window: &mut Window,
14407 cx: &mut Context<Self>,
14408 ) {
14409 if self.take_rename(true, window, cx).is_some() {
14410 return;
14411 }
14412
14413 if self.mode.is_single_line() {
14414 cx.propagate();
14415 return;
14416 }
14417
14418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14419
14420 let text_layout_details = &self.text_layout_details(window, cx);
14421
14422 self.change_selections(Default::default(), window, cx, |s| {
14423 s.move_with(&mut |map, selection| {
14424 if !selection.is_empty() {
14425 selection.goal = SelectionGoal::None;
14426 }
14427 let (cursor, goal) = movement::up_by_rows(
14428 map,
14429 selection.start,
14430 action.lines,
14431 selection.goal,
14432 false,
14433 text_layout_details,
14434 );
14435 selection.collapse_to(cursor, goal);
14436 });
14437 })
14438 }
14439
14440 pub fn move_down_by_lines(
14441 &mut self,
14442 action: &MoveDownByLines,
14443 window: &mut Window,
14444 cx: &mut Context<Self>,
14445 ) {
14446 if self.take_rename(true, window, cx).is_some() {
14447 return;
14448 }
14449
14450 if self.mode.is_single_line() {
14451 cx.propagate();
14452 return;
14453 }
14454
14455 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14456
14457 let text_layout_details = &self.text_layout_details(window, cx);
14458
14459 self.change_selections(Default::default(), window, cx, |s| {
14460 s.move_with(&mut |map, selection| {
14461 if !selection.is_empty() {
14462 selection.goal = SelectionGoal::None;
14463 }
14464 let (cursor, goal) = movement::down_by_rows(
14465 map,
14466 selection.start,
14467 action.lines,
14468 selection.goal,
14469 false,
14470 text_layout_details,
14471 );
14472 selection.collapse_to(cursor, goal);
14473 });
14474 })
14475 }
14476
14477 pub fn select_down_by_lines(
14478 &mut self,
14479 action: &SelectDownByLines,
14480 window: &mut Window,
14481 cx: &mut Context<Self>,
14482 ) {
14483 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14484 let text_layout_details = &self.text_layout_details(window, cx);
14485 self.change_selections(Default::default(), window, cx, |s| {
14486 s.move_heads_with(&mut |map, head, goal| {
14487 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14488 })
14489 })
14490 }
14491
14492 pub fn select_up_by_lines(
14493 &mut self,
14494 action: &SelectUpByLines,
14495 window: &mut Window,
14496 cx: &mut Context<Self>,
14497 ) {
14498 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14499 let text_layout_details = &self.text_layout_details(window, cx);
14500 self.change_selections(Default::default(), window, cx, |s| {
14501 s.move_heads_with(&mut |map, head, goal| {
14502 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14503 })
14504 })
14505 }
14506
14507 pub fn select_page_up(
14508 &mut self,
14509 _: &SelectPageUp,
14510 window: &mut Window,
14511 cx: &mut Context<Self>,
14512 ) {
14513 let Some(row_count) = self.visible_row_count() else {
14514 return;
14515 };
14516
14517 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14518
14519 let text_layout_details = &self.text_layout_details(window, cx);
14520
14521 self.change_selections(Default::default(), window, cx, |s| {
14522 s.move_heads_with(&mut |map, head, goal| {
14523 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14524 })
14525 })
14526 }
14527
14528 pub fn move_page_up(
14529 &mut self,
14530 action: &MovePageUp,
14531 window: &mut Window,
14532 cx: &mut Context<Self>,
14533 ) {
14534 if self.take_rename(true, window, cx).is_some() {
14535 return;
14536 }
14537
14538 if self
14539 .context_menu
14540 .borrow_mut()
14541 .as_mut()
14542 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14543 .unwrap_or(false)
14544 {
14545 return;
14546 }
14547
14548 if matches!(self.mode, EditorMode::SingleLine) {
14549 cx.propagate();
14550 return;
14551 }
14552
14553 let Some(row_count) = self.visible_row_count() else {
14554 return;
14555 };
14556
14557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14558
14559 let effects = if action.center_cursor {
14560 SelectionEffects::scroll(Autoscroll::center())
14561 } else {
14562 SelectionEffects::default()
14563 };
14564
14565 let text_layout_details = &self.text_layout_details(window, cx);
14566
14567 self.change_selections(effects, window, cx, |s| {
14568 s.move_with(&mut |map, selection| {
14569 if !selection.is_empty() {
14570 selection.goal = SelectionGoal::None;
14571 }
14572 let (cursor, goal) = movement::up_by_rows(
14573 map,
14574 selection.end,
14575 row_count,
14576 selection.goal,
14577 false,
14578 text_layout_details,
14579 );
14580 selection.collapse_to(cursor, goal);
14581 });
14582 });
14583 }
14584
14585 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14586 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14587 let text_layout_details = &self.text_layout_details(window, cx);
14588 self.change_selections(Default::default(), window, cx, |s| {
14589 s.move_heads_with(&mut |map, head, goal| {
14590 movement::up(map, head, goal, false, text_layout_details)
14591 })
14592 })
14593 }
14594
14595 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14596 self.take_rename(true, window, cx);
14597
14598 if self.mode.is_single_line() {
14599 cx.propagate();
14600 return;
14601 }
14602
14603 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14604
14605 let text_layout_details = &self.text_layout_details(window, cx);
14606 let selection_count = self.selections.count();
14607 let first_selection = self.selections.first_anchor();
14608
14609 self.change_selections(Default::default(), window, cx, |s| {
14610 s.move_with(&mut |map, selection| {
14611 if !selection.is_empty() {
14612 selection.goal = SelectionGoal::None;
14613 }
14614 let (cursor, goal) = movement::down(
14615 map,
14616 selection.end,
14617 selection.goal,
14618 false,
14619 text_layout_details,
14620 );
14621 selection.collapse_to(cursor, goal);
14622 });
14623 });
14624
14625 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14626 {
14627 cx.propagate();
14628 }
14629 }
14630
14631 pub fn select_page_down(
14632 &mut self,
14633 _: &SelectPageDown,
14634 window: &mut Window,
14635 cx: &mut Context<Self>,
14636 ) {
14637 let Some(row_count) = self.visible_row_count() else {
14638 return;
14639 };
14640
14641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14642
14643 let text_layout_details = &self.text_layout_details(window, cx);
14644
14645 self.change_selections(Default::default(), window, cx, |s| {
14646 s.move_heads_with(&mut |map, head, goal| {
14647 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14648 })
14649 })
14650 }
14651
14652 pub fn move_page_down(
14653 &mut self,
14654 action: &MovePageDown,
14655 window: &mut Window,
14656 cx: &mut Context<Self>,
14657 ) {
14658 if self.take_rename(true, window, cx).is_some() {
14659 return;
14660 }
14661
14662 if self
14663 .context_menu
14664 .borrow_mut()
14665 .as_mut()
14666 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14667 .unwrap_or(false)
14668 {
14669 return;
14670 }
14671
14672 if matches!(self.mode, EditorMode::SingleLine) {
14673 cx.propagate();
14674 return;
14675 }
14676
14677 let Some(row_count) = self.visible_row_count() else {
14678 return;
14679 };
14680
14681 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14682
14683 let effects = if action.center_cursor {
14684 SelectionEffects::scroll(Autoscroll::center())
14685 } else {
14686 SelectionEffects::default()
14687 };
14688
14689 let text_layout_details = &self.text_layout_details(window, cx);
14690 self.change_selections(effects, window, cx, |s| {
14691 s.move_with(&mut |map, selection| {
14692 if !selection.is_empty() {
14693 selection.goal = SelectionGoal::None;
14694 }
14695 let (cursor, goal) = movement::down_by_rows(
14696 map,
14697 selection.end,
14698 row_count,
14699 selection.goal,
14700 false,
14701 text_layout_details,
14702 );
14703 selection.collapse_to(cursor, goal);
14704 });
14705 });
14706 }
14707
14708 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14709 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14710 let text_layout_details = &self.text_layout_details(window, cx);
14711 self.change_selections(Default::default(), window, cx, |s| {
14712 s.move_heads_with(&mut |map, head, goal| {
14713 movement::down(map, head, goal, false, text_layout_details)
14714 })
14715 });
14716 }
14717
14718 pub fn context_menu_first(
14719 &mut self,
14720 _: &ContextMenuFirst,
14721 window: &mut Window,
14722 cx: &mut Context<Self>,
14723 ) {
14724 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14725 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14726 }
14727 }
14728
14729 pub fn context_menu_prev(
14730 &mut self,
14731 _: &ContextMenuPrevious,
14732 window: &mut Window,
14733 cx: &mut Context<Self>,
14734 ) {
14735 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14736 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14737 }
14738 }
14739
14740 pub fn context_menu_next(
14741 &mut self,
14742 _: &ContextMenuNext,
14743 window: &mut Window,
14744 cx: &mut Context<Self>,
14745 ) {
14746 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14747 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14748 }
14749 }
14750
14751 pub fn context_menu_last(
14752 &mut self,
14753 _: &ContextMenuLast,
14754 window: &mut Window,
14755 cx: &mut Context<Self>,
14756 ) {
14757 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14758 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14759 }
14760 }
14761
14762 pub fn signature_help_prev(
14763 &mut self,
14764 _: &SignatureHelpPrevious,
14765 _: &mut Window,
14766 cx: &mut Context<Self>,
14767 ) {
14768 if let Some(popover) = self.signature_help_state.popover_mut() {
14769 if popover.current_signature == 0 {
14770 popover.current_signature = popover.signatures.len() - 1;
14771 } else {
14772 popover.current_signature -= 1;
14773 }
14774 cx.notify();
14775 }
14776 }
14777
14778 pub fn signature_help_next(
14779 &mut self,
14780 _: &SignatureHelpNext,
14781 _: &mut Window,
14782 cx: &mut Context<Self>,
14783 ) {
14784 if let Some(popover) = self.signature_help_state.popover_mut() {
14785 if popover.current_signature + 1 == popover.signatures.len() {
14786 popover.current_signature = 0;
14787 } else {
14788 popover.current_signature += 1;
14789 }
14790 cx.notify();
14791 }
14792 }
14793
14794 pub fn move_to_previous_word_start(
14795 &mut self,
14796 _: &MoveToPreviousWordStart,
14797 window: &mut Window,
14798 cx: &mut Context<Self>,
14799 ) {
14800 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14801 self.change_selections(Default::default(), window, cx, |s| {
14802 s.move_cursors_with(&mut |map, head, _| {
14803 (
14804 movement::previous_word_start(map, head),
14805 SelectionGoal::None,
14806 )
14807 });
14808 })
14809 }
14810
14811 pub fn move_to_previous_subword_start(
14812 &mut self,
14813 _: &MoveToPreviousSubwordStart,
14814 window: &mut Window,
14815 cx: &mut Context<Self>,
14816 ) {
14817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14818 self.change_selections(Default::default(), window, cx, |s| {
14819 s.move_cursors_with(&mut |map, head, _| {
14820 (
14821 movement::previous_subword_start(map, head),
14822 SelectionGoal::None,
14823 )
14824 });
14825 })
14826 }
14827
14828 pub fn select_to_previous_word_start(
14829 &mut self,
14830 _: &SelectToPreviousWordStart,
14831 window: &mut Window,
14832 cx: &mut Context<Self>,
14833 ) {
14834 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14835 self.change_selections(Default::default(), window, cx, |s| {
14836 s.move_heads_with(&mut |map, head, _| {
14837 (
14838 movement::previous_word_start(map, head),
14839 SelectionGoal::None,
14840 )
14841 });
14842 })
14843 }
14844
14845 pub fn select_to_previous_subword_start(
14846 &mut self,
14847 _: &SelectToPreviousSubwordStart,
14848 window: &mut Window,
14849 cx: &mut Context<Self>,
14850 ) {
14851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14852 self.change_selections(Default::default(), window, cx, |s| {
14853 s.move_heads_with(&mut |map, head, _| {
14854 (
14855 movement::previous_subword_start(map, head),
14856 SelectionGoal::None,
14857 )
14858 });
14859 })
14860 }
14861
14862 pub fn delete_to_previous_word_start(
14863 &mut self,
14864 action: &DeleteToPreviousWordStart,
14865 window: &mut Window,
14866 cx: &mut Context<Self>,
14867 ) {
14868 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14869 self.transact(window, cx, |this, window, cx| {
14870 this.select_autoclose_pair(window, cx);
14871 this.change_selections(Default::default(), window, cx, |s| {
14872 s.move_with(&mut |map, selection| {
14873 if selection.is_empty() {
14874 let mut cursor = if action.ignore_newlines {
14875 movement::previous_word_start(map, selection.head())
14876 } else {
14877 movement::previous_word_start_or_newline(map, selection.head())
14878 };
14879 cursor = movement::adjust_greedy_deletion(
14880 map,
14881 selection.head(),
14882 cursor,
14883 action.ignore_brackets,
14884 );
14885 selection.set_head(cursor, SelectionGoal::None);
14886 }
14887 });
14888 });
14889 this.insert("", window, cx);
14890 });
14891 }
14892
14893 pub fn delete_to_previous_subword_start(
14894 &mut self,
14895 action: &DeleteToPreviousSubwordStart,
14896 window: &mut Window,
14897 cx: &mut Context<Self>,
14898 ) {
14899 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14900 self.transact(window, cx, |this, window, cx| {
14901 this.select_autoclose_pair(window, cx);
14902 this.change_selections(Default::default(), window, cx, |s| {
14903 s.move_with(&mut |map, selection| {
14904 if selection.is_empty() {
14905 let mut cursor = if action.ignore_newlines {
14906 movement::previous_subword_start(map, selection.head())
14907 } else {
14908 movement::previous_subword_start_or_newline(map, selection.head())
14909 };
14910 cursor = movement::adjust_greedy_deletion(
14911 map,
14912 selection.head(),
14913 cursor,
14914 action.ignore_brackets,
14915 );
14916 selection.set_head(cursor, SelectionGoal::None);
14917 }
14918 });
14919 });
14920 this.insert("", window, cx);
14921 });
14922 }
14923
14924 pub fn move_to_next_word_end(
14925 &mut self,
14926 _: &MoveToNextWordEnd,
14927 window: &mut Window,
14928 cx: &mut Context<Self>,
14929 ) {
14930 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14931 self.change_selections(Default::default(), window, cx, |s| {
14932 s.move_cursors_with(&mut |map, head, _| {
14933 (movement::next_word_end(map, head), SelectionGoal::None)
14934 });
14935 })
14936 }
14937
14938 pub fn move_to_next_subword_end(
14939 &mut self,
14940 _: &MoveToNextSubwordEnd,
14941 window: &mut Window,
14942 cx: &mut Context<Self>,
14943 ) {
14944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14945 self.change_selections(Default::default(), window, cx, |s| {
14946 s.move_cursors_with(&mut |map, head, _| {
14947 (movement::next_subword_end(map, head), SelectionGoal::None)
14948 });
14949 })
14950 }
14951
14952 pub fn select_to_next_word_end(
14953 &mut self,
14954 _: &SelectToNextWordEnd,
14955 window: &mut Window,
14956 cx: &mut Context<Self>,
14957 ) {
14958 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14959 self.change_selections(Default::default(), window, cx, |s| {
14960 s.move_heads_with(&mut |map, head, _| {
14961 (movement::next_word_end(map, head), SelectionGoal::None)
14962 });
14963 })
14964 }
14965
14966 pub fn select_to_next_subword_end(
14967 &mut self,
14968 _: &SelectToNextSubwordEnd,
14969 window: &mut Window,
14970 cx: &mut Context<Self>,
14971 ) {
14972 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14973 self.change_selections(Default::default(), window, cx, |s| {
14974 s.move_heads_with(&mut |map, head, _| {
14975 (movement::next_subword_end(map, head), SelectionGoal::None)
14976 });
14977 })
14978 }
14979
14980 pub fn delete_to_next_word_end(
14981 &mut self,
14982 action: &DeleteToNextWordEnd,
14983 window: &mut Window,
14984 cx: &mut Context<Self>,
14985 ) {
14986 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14987 self.transact(window, cx, |this, window, cx| {
14988 this.change_selections(Default::default(), window, cx, |s| {
14989 s.move_with(&mut |map, selection| {
14990 if selection.is_empty() {
14991 let mut cursor = if action.ignore_newlines {
14992 movement::next_word_end(map, selection.head())
14993 } else {
14994 movement::next_word_end_or_newline(map, selection.head())
14995 };
14996 cursor = movement::adjust_greedy_deletion(
14997 map,
14998 selection.head(),
14999 cursor,
15000 action.ignore_brackets,
15001 );
15002 selection.set_head(cursor, SelectionGoal::None);
15003 }
15004 });
15005 });
15006 this.insert("", window, cx);
15007 });
15008 }
15009
15010 pub fn delete_to_next_subword_end(
15011 &mut self,
15012 action: &DeleteToNextSubwordEnd,
15013 window: &mut Window,
15014 cx: &mut Context<Self>,
15015 ) {
15016 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15017 self.transact(window, cx, |this, window, cx| {
15018 this.change_selections(Default::default(), window, cx, |s| {
15019 s.move_with(&mut |map, selection| {
15020 if selection.is_empty() {
15021 let mut cursor = if action.ignore_newlines {
15022 movement::next_subword_end(map, selection.head())
15023 } else {
15024 movement::next_subword_end_or_newline(map, selection.head())
15025 };
15026 cursor = movement::adjust_greedy_deletion(
15027 map,
15028 selection.head(),
15029 cursor,
15030 action.ignore_brackets,
15031 );
15032 selection.set_head(cursor, SelectionGoal::None);
15033 }
15034 });
15035 });
15036 this.insert("", window, cx);
15037 });
15038 }
15039
15040 pub fn move_to_beginning_of_line(
15041 &mut self,
15042 action: &MoveToBeginningOfLine,
15043 window: &mut Window,
15044 cx: &mut Context<Self>,
15045 ) {
15046 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15047 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15048 self.change_selections(Default::default(), window, cx, |s| {
15049 s.move_cursors_with(&mut |map, head, _| {
15050 (
15051 movement::indented_line_beginning(
15052 map,
15053 head,
15054 action.stop_at_soft_wraps,
15055 stop_at_indent,
15056 ),
15057 SelectionGoal::None,
15058 )
15059 });
15060 })
15061 }
15062
15063 pub fn select_to_beginning_of_line(
15064 &mut self,
15065 action: &SelectToBeginningOfLine,
15066 window: &mut Window,
15067 cx: &mut Context<Self>,
15068 ) {
15069 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15071 self.change_selections(Default::default(), window, cx, |s| {
15072 s.move_heads_with(&mut |map, head, _| {
15073 (
15074 movement::indented_line_beginning(
15075 map,
15076 head,
15077 action.stop_at_soft_wraps,
15078 stop_at_indent,
15079 ),
15080 SelectionGoal::None,
15081 )
15082 });
15083 });
15084 }
15085
15086 pub fn delete_to_beginning_of_line(
15087 &mut self,
15088 action: &DeleteToBeginningOfLine,
15089 window: &mut Window,
15090 cx: &mut Context<Self>,
15091 ) {
15092 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15093 self.transact(window, cx, |this, window, cx| {
15094 this.change_selections(Default::default(), window, cx, |s| {
15095 s.move_with(&mut |_, selection| {
15096 selection.reversed = true;
15097 });
15098 });
15099
15100 this.select_to_beginning_of_line(
15101 &SelectToBeginningOfLine {
15102 stop_at_soft_wraps: false,
15103 stop_at_indent: action.stop_at_indent,
15104 },
15105 window,
15106 cx,
15107 );
15108 this.backspace(&Backspace, window, cx);
15109 });
15110 }
15111
15112 pub fn move_to_end_of_line(
15113 &mut self,
15114 action: &MoveToEndOfLine,
15115 window: &mut Window,
15116 cx: &mut Context<Self>,
15117 ) {
15118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15119 self.change_selections(Default::default(), window, cx, |s| {
15120 s.move_cursors_with(&mut |map, head, _| {
15121 (
15122 movement::line_end(map, head, action.stop_at_soft_wraps),
15123 SelectionGoal::None,
15124 )
15125 });
15126 })
15127 }
15128
15129 pub fn select_to_end_of_line(
15130 &mut self,
15131 action: &SelectToEndOfLine,
15132 window: &mut Window,
15133 cx: &mut Context<Self>,
15134 ) {
15135 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15136 self.change_selections(Default::default(), window, cx, |s| {
15137 s.move_heads_with(&mut |map, head, _| {
15138 (
15139 movement::line_end(map, head, action.stop_at_soft_wraps),
15140 SelectionGoal::None,
15141 )
15142 });
15143 })
15144 }
15145
15146 pub fn delete_to_end_of_line(
15147 &mut self,
15148 _: &DeleteToEndOfLine,
15149 window: &mut Window,
15150 cx: &mut Context<Self>,
15151 ) {
15152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15153 self.transact(window, cx, |this, window, cx| {
15154 this.select_to_end_of_line(
15155 &SelectToEndOfLine {
15156 stop_at_soft_wraps: false,
15157 },
15158 window,
15159 cx,
15160 );
15161 this.delete(&Delete, window, cx);
15162 });
15163 }
15164
15165 pub fn cut_to_end_of_line(
15166 &mut self,
15167 action: &CutToEndOfLine,
15168 window: &mut Window,
15169 cx: &mut Context<Self>,
15170 ) {
15171 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15172 self.transact(window, cx, |this, window, cx| {
15173 this.select_to_end_of_line(
15174 &SelectToEndOfLine {
15175 stop_at_soft_wraps: false,
15176 },
15177 window,
15178 cx,
15179 );
15180 if !action.stop_at_newlines {
15181 this.change_selections(Default::default(), window, cx, |s| {
15182 s.move_with(&mut |_, sel| {
15183 if sel.is_empty() {
15184 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15185 }
15186 });
15187 });
15188 }
15189 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15190 let item = this.cut_common(false, window, cx);
15191 cx.write_to_clipboard(item);
15192 });
15193 }
15194
15195 pub fn move_to_start_of_paragraph(
15196 &mut self,
15197 _: &MoveToStartOfParagraph,
15198 window: &mut Window,
15199 cx: &mut Context<Self>,
15200 ) {
15201 if matches!(self.mode, EditorMode::SingleLine) {
15202 cx.propagate();
15203 return;
15204 }
15205 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15206 self.change_selections(Default::default(), window, cx, |s| {
15207 s.move_with(&mut |map, selection| {
15208 selection.collapse_to(
15209 movement::start_of_paragraph(map, selection.head(), 1),
15210 SelectionGoal::None,
15211 )
15212 });
15213 })
15214 }
15215
15216 pub fn move_to_end_of_paragraph(
15217 &mut self,
15218 _: &MoveToEndOfParagraph,
15219 window: &mut Window,
15220 cx: &mut Context<Self>,
15221 ) {
15222 if matches!(self.mode, EditorMode::SingleLine) {
15223 cx.propagate();
15224 return;
15225 }
15226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15227 self.change_selections(Default::default(), window, cx, |s| {
15228 s.move_with(&mut |map, selection| {
15229 selection.collapse_to(
15230 movement::end_of_paragraph(map, selection.head(), 1),
15231 SelectionGoal::None,
15232 )
15233 });
15234 })
15235 }
15236
15237 pub fn select_to_start_of_paragraph(
15238 &mut self,
15239 _: &SelectToStartOfParagraph,
15240 window: &mut Window,
15241 cx: &mut Context<Self>,
15242 ) {
15243 if matches!(self.mode, EditorMode::SingleLine) {
15244 cx.propagate();
15245 return;
15246 }
15247 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15248 self.change_selections(Default::default(), window, cx, |s| {
15249 s.move_heads_with(&mut |map, head, _| {
15250 (
15251 movement::start_of_paragraph(map, head, 1),
15252 SelectionGoal::None,
15253 )
15254 });
15255 })
15256 }
15257
15258 pub fn select_to_end_of_paragraph(
15259 &mut self,
15260 _: &SelectToEndOfParagraph,
15261 window: &mut Window,
15262 cx: &mut Context<Self>,
15263 ) {
15264 if matches!(self.mode, EditorMode::SingleLine) {
15265 cx.propagate();
15266 return;
15267 }
15268 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15269 self.change_selections(Default::default(), window, cx, |s| {
15270 s.move_heads_with(&mut |map, head, _| {
15271 (
15272 movement::end_of_paragraph(map, head, 1),
15273 SelectionGoal::None,
15274 )
15275 });
15276 })
15277 }
15278
15279 pub fn move_to_start_of_excerpt(
15280 &mut self,
15281 _: &MoveToStartOfExcerpt,
15282 window: &mut Window,
15283 cx: &mut Context<Self>,
15284 ) {
15285 if matches!(self.mode, EditorMode::SingleLine) {
15286 cx.propagate();
15287 return;
15288 }
15289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15290 self.change_selections(Default::default(), window, cx, |s| {
15291 s.move_with(&mut |map, selection| {
15292 selection.collapse_to(
15293 movement::start_of_excerpt(
15294 map,
15295 selection.head(),
15296 workspace::searchable::Direction::Prev,
15297 ),
15298 SelectionGoal::None,
15299 )
15300 });
15301 })
15302 }
15303
15304 pub fn move_to_start_of_next_excerpt(
15305 &mut self,
15306 _: &MoveToStartOfNextExcerpt,
15307 window: &mut Window,
15308 cx: &mut Context<Self>,
15309 ) {
15310 if matches!(self.mode, EditorMode::SingleLine) {
15311 cx.propagate();
15312 return;
15313 }
15314
15315 self.change_selections(Default::default(), window, cx, |s| {
15316 s.move_with(&mut |map, selection| {
15317 selection.collapse_to(
15318 movement::start_of_excerpt(
15319 map,
15320 selection.head(),
15321 workspace::searchable::Direction::Next,
15322 ),
15323 SelectionGoal::None,
15324 )
15325 });
15326 })
15327 }
15328
15329 pub fn move_to_end_of_excerpt(
15330 &mut self,
15331 _: &MoveToEndOfExcerpt,
15332 window: &mut Window,
15333 cx: &mut Context<Self>,
15334 ) {
15335 if matches!(self.mode, EditorMode::SingleLine) {
15336 cx.propagate();
15337 return;
15338 }
15339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15340 self.change_selections(Default::default(), window, cx, |s| {
15341 s.move_with(&mut |map, selection| {
15342 selection.collapse_to(
15343 movement::end_of_excerpt(
15344 map,
15345 selection.head(),
15346 workspace::searchable::Direction::Next,
15347 ),
15348 SelectionGoal::None,
15349 )
15350 });
15351 })
15352 }
15353
15354 pub fn move_to_end_of_previous_excerpt(
15355 &mut self,
15356 _: &MoveToEndOfPreviousExcerpt,
15357 window: &mut Window,
15358 cx: &mut Context<Self>,
15359 ) {
15360 if matches!(self.mode, EditorMode::SingleLine) {
15361 cx.propagate();
15362 return;
15363 }
15364 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15365 self.change_selections(Default::default(), window, cx, |s| {
15366 s.move_with(&mut |map, selection| {
15367 selection.collapse_to(
15368 movement::end_of_excerpt(
15369 map,
15370 selection.head(),
15371 workspace::searchable::Direction::Prev,
15372 ),
15373 SelectionGoal::None,
15374 )
15375 });
15376 })
15377 }
15378
15379 pub fn select_to_start_of_excerpt(
15380 &mut self,
15381 _: &SelectToStartOfExcerpt,
15382 window: &mut Window,
15383 cx: &mut Context<Self>,
15384 ) {
15385 if matches!(self.mode, EditorMode::SingleLine) {
15386 cx.propagate();
15387 return;
15388 }
15389 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15390 self.change_selections(Default::default(), window, cx, |s| {
15391 s.move_heads_with(&mut |map, head, _| {
15392 (
15393 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15394 SelectionGoal::None,
15395 )
15396 });
15397 })
15398 }
15399
15400 pub fn select_to_start_of_next_excerpt(
15401 &mut self,
15402 _: &SelectToStartOfNextExcerpt,
15403 window: &mut Window,
15404 cx: &mut Context<Self>,
15405 ) {
15406 if matches!(self.mode, EditorMode::SingleLine) {
15407 cx.propagate();
15408 return;
15409 }
15410 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15411 self.change_selections(Default::default(), window, cx, |s| {
15412 s.move_heads_with(&mut |map, head, _| {
15413 (
15414 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15415 SelectionGoal::None,
15416 )
15417 });
15418 })
15419 }
15420
15421 pub fn select_to_end_of_excerpt(
15422 &mut self,
15423 _: &SelectToEndOfExcerpt,
15424 window: &mut Window,
15425 cx: &mut Context<Self>,
15426 ) {
15427 if matches!(self.mode, EditorMode::SingleLine) {
15428 cx.propagate();
15429 return;
15430 }
15431 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15432 self.change_selections(Default::default(), window, cx, |s| {
15433 s.move_heads_with(&mut |map, head, _| {
15434 (
15435 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15436 SelectionGoal::None,
15437 )
15438 });
15439 })
15440 }
15441
15442 pub fn select_to_end_of_previous_excerpt(
15443 &mut self,
15444 _: &SelectToEndOfPreviousExcerpt,
15445 window: &mut Window,
15446 cx: &mut Context<Self>,
15447 ) {
15448 if matches!(self.mode, EditorMode::SingleLine) {
15449 cx.propagate();
15450 return;
15451 }
15452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15453 self.change_selections(Default::default(), window, cx, |s| {
15454 s.move_heads_with(&mut |map, head, _| {
15455 (
15456 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15457 SelectionGoal::None,
15458 )
15459 });
15460 })
15461 }
15462
15463 pub fn move_to_beginning(
15464 &mut self,
15465 _: &MoveToBeginning,
15466 window: &mut Window,
15467 cx: &mut Context<Self>,
15468 ) {
15469 if matches!(self.mode, EditorMode::SingleLine) {
15470 cx.propagate();
15471 return;
15472 }
15473 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15474 self.change_selections(Default::default(), window, cx, |s| {
15475 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15476 });
15477 }
15478
15479 pub fn select_to_beginning(
15480 &mut self,
15481 _: &SelectToBeginning,
15482 window: &mut Window,
15483 cx: &mut Context<Self>,
15484 ) {
15485 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15486 selection.set_head(Point::zero(), SelectionGoal::None);
15487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15488 self.change_selections(Default::default(), window, cx, |s| {
15489 s.select(vec![selection]);
15490 });
15491 }
15492
15493 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15494 if matches!(self.mode, EditorMode::SingleLine) {
15495 cx.propagate();
15496 return;
15497 }
15498 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15499 let cursor = self.buffer.read(cx).read(cx).len();
15500 self.change_selections(Default::default(), window, cx, |s| {
15501 s.select_ranges(vec![cursor..cursor])
15502 });
15503 }
15504
15505 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15506 self.nav_history = nav_history;
15507 }
15508
15509 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15510 self.nav_history.as_ref()
15511 }
15512
15513 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15514 self.push_to_nav_history(
15515 self.selections.newest_anchor().head(),
15516 None,
15517 false,
15518 true,
15519 cx,
15520 );
15521 }
15522
15523 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15524 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15525 let buffer = self.buffer.read(cx).read(cx);
15526 let cursor_position = cursor_anchor.to_point(&buffer);
15527 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15528 let scroll_top_row = scroll_anchor.top_row(&buffer);
15529 drop(buffer);
15530
15531 NavigationData {
15532 cursor_anchor,
15533 cursor_position,
15534 scroll_anchor,
15535 scroll_top_row,
15536 }
15537 }
15538
15539 fn navigation_entry(
15540 &self,
15541 cursor_anchor: Anchor,
15542 cx: &mut Context<Self>,
15543 ) -> Option<NavigationEntry> {
15544 let Some(history) = self.nav_history.clone() else {
15545 return None;
15546 };
15547 let data = self.navigation_data(cursor_anchor, cx);
15548 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15549 }
15550
15551 fn push_to_nav_history(
15552 &mut self,
15553 cursor_anchor: Anchor,
15554 new_position: Option<Point>,
15555 is_deactivate: bool,
15556 always: bool,
15557 cx: &mut Context<Self>,
15558 ) {
15559 let data = self.navigation_data(cursor_anchor, cx);
15560 if let Some(nav_history) = self.nav_history.as_mut() {
15561 if let Some(new_position) = new_position {
15562 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15563 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15564 return;
15565 }
15566 }
15567
15568 let cursor_row = data.cursor_position.row;
15569 nav_history.push(Some(data), Some(cursor_row), cx);
15570 cx.emit(EditorEvent::PushedToNavHistory {
15571 anchor: cursor_anchor,
15572 is_deactivate,
15573 })
15574 }
15575 }
15576
15577 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15579 let buffer = self.buffer.read(cx).snapshot(cx);
15580 let mut selection = self
15581 .selections
15582 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15583 selection.set_head(buffer.len(), SelectionGoal::None);
15584 self.change_selections(Default::default(), window, cx, |s| {
15585 s.select(vec![selection]);
15586 });
15587 }
15588
15589 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15591 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15592 s.select_ranges([Anchor::min()..Anchor::max()]);
15593 });
15594 }
15595
15596 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15597 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15598 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15599 let mut selections = self.selections.all::<Point>(&display_map);
15600 let max_point = display_map.buffer_snapshot().max_point();
15601 for selection in &mut selections {
15602 let rows = selection.spanned_rows(true, &display_map);
15603 selection.start = Point::new(rows.start.0, 0);
15604 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15605 selection.reversed = false;
15606 }
15607 self.change_selections(Default::default(), window, cx, |s| {
15608 s.select(selections);
15609 });
15610 }
15611
15612 pub fn split_selection_into_lines(
15613 &mut self,
15614 action: &SplitSelectionIntoLines,
15615 window: &mut Window,
15616 cx: &mut Context<Self>,
15617 ) {
15618 let selections = self
15619 .selections
15620 .all::<Point>(&self.display_snapshot(cx))
15621 .into_iter()
15622 .map(|selection| selection.start..selection.end)
15623 .collect::<Vec<_>>();
15624 self.unfold_ranges(&selections, true, false, cx);
15625
15626 let mut new_selection_ranges = Vec::new();
15627 {
15628 let buffer = self.buffer.read(cx).read(cx);
15629 for selection in selections {
15630 for row in selection.start.row..selection.end.row {
15631 let line_start = Point::new(row, 0);
15632 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15633
15634 if action.keep_selections {
15635 // Keep the selection range for each line
15636 let selection_start = if row == selection.start.row {
15637 selection.start
15638 } else {
15639 line_start
15640 };
15641 new_selection_ranges.push(selection_start..line_end);
15642 } else {
15643 // Collapse to cursor at end of line
15644 new_selection_ranges.push(line_end..line_end);
15645 }
15646 }
15647
15648 let is_multiline_selection = selection.start.row != selection.end.row;
15649 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15650 // so this action feels more ergonomic when paired with other selection operations
15651 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15652 if !should_skip_last {
15653 if action.keep_selections {
15654 if is_multiline_selection {
15655 let line_start = Point::new(selection.end.row, 0);
15656 new_selection_ranges.push(line_start..selection.end);
15657 } else {
15658 new_selection_ranges.push(selection.start..selection.end);
15659 }
15660 } else {
15661 new_selection_ranges.push(selection.end..selection.end);
15662 }
15663 }
15664 }
15665 }
15666 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15667 s.select_ranges(new_selection_ranges);
15668 });
15669 }
15670
15671 pub fn add_selection_above(
15672 &mut self,
15673 action: &AddSelectionAbove,
15674 window: &mut Window,
15675 cx: &mut Context<Self>,
15676 ) {
15677 self.add_selection(true, action.skip_soft_wrap, window, cx);
15678 }
15679
15680 pub fn add_selection_below(
15681 &mut self,
15682 action: &AddSelectionBelow,
15683 window: &mut Window,
15684 cx: &mut Context<Self>,
15685 ) {
15686 self.add_selection(false, action.skip_soft_wrap, window, cx);
15687 }
15688
15689 fn add_selection(
15690 &mut self,
15691 above: bool,
15692 skip_soft_wrap: bool,
15693 window: &mut Window,
15694 cx: &mut Context<Self>,
15695 ) {
15696 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15697
15698 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15699 let all_selections = self.selections.all::<Point>(&display_map);
15700 let text_layout_details = self.text_layout_details(window, cx);
15701
15702 let (mut columnar_selections, new_selections_to_columnarize) = {
15703 if let Some(state) = self.add_selections_state.as_ref() {
15704 let columnar_selection_ids: HashSet<_> = state
15705 .groups
15706 .iter()
15707 .flat_map(|group| group.stack.iter())
15708 .copied()
15709 .collect();
15710
15711 all_selections
15712 .into_iter()
15713 .partition(|s| columnar_selection_ids.contains(&s.id))
15714 } else {
15715 (Vec::new(), all_selections)
15716 }
15717 };
15718
15719 let mut state = self
15720 .add_selections_state
15721 .take()
15722 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15723
15724 for selection in new_selections_to_columnarize {
15725 let range = selection.display_range(&display_map).sorted();
15726 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15727 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15728 let positions = start_x.min(end_x)..start_x.max(end_x);
15729 let mut stack = Vec::new();
15730 for row in range.start.row().0..=range.end.row().0 {
15731 if let Some(selection) = self.selections.build_columnar_selection(
15732 &display_map,
15733 DisplayRow(row),
15734 &positions,
15735 selection.reversed,
15736 &text_layout_details,
15737 ) {
15738 stack.push(selection.id);
15739 columnar_selections.push(selection);
15740 }
15741 }
15742 if !stack.is_empty() {
15743 if above {
15744 stack.reverse();
15745 }
15746 state.groups.push(AddSelectionsGroup { above, stack });
15747 }
15748 }
15749
15750 let mut final_selections = Vec::new();
15751 let end_row = if above {
15752 DisplayRow(0)
15753 } else {
15754 display_map.max_point().row()
15755 };
15756
15757 // When `skip_soft_wrap` is true, we use UTF-16 columns instead of pixel
15758 // positions to place new selections, so we need to keep track of the
15759 // column range of the oldest selection in each group, because
15760 // intermediate selections may have been clamped to shorter lines.
15761 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15762 let mut map = HashMap::default();
15763 for group in state.groups.iter() {
15764 if let Some(oldest_id) = group.stack.first() {
15765 if let Some(oldest_selection) =
15766 columnar_selections.iter().find(|s| s.id == *oldest_id)
15767 {
15768 let snapshot = display_map.buffer_snapshot();
15769 let start_col =
15770 snapshot.point_to_point_utf16(oldest_selection.start).column;
15771 let end_col = snapshot.point_to_point_utf16(oldest_selection.end).column;
15772 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15773 for id in &group.stack {
15774 map.insert(*id, goal_columns.clone());
15775 }
15776 }
15777 }
15778 }
15779 map
15780 } else {
15781 HashMap::default()
15782 };
15783
15784 let mut last_added_item_per_group = HashMap::default();
15785 for group in state.groups.iter_mut() {
15786 if let Some(last_id) = group.stack.last() {
15787 last_added_item_per_group.insert(*last_id, group);
15788 }
15789 }
15790
15791 for selection in columnar_selections {
15792 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15793 if above == group.above {
15794 let range = selection.display_range(&display_map).sorted();
15795 debug_assert_eq!(range.start.row(), range.end.row());
15796 let row = range.start.row();
15797 let positions =
15798 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15799 Pixels::from(start)..Pixels::from(end)
15800 } else {
15801 let start_x =
15802 display_map.x_for_display_point(range.start, &text_layout_details);
15803 let end_x =
15804 display_map.x_for_display_point(range.end, &text_layout_details);
15805 start_x.min(end_x)..start_x.max(end_x)
15806 };
15807
15808 let maybe_new_selection = if skip_soft_wrap {
15809 let goal_columns = goal_columns_by_selection_id
15810 .remove(&selection.id)
15811 .unwrap_or_else(|| {
15812 let snapshot = display_map.buffer_snapshot();
15813 let start_col =
15814 snapshot.point_to_point_utf16(selection.start).column;
15815 let end_col = snapshot.point_to_point_utf16(selection.end).column;
15816 start_col.min(end_col)..start_col.max(end_col)
15817 });
15818 self.selections.find_next_columnar_selection_by_buffer_row(
15819 &display_map,
15820 row,
15821 end_row,
15822 above,
15823 &goal_columns,
15824 selection.reversed,
15825 &text_layout_details,
15826 )
15827 } else {
15828 self.selections.find_next_columnar_selection_by_display_row(
15829 &display_map,
15830 row,
15831 end_row,
15832 above,
15833 &positions,
15834 selection.reversed,
15835 &text_layout_details,
15836 )
15837 };
15838
15839 if let Some(new_selection) = maybe_new_selection {
15840 group.stack.push(new_selection.id);
15841 if above {
15842 final_selections.push(new_selection);
15843 final_selections.push(selection);
15844 } else {
15845 final_selections.push(selection);
15846 final_selections.push(new_selection);
15847 }
15848 } else {
15849 final_selections.push(selection);
15850 }
15851 } else {
15852 group.stack.pop();
15853 }
15854 } else {
15855 final_selections.push(selection);
15856 }
15857 }
15858
15859 self.change_selections(Default::default(), window, cx, |s| {
15860 s.select(final_selections);
15861 });
15862
15863 let final_selection_ids: HashSet<_> = self
15864 .selections
15865 .all::<Point>(&display_map)
15866 .iter()
15867 .map(|s| s.id)
15868 .collect();
15869 state.groups.retain_mut(|group| {
15870 // selections might get merged above so we remove invalid items from stacks
15871 group.stack.retain(|id| final_selection_ids.contains(id));
15872
15873 // single selection in stack can be treated as initial state
15874 group.stack.len() > 1
15875 });
15876
15877 if !state.groups.is_empty() {
15878 self.add_selections_state = Some(state);
15879 }
15880 }
15881
15882 pub fn insert_snippet_at_selections(
15883 &mut self,
15884 action: &InsertSnippet,
15885 window: &mut Window,
15886 cx: &mut Context<Self>,
15887 ) {
15888 self.try_insert_snippet_at_selections(action, window, cx)
15889 .log_err();
15890 }
15891
15892 fn try_insert_snippet_at_selections(
15893 &mut self,
15894 action: &InsertSnippet,
15895 window: &mut Window,
15896 cx: &mut Context<Self>,
15897 ) -> Result<()> {
15898 let insertion_ranges = self
15899 .selections
15900 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15901 .into_iter()
15902 .map(|selection| selection.range())
15903 .collect_vec();
15904
15905 let snippet = if let Some(snippet_body) = &action.snippet {
15906 if action.language.is_none() && action.name.is_none() {
15907 Snippet::parse(snippet_body)?
15908 } else {
15909 bail!("`snippet` is mutually exclusive with `language` and `name`")
15910 }
15911 } else if let Some(name) = &action.name {
15912 let project = self.project().context("no project")?;
15913 let snippet_store = project.read(cx).snippets().read(cx);
15914 let snippet = snippet_store
15915 .snippets_for(action.language.clone(), cx)
15916 .into_iter()
15917 .find(|snippet| snippet.name == *name)
15918 .context("snippet not found")?;
15919 Snippet::parse(&snippet.body)?
15920 } else {
15921 // todo(andrew): open modal to select snippet
15922 bail!("`name` or `snippet` is required")
15923 };
15924
15925 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15926 }
15927
15928 fn select_match_ranges(
15929 &mut self,
15930 range: Range<MultiBufferOffset>,
15931 reversed: bool,
15932 replace_newest: bool,
15933 auto_scroll: Option<Autoscroll>,
15934 window: &mut Window,
15935 cx: &mut Context<Editor>,
15936 ) {
15937 self.unfold_ranges(
15938 std::slice::from_ref(&range),
15939 false,
15940 auto_scroll.is_some(),
15941 cx,
15942 );
15943 let effects = if let Some(scroll) = auto_scroll {
15944 SelectionEffects::scroll(scroll)
15945 } else {
15946 SelectionEffects::no_scroll()
15947 };
15948 self.change_selections(effects, window, cx, |s| {
15949 if replace_newest {
15950 s.delete(s.newest_anchor().id);
15951 }
15952 if reversed {
15953 s.insert_range(range.end..range.start);
15954 } else {
15955 s.insert_range(range);
15956 }
15957 });
15958 }
15959
15960 pub fn select_next_match_internal(
15961 &mut self,
15962 display_map: &DisplaySnapshot,
15963 replace_newest: bool,
15964 autoscroll: Option<Autoscroll>,
15965 window: &mut Window,
15966 cx: &mut Context<Self>,
15967 ) -> Result<()> {
15968 let buffer = display_map.buffer_snapshot();
15969 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15970 if let Some(mut select_next_state) = self.select_next_state.take() {
15971 let query = &select_next_state.query;
15972 if !select_next_state.done {
15973 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15974 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15975 let mut next_selected_range = None;
15976
15977 let bytes_after_last_selection =
15978 buffer.bytes_in_range(last_selection.end..buffer.len());
15979 let bytes_before_first_selection =
15980 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15981 let query_matches = query
15982 .stream_find_iter(bytes_after_last_selection)
15983 .map(|result| (last_selection.end, result))
15984 .chain(
15985 query
15986 .stream_find_iter(bytes_before_first_selection)
15987 .map(|result| (MultiBufferOffset(0), result)),
15988 );
15989
15990 for (start_offset, query_match) in query_matches {
15991 let query_match = query_match.unwrap(); // can only fail due to I/O
15992 let offset_range =
15993 start_offset + query_match.start()..start_offset + query_match.end();
15994
15995 if !select_next_state.wordwise
15996 || (!buffer.is_inside_word(offset_range.start, None)
15997 && !buffer.is_inside_word(offset_range.end, None))
15998 {
15999 let idx = selections
16000 .partition_point(|selection| selection.end <= offset_range.start);
16001 let overlaps = selections
16002 .get(idx)
16003 .map_or(false, |selection| selection.start < offset_range.end);
16004
16005 if !overlaps {
16006 next_selected_range = Some(offset_range);
16007 break;
16008 }
16009 }
16010 }
16011
16012 if let Some(next_selected_range) = next_selected_range {
16013 self.select_match_ranges(
16014 next_selected_range,
16015 last_selection.reversed,
16016 replace_newest,
16017 autoscroll,
16018 window,
16019 cx,
16020 );
16021 } else {
16022 select_next_state.done = true;
16023 }
16024 }
16025
16026 self.select_next_state = Some(select_next_state);
16027 } else {
16028 let mut only_carets = true;
16029 let mut same_text_selected = true;
16030 let mut selected_text = None;
16031
16032 let mut selections_iter = selections.iter().peekable();
16033 while let Some(selection) = selections_iter.next() {
16034 if selection.start != selection.end {
16035 only_carets = false;
16036 }
16037
16038 if same_text_selected {
16039 if selected_text.is_none() {
16040 selected_text =
16041 Some(buffer.text_for_range(selection.range()).collect::<String>());
16042 }
16043
16044 if let Some(next_selection) = selections_iter.peek() {
16045 if next_selection.len() == selection.len() {
16046 let next_selected_text = buffer
16047 .text_for_range(next_selection.range())
16048 .collect::<String>();
16049 if Some(next_selected_text) != selected_text {
16050 same_text_selected = false;
16051 selected_text = None;
16052 }
16053 } else {
16054 same_text_selected = false;
16055 selected_text = None;
16056 }
16057 }
16058 }
16059 }
16060
16061 if only_carets {
16062 for selection in &mut selections {
16063 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16064 selection.start = word_range.start;
16065 selection.end = word_range.end;
16066 selection.goal = SelectionGoal::None;
16067 selection.reversed = false;
16068 self.select_match_ranges(
16069 selection.start..selection.end,
16070 selection.reversed,
16071 replace_newest,
16072 autoscroll,
16073 window,
16074 cx,
16075 );
16076 }
16077
16078 if selections.len() == 1 {
16079 let selection = selections
16080 .last()
16081 .expect("ensured that there's only one selection");
16082 let query = buffer
16083 .text_for_range(selection.start..selection.end)
16084 .collect::<String>();
16085 let is_empty = query.is_empty();
16086 let select_state = SelectNextState {
16087 query: self.build_query(&[query], cx)?,
16088 wordwise: true,
16089 done: is_empty,
16090 };
16091 self.select_next_state = Some(select_state);
16092 } else {
16093 self.select_next_state = None;
16094 }
16095 } else if let Some(selected_text) = selected_text {
16096 self.select_next_state = Some(SelectNextState {
16097 query: self.build_query(&[selected_text], cx)?,
16098 wordwise: false,
16099 done: false,
16100 });
16101 self.select_next_match_internal(
16102 display_map,
16103 replace_newest,
16104 autoscroll,
16105 window,
16106 cx,
16107 )?;
16108 }
16109 }
16110 Ok(())
16111 }
16112
16113 pub fn select_all_matches(
16114 &mut self,
16115 _action: &SelectAllMatches,
16116 window: &mut Window,
16117 cx: &mut Context<Self>,
16118 ) -> Result<()> {
16119 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16120
16121 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16122
16123 self.select_next_match_internal(&display_map, false, None, window, cx)?;
16124 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
16125 else {
16126 return Ok(());
16127 };
16128
16129 let mut new_selections = Vec::new();
16130 let initial_selection = self.selections.oldest::<MultiBufferOffset>(&display_map);
16131 let reversed = initial_selection.reversed;
16132 let buffer = display_map.buffer_snapshot();
16133 let query_matches = select_next_state
16134 .query
16135 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
16136
16137 for query_match in query_matches.into_iter() {
16138 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
16139 let offset_range = if reversed {
16140 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
16141 } else {
16142 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
16143 };
16144
16145 let is_partial_word_match = select_next_state.wordwise
16146 && (buffer.is_inside_word(offset_range.start, None)
16147 || buffer.is_inside_word(offset_range.end, None));
16148
16149 let is_initial_selection = MultiBufferOffset(query_match.start())
16150 == initial_selection.start
16151 && MultiBufferOffset(query_match.end()) == initial_selection.end;
16152
16153 if !is_partial_word_match && !is_initial_selection {
16154 new_selections.push(offset_range);
16155 }
16156 }
16157
16158 // Ensure that the initial range is the last selection, as
16159 // `MutableSelectionsCollection::select_ranges` makes the last selection
16160 // the newest selection, which the editor then relies on as the primary
16161 // cursor for scroll targeting. Without this, the last match would then
16162 // be automatically focused when the user started editing the selected
16163 // matches.
16164 let initial_directed_range = if reversed {
16165 initial_selection.end..initial_selection.start
16166 } else {
16167 initial_selection.start..initial_selection.end
16168 };
16169 new_selections.push(initial_directed_range);
16170
16171 select_next_state.done = true;
16172 self.unfold_ranges(&new_selections, false, false, cx);
16173 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16174 selections.select_ranges(new_selections)
16175 });
16176
16177 Ok(())
16178 }
16179
16180 pub fn select_next(
16181 &mut self,
16182 action: &SelectNext,
16183 window: &mut Window,
16184 cx: &mut Context<Self>,
16185 ) -> Result<()> {
16186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16187 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16188 self.select_next_match_internal(
16189 &display_map,
16190 action.replace_newest,
16191 Some(Autoscroll::newest()),
16192 window,
16193 cx,
16194 )
16195 }
16196
16197 pub fn select_previous(
16198 &mut self,
16199 action: &SelectPrevious,
16200 window: &mut Window,
16201 cx: &mut Context<Self>,
16202 ) -> Result<()> {
16203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16205 let buffer = display_map.buffer_snapshot();
16206 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16207 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16208 let query = &select_prev_state.query;
16209 if !select_prev_state.done {
16210 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16211 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16212 let mut next_selected_range = None;
16213 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16214 let bytes_before_last_selection =
16215 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16216 let bytes_after_first_selection =
16217 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16218 let query_matches = query
16219 .stream_find_iter(bytes_before_last_selection)
16220 .map(|result| (last_selection.start, result))
16221 .chain(
16222 query
16223 .stream_find_iter(bytes_after_first_selection)
16224 .map(|result| (buffer.len(), result)),
16225 );
16226 for (end_offset, query_match) in query_matches {
16227 let query_match = query_match.unwrap(); // can only fail due to I/O
16228 let offset_range =
16229 end_offset - query_match.end()..end_offset - query_match.start();
16230
16231 if !select_prev_state.wordwise
16232 || (!buffer.is_inside_word(offset_range.start, None)
16233 && !buffer.is_inside_word(offset_range.end, None))
16234 {
16235 next_selected_range = Some(offset_range);
16236 break;
16237 }
16238 }
16239
16240 if let Some(next_selected_range) = next_selected_range {
16241 self.select_match_ranges(
16242 next_selected_range,
16243 last_selection.reversed,
16244 action.replace_newest,
16245 Some(Autoscroll::newest()),
16246 window,
16247 cx,
16248 );
16249 } else {
16250 select_prev_state.done = true;
16251 }
16252 }
16253
16254 self.select_prev_state = Some(select_prev_state);
16255 } else {
16256 let mut only_carets = true;
16257 let mut same_text_selected = true;
16258 let mut selected_text = None;
16259
16260 let mut selections_iter = selections.iter().peekable();
16261 while let Some(selection) = selections_iter.next() {
16262 if selection.start != selection.end {
16263 only_carets = false;
16264 }
16265
16266 if same_text_selected {
16267 if selected_text.is_none() {
16268 selected_text =
16269 Some(buffer.text_for_range(selection.range()).collect::<String>());
16270 }
16271
16272 if let Some(next_selection) = selections_iter.peek() {
16273 if next_selection.len() == selection.len() {
16274 let next_selected_text = buffer
16275 .text_for_range(next_selection.range())
16276 .collect::<String>();
16277 if Some(next_selected_text) != selected_text {
16278 same_text_selected = false;
16279 selected_text = None;
16280 }
16281 } else {
16282 same_text_selected = false;
16283 selected_text = None;
16284 }
16285 }
16286 }
16287 }
16288
16289 if only_carets {
16290 for selection in &mut selections {
16291 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16292 selection.start = word_range.start;
16293 selection.end = word_range.end;
16294 selection.goal = SelectionGoal::None;
16295 selection.reversed = false;
16296 self.select_match_ranges(
16297 selection.start..selection.end,
16298 selection.reversed,
16299 action.replace_newest,
16300 Some(Autoscroll::newest()),
16301 window,
16302 cx,
16303 );
16304 }
16305 if selections.len() == 1 {
16306 let selection = selections
16307 .last()
16308 .expect("ensured that there's only one selection");
16309 let query = buffer
16310 .text_for_range(selection.start..selection.end)
16311 .collect::<String>();
16312 let is_empty = query.is_empty();
16313 let select_state = SelectNextState {
16314 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16315 wordwise: true,
16316 done: is_empty,
16317 };
16318 self.select_prev_state = Some(select_state);
16319 } else {
16320 self.select_prev_state = None;
16321 }
16322 } else if let Some(selected_text) = selected_text {
16323 self.select_prev_state = Some(SelectNextState {
16324 query: self
16325 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16326 wordwise: false,
16327 done: false,
16328 });
16329 self.select_previous(action, window, cx)?;
16330 }
16331 }
16332 Ok(())
16333 }
16334
16335 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16336 /// setting the case sensitivity based on the global
16337 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16338 /// editor's settings.
16339 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16340 where
16341 I: IntoIterator<Item = P>,
16342 P: AsRef<[u8]>,
16343 {
16344 let case_sensitive = self
16345 .select_next_is_case_sensitive
16346 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16347
16348 let mut builder = AhoCorasickBuilder::new();
16349 builder.ascii_case_insensitive(!case_sensitive);
16350 builder.build(patterns)
16351 }
16352
16353 pub fn find_next_match(
16354 &mut self,
16355 _: &FindNextMatch,
16356 window: &mut Window,
16357 cx: &mut Context<Self>,
16358 ) -> Result<()> {
16359 let selections = self.selections.disjoint_anchors_arc();
16360 match selections.first() {
16361 Some(first) if selections.len() >= 2 => {
16362 self.change_selections(Default::default(), window, cx, |s| {
16363 s.select_ranges([first.range()]);
16364 });
16365 }
16366 _ => self.select_next(
16367 &SelectNext {
16368 replace_newest: true,
16369 },
16370 window,
16371 cx,
16372 )?,
16373 }
16374 Ok(())
16375 }
16376
16377 pub fn find_previous_match(
16378 &mut self,
16379 _: &FindPreviousMatch,
16380 window: &mut Window,
16381 cx: &mut Context<Self>,
16382 ) -> Result<()> {
16383 let selections = self.selections.disjoint_anchors_arc();
16384 match selections.last() {
16385 Some(last) if selections.len() >= 2 => {
16386 self.change_selections(Default::default(), window, cx, |s| {
16387 s.select_ranges([last.range()]);
16388 });
16389 }
16390 _ => self.select_previous(
16391 &SelectPrevious {
16392 replace_newest: true,
16393 },
16394 window,
16395 cx,
16396 )?,
16397 }
16398 Ok(())
16399 }
16400
16401 pub fn toggle_comments(
16402 &mut self,
16403 action: &ToggleComments,
16404 window: &mut Window,
16405 cx: &mut Context<Self>,
16406 ) {
16407 if self.read_only(cx) {
16408 return;
16409 }
16410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16411 let text_layout_details = &self.text_layout_details(window, cx);
16412 self.transact(window, cx, |this, window, cx| {
16413 let mut selections = this
16414 .selections
16415 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16416 let mut edits = Vec::new();
16417 let mut selection_edit_ranges = Vec::new();
16418 let mut last_toggled_row = None;
16419 let snapshot = this.buffer.read(cx).read(cx);
16420 let empty_str: Arc<str> = Arc::default();
16421 let mut suffixes_inserted = Vec::new();
16422 let ignore_indent = action.ignore_indent;
16423
16424 fn comment_prefix_range(
16425 snapshot: &MultiBufferSnapshot,
16426 row: MultiBufferRow,
16427 comment_prefix: &str,
16428 comment_prefix_whitespace: &str,
16429 ignore_indent: bool,
16430 ) -> Range<Point> {
16431 let indent_size = if ignore_indent {
16432 0
16433 } else {
16434 snapshot.indent_size_for_line(row).len
16435 };
16436
16437 let start = Point::new(row.0, indent_size);
16438
16439 let mut line_bytes = snapshot
16440 .bytes_in_range(start..snapshot.max_point())
16441 .flatten()
16442 .copied();
16443
16444 // If this line currently begins with the line comment prefix, then record
16445 // the range containing the prefix.
16446 if line_bytes
16447 .by_ref()
16448 .take(comment_prefix.len())
16449 .eq(comment_prefix.bytes())
16450 {
16451 // Include any whitespace that matches the comment prefix.
16452 let matching_whitespace_len = line_bytes
16453 .zip(comment_prefix_whitespace.bytes())
16454 .take_while(|(a, b)| a == b)
16455 .count() as u32;
16456 let end = Point::new(
16457 start.row,
16458 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16459 );
16460 start..end
16461 } else {
16462 start..start
16463 }
16464 }
16465
16466 fn comment_suffix_range(
16467 snapshot: &MultiBufferSnapshot,
16468 row: MultiBufferRow,
16469 comment_suffix: &str,
16470 comment_suffix_has_leading_space: bool,
16471 ) -> Range<Point> {
16472 let end = Point::new(row.0, snapshot.line_len(row));
16473 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16474
16475 let mut line_end_bytes = snapshot
16476 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16477 .flatten()
16478 .copied();
16479
16480 let leading_space_len = if suffix_start_column > 0
16481 && line_end_bytes.next() == Some(b' ')
16482 && comment_suffix_has_leading_space
16483 {
16484 1
16485 } else {
16486 0
16487 };
16488
16489 // If this line currently begins with the line comment prefix, then record
16490 // the range containing the prefix.
16491 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16492 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16493 start..end
16494 } else {
16495 end..end
16496 }
16497 }
16498
16499 // TODO: Handle selections that cross excerpts
16500 for selection in &mut selections {
16501 let start_column = snapshot
16502 .indent_size_for_line(MultiBufferRow(selection.start.row))
16503 .len;
16504 let language = if let Some(language) =
16505 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16506 {
16507 language
16508 } else {
16509 continue;
16510 };
16511
16512 selection_edit_ranges.clear();
16513
16514 // If multiple selections contain a given row, avoid processing that
16515 // row more than once.
16516 let mut start_row = MultiBufferRow(selection.start.row);
16517 if last_toggled_row == Some(start_row) {
16518 start_row = start_row.next_row();
16519 }
16520 let end_row =
16521 if selection.end.row > selection.start.row && selection.end.column == 0 {
16522 MultiBufferRow(selection.end.row - 1)
16523 } else {
16524 MultiBufferRow(selection.end.row)
16525 };
16526 last_toggled_row = Some(end_row);
16527
16528 if start_row > end_row {
16529 continue;
16530 }
16531
16532 // If the language has line comments, toggle those.
16533 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16534
16535 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16536 if ignore_indent {
16537 full_comment_prefixes = full_comment_prefixes
16538 .into_iter()
16539 .map(|s| Arc::from(s.trim_end()))
16540 .collect();
16541 }
16542
16543 if !full_comment_prefixes.is_empty() {
16544 let first_prefix = full_comment_prefixes
16545 .first()
16546 .expect("prefixes is non-empty");
16547 let prefix_trimmed_lengths = full_comment_prefixes
16548 .iter()
16549 .map(|p| p.trim_end_matches(' ').len())
16550 .collect::<SmallVec<[usize; 4]>>();
16551
16552 let mut all_selection_lines_are_comments = true;
16553
16554 for row in start_row.0..=end_row.0 {
16555 let row = MultiBufferRow(row);
16556 if start_row < end_row && snapshot.is_line_blank(row) {
16557 continue;
16558 }
16559
16560 let prefix_range = full_comment_prefixes
16561 .iter()
16562 .zip(prefix_trimmed_lengths.iter().copied())
16563 .map(|(prefix, trimmed_prefix_len)| {
16564 comment_prefix_range(
16565 snapshot.deref(),
16566 row,
16567 &prefix[..trimmed_prefix_len],
16568 &prefix[trimmed_prefix_len..],
16569 ignore_indent,
16570 )
16571 })
16572 .max_by_key(|range| range.end.column - range.start.column)
16573 .expect("prefixes is non-empty");
16574
16575 if prefix_range.is_empty() {
16576 all_selection_lines_are_comments = false;
16577 }
16578
16579 selection_edit_ranges.push(prefix_range);
16580 }
16581
16582 if all_selection_lines_are_comments {
16583 edits.extend(
16584 selection_edit_ranges
16585 .iter()
16586 .cloned()
16587 .map(|range| (range, empty_str.clone())),
16588 );
16589 } else {
16590 let min_column = selection_edit_ranges
16591 .iter()
16592 .map(|range| range.start.column)
16593 .min()
16594 .unwrap_or(0);
16595 edits.extend(selection_edit_ranges.iter().map(|range| {
16596 let position = Point::new(range.start.row, min_column);
16597 (position..position, first_prefix.clone())
16598 }));
16599 }
16600 } else if let Some(BlockCommentConfig {
16601 start: full_comment_prefix,
16602 end: comment_suffix,
16603 ..
16604 }) = language.block_comment()
16605 {
16606 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16607 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16608 let prefix_range = comment_prefix_range(
16609 snapshot.deref(),
16610 start_row,
16611 comment_prefix,
16612 comment_prefix_whitespace,
16613 ignore_indent,
16614 );
16615 let suffix_range = comment_suffix_range(
16616 snapshot.deref(),
16617 end_row,
16618 comment_suffix.trim_start_matches(' '),
16619 comment_suffix.starts_with(' '),
16620 );
16621
16622 if prefix_range.is_empty() || suffix_range.is_empty() {
16623 edits.push((
16624 prefix_range.start..prefix_range.start,
16625 full_comment_prefix.clone(),
16626 ));
16627 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16628 suffixes_inserted.push((end_row, comment_suffix.len()));
16629 } else {
16630 edits.push((prefix_range, empty_str.clone()));
16631 edits.push((suffix_range, empty_str.clone()));
16632 }
16633 } else {
16634 continue;
16635 }
16636 }
16637
16638 drop(snapshot);
16639 this.buffer.update(cx, |buffer, cx| {
16640 buffer.edit(edits, None, cx);
16641 });
16642
16643 // Adjust selections so that they end before any comment suffixes that
16644 // were inserted.
16645 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16646 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16647 let snapshot = this.buffer.read(cx).read(cx);
16648 for selection in &mut selections {
16649 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16650 match row.cmp(&MultiBufferRow(selection.end.row)) {
16651 Ordering::Less => {
16652 suffixes_inserted.next();
16653 continue;
16654 }
16655 Ordering::Greater => break,
16656 Ordering::Equal => {
16657 if selection.end.column == snapshot.line_len(row) {
16658 if selection.is_empty() {
16659 selection.start.column -= suffix_len as u32;
16660 }
16661 selection.end.column -= suffix_len as u32;
16662 }
16663 break;
16664 }
16665 }
16666 }
16667 }
16668
16669 drop(snapshot);
16670 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16671
16672 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16673 let selections_on_single_row = selections.windows(2).all(|selections| {
16674 selections[0].start.row == selections[1].start.row
16675 && selections[0].end.row == selections[1].end.row
16676 && selections[0].start.row == selections[0].end.row
16677 });
16678 let selections_selecting = selections
16679 .iter()
16680 .any(|selection| selection.start != selection.end);
16681 let advance_downwards = action.advance_downwards
16682 && selections_on_single_row
16683 && !selections_selecting
16684 && !matches!(this.mode, EditorMode::SingleLine);
16685
16686 if advance_downwards {
16687 let snapshot = this.buffer.read(cx).snapshot(cx);
16688
16689 this.change_selections(Default::default(), window, cx, |s| {
16690 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16691 let mut point = display_point.to_point(display_snapshot);
16692 point.row += 1;
16693 point = snapshot.clip_point(point, Bias::Left);
16694 let display_point = point.to_display_point(display_snapshot);
16695 let goal = SelectionGoal::HorizontalPosition(
16696 display_snapshot
16697 .x_for_display_point(display_point, text_layout_details)
16698 .into(),
16699 );
16700 (display_point, goal)
16701 })
16702 });
16703 }
16704 });
16705 }
16706
16707 pub fn select_enclosing_symbol(
16708 &mut self,
16709 _: &SelectEnclosingSymbol,
16710 window: &mut Window,
16711 cx: &mut Context<Self>,
16712 ) {
16713 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16714
16715 let buffer = self.buffer.read(cx).snapshot(cx);
16716 let old_selections = self
16717 .selections
16718 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16719 .into_boxed_slice();
16720
16721 fn update_selection(
16722 selection: &Selection<MultiBufferOffset>,
16723 buffer_snap: &MultiBufferSnapshot,
16724 ) -> Option<Selection<MultiBufferOffset>> {
16725 let cursor = selection.head();
16726 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16727 for symbol in symbols.iter().rev() {
16728 let start = symbol.range.start.to_offset(buffer_snap);
16729 let end = symbol.range.end.to_offset(buffer_snap);
16730 let new_range = start..end;
16731 if start < selection.start || end > selection.end {
16732 return Some(Selection {
16733 id: selection.id,
16734 start: new_range.start,
16735 end: new_range.end,
16736 goal: SelectionGoal::None,
16737 reversed: selection.reversed,
16738 });
16739 }
16740 }
16741 None
16742 }
16743
16744 let mut selected_larger_symbol = false;
16745 let new_selections = old_selections
16746 .iter()
16747 .map(|selection| match update_selection(selection, &buffer) {
16748 Some(new_selection) => {
16749 if new_selection.range() != selection.range() {
16750 selected_larger_symbol = true;
16751 }
16752 new_selection
16753 }
16754 None => selection.clone(),
16755 })
16756 .collect::<Vec<_>>();
16757
16758 if selected_larger_symbol {
16759 self.change_selections(Default::default(), window, cx, |s| {
16760 s.select(new_selections);
16761 });
16762 }
16763 }
16764
16765 pub fn select_larger_syntax_node(
16766 &mut self,
16767 _: &SelectLargerSyntaxNode,
16768 window: &mut Window,
16769 cx: &mut Context<Self>,
16770 ) {
16771 let Some(visible_row_count) = self.visible_row_count() else {
16772 return;
16773 };
16774 let old_selections: Box<[_]> = self
16775 .selections
16776 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16777 .into();
16778 if old_selections.is_empty() {
16779 return;
16780 }
16781
16782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16783
16784 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16785 let buffer = self.buffer.read(cx).snapshot(cx);
16786
16787 let mut selected_larger_node = false;
16788 let mut new_selections = old_selections
16789 .iter()
16790 .map(|selection| {
16791 let old_range = selection.start..selection.end;
16792
16793 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16794 // manually select word at selection
16795 if ["string_content", "inline"].contains(&node.kind()) {
16796 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16797 // ignore if word is already selected
16798 if !word_range.is_empty() && old_range != word_range {
16799 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16800 // only select word if start and end point belongs to same word
16801 if word_range == last_word_range {
16802 selected_larger_node = true;
16803 return Selection {
16804 id: selection.id,
16805 start: word_range.start,
16806 end: word_range.end,
16807 goal: SelectionGoal::None,
16808 reversed: selection.reversed,
16809 };
16810 }
16811 }
16812 }
16813 }
16814
16815 let mut new_range = old_range.clone();
16816 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16817 new_range = range;
16818 if !node.is_named() {
16819 continue;
16820 }
16821 if !display_map.intersects_fold(new_range.start)
16822 && !display_map.intersects_fold(new_range.end)
16823 {
16824 break;
16825 }
16826 }
16827
16828 selected_larger_node |= new_range != old_range;
16829 Selection {
16830 id: selection.id,
16831 start: new_range.start,
16832 end: new_range.end,
16833 goal: SelectionGoal::None,
16834 reversed: selection.reversed,
16835 }
16836 })
16837 .collect::<Vec<_>>();
16838
16839 if !selected_larger_node {
16840 return; // don't put this call in the history
16841 }
16842
16843 // scroll based on transformation done to the last selection created by the user
16844 let (last_old, last_new) = old_selections
16845 .last()
16846 .zip(new_selections.last().cloned())
16847 .expect("old_selections isn't empty");
16848
16849 let is_selection_reversed = if new_selections.len() == 1 {
16850 let should_be_reversed = last_old.start != last_new.start;
16851 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
16852 should_be_reversed
16853 } else {
16854 last_new.reversed
16855 };
16856
16857 if selected_larger_node {
16858 self.select_syntax_node_history.disable_clearing = true;
16859 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16860 s.select(new_selections.clone());
16861 });
16862 self.select_syntax_node_history.disable_clearing = false;
16863 }
16864
16865 let start_row = last_new.start.to_display_point(&display_map).row().0;
16866 let end_row = last_new.end.to_display_point(&display_map).row().0;
16867 let selection_height = end_row - start_row + 1;
16868 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16869
16870 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16871 let scroll_behavior = if fits_on_the_screen {
16872 self.request_autoscroll(Autoscroll::fit(), cx);
16873 SelectSyntaxNodeScrollBehavior::FitSelection
16874 } else if is_selection_reversed {
16875 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16876 SelectSyntaxNodeScrollBehavior::CursorTop
16877 } else {
16878 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16879 SelectSyntaxNodeScrollBehavior::CursorBottom
16880 };
16881
16882 let old_selections: Box<[Selection<Anchor>]> = old_selections
16883 .iter()
16884 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16885 .collect();
16886 self.select_syntax_node_history.push((
16887 old_selections,
16888 scroll_behavior,
16889 is_selection_reversed,
16890 ));
16891 }
16892
16893 pub fn select_smaller_syntax_node(
16894 &mut self,
16895 _: &SelectSmallerSyntaxNode,
16896 window: &mut Window,
16897 cx: &mut Context<Self>,
16898 ) {
16899 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16900
16901 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16902 self.select_syntax_node_history.pop()
16903 {
16904 if let Some(selection) = selections.last_mut() {
16905 selection.reversed = is_selection_reversed;
16906 }
16907
16908 let snapshot = self.buffer.read(cx).snapshot(cx);
16909 let selections: Vec<Selection<MultiBufferOffset>> = selections
16910 .iter()
16911 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16912 .collect();
16913
16914 self.select_syntax_node_history.disable_clearing = true;
16915 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16916 s.select(selections);
16917 });
16918 self.select_syntax_node_history.disable_clearing = false;
16919
16920 match scroll_behavior {
16921 SelectSyntaxNodeScrollBehavior::CursorTop => {
16922 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16923 }
16924 SelectSyntaxNodeScrollBehavior::FitSelection => {
16925 self.request_autoscroll(Autoscroll::fit(), cx);
16926 }
16927 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16928 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16929 }
16930 }
16931 }
16932 }
16933
16934 pub fn unwrap_syntax_node(
16935 &mut self,
16936 _: &UnwrapSyntaxNode,
16937 window: &mut Window,
16938 cx: &mut Context<Self>,
16939 ) {
16940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16941
16942 let buffer = self.buffer.read(cx).snapshot(cx);
16943 let selections = self
16944 .selections
16945 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16946 .into_iter()
16947 // subtracting the offset requires sorting
16948 .sorted_by_key(|i| i.start);
16949
16950 let full_edits = selections
16951 .into_iter()
16952 .filter_map(|selection| {
16953 let child = if selection.is_empty()
16954 && let Some((_, ancestor_range)) =
16955 buffer.syntax_ancestor(selection.start..selection.end)
16956 {
16957 ancestor_range
16958 } else {
16959 selection.range()
16960 };
16961
16962 let mut parent = child.clone();
16963 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16964 parent = ancestor_range;
16965 if parent.start < child.start || parent.end > child.end {
16966 break;
16967 }
16968 }
16969
16970 if parent == child {
16971 return None;
16972 }
16973 let text = buffer.text_for_range(child).collect::<String>();
16974 Some((selection.id, parent, text))
16975 })
16976 .collect::<Vec<_>>();
16977 if full_edits.is_empty() {
16978 return;
16979 }
16980
16981 self.transact(window, cx, |this, window, cx| {
16982 this.buffer.update(cx, |buffer, cx| {
16983 buffer.edit(
16984 full_edits
16985 .iter()
16986 .map(|(_, p, t)| (p.clone(), t.clone()))
16987 .collect::<Vec<_>>(),
16988 None,
16989 cx,
16990 );
16991 });
16992 this.change_selections(Default::default(), window, cx, |s| {
16993 let mut offset = 0;
16994 let mut selections = vec![];
16995 for (id, parent, text) in full_edits {
16996 let start = parent.start - offset;
16997 offset += (parent.end - parent.start) - text.len();
16998 selections.push(Selection {
16999 id,
17000 start,
17001 end: start + text.len(),
17002 reversed: false,
17003 goal: Default::default(),
17004 });
17005 }
17006 s.select(selections);
17007 });
17008 });
17009 }
17010
17011 pub fn select_next_syntax_node(
17012 &mut self,
17013 _: &SelectNextSyntaxNode,
17014 window: &mut Window,
17015 cx: &mut Context<Self>,
17016 ) {
17017 let old_selections: Box<[_]> = self
17018 .selections
17019 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17020 .into();
17021 if old_selections.is_empty() {
17022 return;
17023 }
17024
17025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17026
17027 let buffer = self.buffer.read(cx).snapshot(cx);
17028 let mut selected_sibling = false;
17029
17030 let new_selections = old_selections
17031 .iter()
17032 .map(|selection| {
17033 let old_range = selection.start..selection.end;
17034
17035 let old_range =
17036 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17037 let excerpt = buffer.excerpt_containing(old_range.clone());
17038
17039 if let Some(mut excerpt) = excerpt
17040 && let Some(node) = excerpt
17041 .buffer()
17042 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
17043 {
17044 let new_range = excerpt.map_range_from_buffer(
17045 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17046 );
17047 selected_sibling = true;
17048 Selection {
17049 id: selection.id,
17050 start: new_range.start,
17051 end: new_range.end,
17052 goal: SelectionGoal::None,
17053 reversed: selection.reversed,
17054 }
17055 } else {
17056 selection.clone()
17057 }
17058 })
17059 .collect::<Vec<_>>();
17060
17061 if selected_sibling {
17062 self.change_selections(
17063 SelectionEffects::scroll(Autoscroll::fit()),
17064 window,
17065 cx,
17066 |s| {
17067 s.select(new_selections);
17068 },
17069 );
17070 }
17071 }
17072
17073 pub fn select_prev_syntax_node(
17074 &mut self,
17075 _: &SelectPreviousSyntaxNode,
17076 window: &mut Window,
17077 cx: &mut Context<Self>,
17078 ) {
17079 let old_selections: Box<[_]> = self
17080 .selections
17081 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17082 .into();
17083 if old_selections.is_empty() {
17084 return;
17085 }
17086
17087 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17088
17089 let buffer = self.buffer.read(cx).snapshot(cx);
17090 let mut selected_sibling = false;
17091
17092 let new_selections = old_selections
17093 .iter()
17094 .map(|selection| {
17095 let old_range = selection.start..selection.end;
17096 let old_range =
17097 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17098 let excerpt = buffer.excerpt_containing(old_range.clone());
17099
17100 if let Some(mut excerpt) = excerpt
17101 && let Some(node) = excerpt
17102 .buffer()
17103 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
17104 {
17105 let new_range = excerpt.map_range_from_buffer(
17106 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17107 );
17108 selected_sibling = true;
17109 Selection {
17110 id: selection.id,
17111 start: new_range.start,
17112 end: new_range.end,
17113 goal: SelectionGoal::None,
17114 reversed: selection.reversed,
17115 }
17116 } else {
17117 selection.clone()
17118 }
17119 })
17120 .collect::<Vec<_>>();
17121
17122 if selected_sibling {
17123 self.change_selections(
17124 SelectionEffects::scroll(Autoscroll::fit()),
17125 window,
17126 cx,
17127 |s| {
17128 s.select(new_selections);
17129 },
17130 );
17131 }
17132 }
17133
17134 pub fn move_to_start_of_larger_syntax_node(
17135 &mut self,
17136 _: &MoveToStartOfLargerSyntaxNode,
17137 window: &mut Window,
17138 cx: &mut Context<Self>,
17139 ) {
17140 self.move_cursors_to_syntax_nodes(window, cx, false);
17141 }
17142
17143 pub fn move_to_end_of_larger_syntax_node(
17144 &mut self,
17145 _: &MoveToEndOfLargerSyntaxNode,
17146 window: &mut Window,
17147 cx: &mut Context<Self>,
17148 ) {
17149 self.move_cursors_to_syntax_nodes(window, cx, true);
17150 }
17151
17152 fn find_syntax_node_boundary(
17153 &self,
17154 selection_pos: MultiBufferOffset,
17155 move_to_end: bool,
17156 display_map: &DisplaySnapshot,
17157 buffer: &MultiBufferSnapshot,
17158 ) -> MultiBufferOffset {
17159 let old_range = selection_pos..selection_pos;
17160 let mut new_pos = selection_pos;
17161 let mut search_range = old_range;
17162 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17163 search_range = range.clone();
17164 if !node.is_named()
17165 || display_map.intersects_fold(range.start)
17166 || display_map.intersects_fold(range.end)
17167 // If cursor is already at the end of the syntax node, continue searching
17168 || (move_to_end && range.end == selection_pos)
17169 // If cursor is already at the start of the syntax node, continue searching
17170 || (!move_to_end && range.start == selection_pos)
17171 {
17172 continue;
17173 }
17174
17175 // If we found a string_content node, find the largest parent that is still string_content
17176 // Enables us to skip to the end of strings without taking multiple steps inside the string
17177 let (_, final_range) = if node.kind() == "string_content" {
17178 let mut current_node = node;
17179 let mut current_range = range;
17180 while let Some((parent, parent_range)) =
17181 buffer.syntax_ancestor(current_range.clone())
17182 {
17183 if parent.kind() == "string_content" {
17184 current_node = parent;
17185 current_range = parent_range;
17186 } else {
17187 break;
17188 }
17189 }
17190
17191 (current_node, current_range)
17192 } else {
17193 (node, range)
17194 };
17195
17196 new_pos = if move_to_end {
17197 final_range.end
17198 } else {
17199 final_range.start
17200 };
17201
17202 break;
17203 }
17204
17205 new_pos
17206 }
17207
17208 fn move_cursors_to_syntax_nodes(
17209 &mut self,
17210 window: &mut Window,
17211 cx: &mut Context<Self>,
17212 move_to_end: bool,
17213 ) -> bool {
17214 let old_selections: Box<[_]> = self
17215 .selections
17216 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17217 .into();
17218 if old_selections.is_empty() {
17219 return false;
17220 }
17221
17222 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17223
17224 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17225 let buffer = self.buffer.read(cx).snapshot(cx);
17226
17227 let mut any_cursor_moved = false;
17228 let new_selections = old_selections
17229 .iter()
17230 .map(|selection| {
17231 if !selection.is_empty() {
17232 return selection.clone();
17233 }
17234
17235 let selection_pos = selection.head();
17236 let new_pos = self.find_syntax_node_boundary(
17237 selection_pos,
17238 move_to_end,
17239 &display_map,
17240 &buffer,
17241 );
17242
17243 any_cursor_moved |= new_pos != selection_pos;
17244
17245 Selection {
17246 id: selection.id,
17247 start: new_pos,
17248 end: new_pos,
17249 goal: SelectionGoal::None,
17250 reversed: false,
17251 }
17252 })
17253 .collect::<Vec<_>>();
17254
17255 self.change_selections(Default::default(), window, cx, |s| {
17256 s.select(new_selections);
17257 });
17258 self.request_autoscroll(Autoscroll::newest(), cx);
17259
17260 any_cursor_moved
17261 }
17262
17263 pub fn select_to_start_of_larger_syntax_node(
17264 &mut self,
17265 _: &SelectToStartOfLargerSyntaxNode,
17266 window: &mut Window,
17267 cx: &mut Context<Self>,
17268 ) {
17269 self.select_to_syntax_nodes(window, cx, false);
17270 }
17271
17272 pub fn select_to_end_of_larger_syntax_node(
17273 &mut self,
17274 _: &SelectToEndOfLargerSyntaxNode,
17275 window: &mut Window,
17276 cx: &mut Context<Self>,
17277 ) {
17278 self.select_to_syntax_nodes(window, cx, true);
17279 }
17280
17281 fn select_to_syntax_nodes(
17282 &mut self,
17283 window: &mut Window,
17284 cx: &mut Context<Self>,
17285 move_to_end: bool,
17286 ) {
17287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17288
17289 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17290 let buffer = self.buffer.read(cx).snapshot(cx);
17291 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17292
17293 let new_selections = old_selections
17294 .iter()
17295 .map(|selection| {
17296 let new_pos = self.find_syntax_node_boundary(
17297 selection.head(),
17298 move_to_end,
17299 &display_map,
17300 &buffer,
17301 );
17302
17303 let mut new_selection = selection.clone();
17304 new_selection.set_head(new_pos, SelectionGoal::None);
17305 new_selection
17306 })
17307 .collect::<Vec<_>>();
17308
17309 self.change_selections(Default::default(), window, cx, |s| {
17310 s.select(new_selections);
17311 });
17312 }
17313
17314 pub fn move_to_enclosing_bracket(
17315 &mut self,
17316 _: &MoveToEnclosingBracket,
17317 window: &mut Window,
17318 cx: &mut Context<Self>,
17319 ) {
17320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17321 self.change_selections(Default::default(), window, cx, |s| {
17322 s.move_offsets_with(&mut |snapshot, selection| {
17323 let Some(enclosing_bracket_ranges) =
17324 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17325 else {
17326 return;
17327 };
17328
17329 let mut best_length = usize::MAX;
17330 let mut best_inside = false;
17331 let mut best_in_bracket_range = false;
17332 let mut best_destination = None;
17333 for (open, close) in enclosing_bracket_ranges {
17334 let close = close.to_inclusive();
17335 let length = *close.end() - open.start;
17336 let inside = selection.start >= open.end && selection.end <= *close.start();
17337 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17338 || close.contains(&selection.head());
17339
17340 // If best is next to a bracket and current isn't, skip
17341 if !in_bracket_range && best_in_bracket_range {
17342 continue;
17343 }
17344
17345 // Prefer smaller lengths unless best is inside and current isn't
17346 if length > best_length && (best_inside || !inside) {
17347 continue;
17348 }
17349
17350 best_length = length;
17351 best_inside = inside;
17352 best_in_bracket_range = in_bracket_range;
17353 best_destination = Some(
17354 if close.contains(&selection.start) && close.contains(&selection.end) {
17355 if inside { open.end } else { open.start }
17356 } else if inside {
17357 *close.start()
17358 } else {
17359 *close.end()
17360 },
17361 );
17362 }
17363
17364 if let Some(destination) = best_destination {
17365 selection.collapse_to(destination, SelectionGoal::None);
17366 }
17367 })
17368 });
17369 }
17370
17371 pub fn undo_selection(
17372 &mut self,
17373 _: &UndoSelection,
17374 window: &mut Window,
17375 cx: &mut Context<Self>,
17376 ) {
17377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17378 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17379 self.selection_history.mode = SelectionHistoryMode::Undoing;
17380 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17381 this.end_selection(window, cx);
17382 this.change_selections(
17383 SelectionEffects::scroll(Autoscroll::newest()),
17384 window,
17385 cx,
17386 |s| s.select_anchors(entry.selections.to_vec()),
17387 );
17388 });
17389 self.selection_history.mode = SelectionHistoryMode::Normal;
17390
17391 self.select_next_state = entry.select_next_state;
17392 self.select_prev_state = entry.select_prev_state;
17393 self.add_selections_state = entry.add_selections_state;
17394 }
17395 }
17396
17397 pub fn redo_selection(
17398 &mut self,
17399 _: &RedoSelection,
17400 window: &mut Window,
17401 cx: &mut Context<Self>,
17402 ) {
17403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17404 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17405 self.selection_history.mode = SelectionHistoryMode::Redoing;
17406 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17407 this.end_selection(window, cx);
17408 this.change_selections(
17409 SelectionEffects::scroll(Autoscroll::newest()),
17410 window,
17411 cx,
17412 |s| s.select_anchors(entry.selections.to_vec()),
17413 );
17414 });
17415 self.selection_history.mode = SelectionHistoryMode::Normal;
17416
17417 self.select_next_state = entry.select_next_state;
17418 self.select_prev_state = entry.select_prev_state;
17419 self.add_selections_state = entry.add_selections_state;
17420 }
17421 }
17422
17423 pub fn expand_excerpts(
17424 &mut self,
17425 action: &ExpandExcerpts,
17426 _: &mut Window,
17427 cx: &mut Context<Self>,
17428 ) {
17429 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17430 }
17431
17432 pub fn expand_excerpts_down(
17433 &mut self,
17434 action: &ExpandExcerptsDown,
17435 _: &mut Window,
17436 cx: &mut Context<Self>,
17437 ) {
17438 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17439 }
17440
17441 pub fn expand_excerpts_up(
17442 &mut self,
17443 action: &ExpandExcerptsUp,
17444 _: &mut Window,
17445 cx: &mut Context<Self>,
17446 ) {
17447 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17448 }
17449
17450 pub fn expand_excerpts_for_direction(
17451 &mut self,
17452 lines: u32,
17453 direction: ExpandExcerptDirection,
17454 cx: &mut Context<Self>,
17455 ) {
17456 let selections = self.selections.disjoint_anchors_arc();
17457
17458 let lines = if lines == 0 {
17459 EditorSettings::get_global(cx).expand_excerpt_lines
17460 } else {
17461 lines
17462 };
17463
17464 let snapshot = self.buffer.read(cx).snapshot(cx);
17465 let excerpt_ids = selections
17466 .iter()
17467 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17468 .unique()
17469 .sorted()
17470 .collect::<Vec<_>>();
17471
17472 if self.delegate_expand_excerpts {
17473 cx.emit(EditorEvent::ExpandExcerptsRequested {
17474 excerpt_ids,
17475 lines,
17476 direction,
17477 });
17478 return;
17479 }
17480
17481 self.buffer.update(cx, |buffer, cx| {
17482 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17483 })
17484 }
17485
17486 pub fn expand_excerpt(
17487 &mut self,
17488 excerpt: ExcerptId,
17489 direction: ExpandExcerptDirection,
17490 window: &mut Window,
17491 cx: &mut Context<Self>,
17492 ) {
17493 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17494
17495 if self.delegate_expand_excerpts {
17496 cx.emit(EditorEvent::ExpandExcerptsRequested {
17497 excerpt_ids: vec![excerpt],
17498 lines: lines_to_expand,
17499 direction,
17500 });
17501 return;
17502 }
17503
17504 let current_scroll_position = self.scroll_position(cx);
17505 let mut scroll = None;
17506
17507 if direction == ExpandExcerptDirection::Down {
17508 let multi_buffer = self.buffer.read(cx);
17509 let snapshot = multi_buffer.snapshot(cx);
17510 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17511 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17512 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17513 {
17514 let buffer_snapshot = buffer.read(cx).snapshot();
17515 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17516 let last_row = buffer_snapshot.max_point().row;
17517 let lines_below = last_row.saturating_sub(excerpt_end_row);
17518 if lines_below >= lines_to_expand {
17519 scroll = Some(
17520 current_scroll_position
17521 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17522 );
17523 }
17524 }
17525 }
17526 if direction == ExpandExcerptDirection::Up
17527 && self
17528 .buffer
17529 .read(cx)
17530 .snapshot(cx)
17531 .excerpt_before(excerpt)
17532 .is_none()
17533 {
17534 scroll = Some(current_scroll_position);
17535 }
17536
17537 self.buffer.update(cx, |buffer, cx| {
17538 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17539 });
17540
17541 if let Some(new_scroll_position) = scroll {
17542 self.set_scroll_position(new_scroll_position, window, cx);
17543 }
17544 }
17545
17546 pub fn go_to_singleton_buffer_point(
17547 &mut self,
17548 point: Point,
17549 window: &mut Window,
17550 cx: &mut Context<Self>,
17551 ) {
17552 self.go_to_singleton_buffer_range(point..point, window, cx);
17553 }
17554
17555 pub fn go_to_singleton_buffer_range(
17556 &mut self,
17557 range: Range<Point>,
17558 window: &mut Window,
17559 cx: &mut Context<Self>,
17560 ) {
17561 let multibuffer = self.buffer().read(cx);
17562 let Some(buffer) = multibuffer.as_singleton() else {
17563 return;
17564 };
17565 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17566 return;
17567 };
17568 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17569 return;
17570 };
17571 self.change_selections(
17572 SelectionEffects::default().nav_history(true),
17573 window,
17574 cx,
17575 |s| s.select_anchor_ranges([start..end]),
17576 );
17577 }
17578
17579 pub fn go_to_diagnostic(
17580 &mut self,
17581 action: &GoToDiagnostic,
17582 window: &mut Window,
17583 cx: &mut Context<Self>,
17584 ) {
17585 if !self.diagnostics_enabled() {
17586 return;
17587 }
17588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17589 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17590 }
17591
17592 pub fn go_to_prev_diagnostic(
17593 &mut self,
17594 action: &GoToPreviousDiagnostic,
17595 window: &mut Window,
17596 cx: &mut Context<Self>,
17597 ) {
17598 if !self.diagnostics_enabled() {
17599 return;
17600 }
17601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17602 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17603 }
17604
17605 pub fn go_to_diagnostic_impl(
17606 &mut self,
17607 direction: Direction,
17608 severity: GoToDiagnosticSeverityFilter,
17609 window: &mut Window,
17610 cx: &mut Context<Self>,
17611 ) {
17612 let buffer = self.buffer.read(cx).snapshot(cx);
17613 let selection = self
17614 .selections
17615 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17616
17617 let mut active_group_id = None;
17618 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17619 && active_group.active_range.start.to_offset(&buffer) == selection.start
17620 {
17621 active_group_id = Some(active_group.group_id);
17622 }
17623
17624 fn filtered<'a>(
17625 severity: GoToDiagnosticSeverityFilter,
17626 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17627 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17628 diagnostics
17629 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17630 .filter(|entry| entry.range.start != entry.range.end)
17631 .filter(|entry| !entry.diagnostic.is_unnecessary)
17632 }
17633
17634 let before = filtered(
17635 severity,
17636 buffer
17637 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17638 .filter(|entry| entry.range.start <= selection.start),
17639 );
17640 let after = filtered(
17641 severity,
17642 buffer
17643 .diagnostics_in_range(selection.start..buffer.len())
17644 .filter(|entry| entry.range.start >= selection.start),
17645 );
17646
17647 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17648 if direction == Direction::Prev {
17649 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17650 {
17651 for diagnostic in prev_diagnostics.into_iter().rev() {
17652 if diagnostic.range.start != selection.start
17653 || active_group_id
17654 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17655 {
17656 found = Some(diagnostic);
17657 break 'outer;
17658 }
17659 }
17660 }
17661 } else {
17662 for diagnostic in after.chain(before) {
17663 if diagnostic.range.start != selection.start
17664 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17665 {
17666 found = Some(diagnostic);
17667 break;
17668 }
17669 }
17670 }
17671 let Some(next_diagnostic) = found else {
17672 return;
17673 };
17674
17675 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17676 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17677 return;
17678 };
17679 let snapshot = self.snapshot(window, cx);
17680 if snapshot.intersects_fold(next_diagnostic.range.start) {
17681 self.unfold_ranges(
17682 std::slice::from_ref(&next_diagnostic.range),
17683 true,
17684 false,
17685 cx,
17686 );
17687 }
17688 self.change_selections(Default::default(), window, cx, |s| {
17689 s.select_ranges(vec![
17690 next_diagnostic.range.start..next_diagnostic.range.start,
17691 ])
17692 });
17693 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17694 self.refresh_edit_prediction(false, true, window, cx);
17695 }
17696
17697 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17698 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17699 let snapshot = self.snapshot(window, cx);
17700 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17701 self.go_to_hunk_before_or_after_position(
17702 &snapshot,
17703 selection.head(),
17704 Direction::Next,
17705 true,
17706 window,
17707 cx,
17708 );
17709 }
17710
17711 pub fn go_to_hunk_before_or_after_position(
17712 &mut self,
17713 snapshot: &EditorSnapshot,
17714 position: Point,
17715 direction: Direction,
17716 wrap_around: bool,
17717 window: &mut Window,
17718 cx: &mut Context<Editor>,
17719 ) {
17720 let row = if direction == Direction::Next {
17721 self.hunk_after_position(snapshot, position, wrap_around)
17722 .map(|hunk| hunk.row_range.start)
17723 } else {
17724 self.hunk_before_position(snapshot, position, wrap_around)
17725 };
17726
17727 if let Some(row) = row {
17728 let destination = Point::new(row.0, 0);
17729 let autoscroll = Autoscroll::center();
17730
17731 self.unfold_ranges(&[destination..destination], false, false, cx);
17732 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17733 s.select_ranges([destination..destination]);
17734 });
17735 }
17736 }
17737
17738 fn hunk_after_position(
17739 &mut self,
17740 snapshot: &EditorSnapshot,
17741 position: Point,
17742 wrap_around: bool,
17743 ) -> Option<MultiBufferDiffHunk> {
17744 let result = snapshot
17745 .buffer_snapshot()
17746 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17747 .find(|hunk| hunk.row_range.start.0 > position.row);
17748
17749 if wrap_around {
17750 result.or_else(|| {
17751 snapshot
17752 .buffer_snapshot()
17753 .diff_hunks_in_range(Point::zero()..position)
17754 .find(|hunk| hunk.row_range.end.0 < position.row)
17755 })
17756 } else {
17757 result
17758 }
17759 }
17760
17761 fn go_to_prev_hunk(
17762 &mut self,
17763 _: &GoToPreviousHunk,
17764 window: &mut Window,
17765 cx: &mut Context<Self>,
17766 ) {
17767 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17768 let snapshot = self.snapshot(window, cx);
17769 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17770 self.go_to_hunk_before_or_after_position(
17771 &snapshot,
17772 selection.head(),
17773 Direction::Prev,
17774 true,
17775 window,
17776 cx,
17777 );
17778 }
17779
17780 fn hunk_before_position(
17781 &mut self,
17782 snapshot: &EditorSnapshot,
17783 position: Point,
17784 wrap_around: bool,
17785 ) -> Option<MultiBufferRow> {
17786 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17787
17788 if wrap_around {
17789 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17790 } else {
17791 result
17792 }
17793 }
17794
17795 fn go_to_next_change(
17796 &mut self,
17797 _: &GoToNextChange,
17798 window: &mut Window,
17799 cx: &mut Context<Self>,
17800 ) {
17801 if let Some(selections) = self
17802 .change_list
17803 .next_change(1, Direction::Next)
17804 .map(|s| s.to_vec())
17805 {
17806 self.change_selections(Default::default(), window, cx, |s| {
17807 let map = s.display_snapshot();
17808 s.select_display_ranges(selections.iter().map(|a| {
17809 let point = a.to_display_point(&map);
17810 point..point
17811 }))
17812 })
17813 }
17814 }
17815
17816 fn go_to_previous_change(
17817 &mut self,
17818 _: &GoToPreviousChange,
17819 window: &mut Window,
17820 cx: &mut Context<Self>,
17821 ) {
17822 if let Some(selections) = self
17823 .change_list
17824 .next_change(1, Direction::Prev)
17825 .map(|s| s.to_vec())
17826 {
17827 self.change_selections(Default::default(), window, cx, |s| {
17828 let map = s.display_snapshot();
17829 s.select_display_ranges(selections.iter().map(|a| {
17830 let point = a.to_display_point(&map);
17831 point..point
17832 }))
17833 })
17834 }
17835 }
17836
17837 pub fn go_to_next_document_highlight(
17838 &mut self,
17839 _: &GoToNextDocumentHighlight,
17840 window: &mut Window,
17841 cx: &mut Context<Self>,
17842 ) {
17843 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17844 }
17845
17846 pub fn go_to_prev_document_highlight(
17847 &mut self,
17848 _: &GoToPreviousDocumentHighlight,
17849 window: &mut Window,
17850 cx: &mut Context<Self>,
17851 ) {
17852 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17853 }
17854
17855 pub fn go_to_document_highlight_before_or_after_position(
17856 &mut self,
17857 direction: Direction,
17858 window: &mut Window,
17859 cx: &mut Context<Editor>,
17860 ) {
17861 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17862 let snapshot = self.snapshot(window, cx);
17863 let buffer = &snapshot.buffer_snapshot();
17864 let position = self
17865 .selections
17866 .newest::<Point>(&snapshot.display_snapshot)
17867 .head();
17868 let anchor_position = buffer.anchor_after(position);
17869
17870 // Get all document highlights (both read and write)
17871 let mut all_highlights = Vec::new();
17872
17873 if let Some((_, read_highlights)) = self
17874 .background_highlights
17875 .get(&HighlightKey::DocumentHighlightRead)
17876 {
17877 all_highlights.extend(read_highlights.iter());
17878 }
17879
17880 if let Some((_, write_highlights)) = self
17881 .background_highlights
17882 .get(&HighlightKey::DocumentHighlightWrite)
17883 {
17884 all_highlights.extend(write_highlights.iter());
17885 }
17886
17887 if all_highlights.is_empty() {
17888 return;
17889 }
17890
17891 // Sort highlights by position
17892 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17893
17894 let target_highlight = match direction {
17895 Direction::Next => {
17896 // Find the first highlight after the current position
17897 all_highlights
17898 .iter()
17899 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17900 }
17901 Direction::Prev => {
17902 // Find the last highlight before the current position
17903 all_highlights
17904 .iter()
17905 .rev()
17906 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17907 }
17908 };
17909
17910 if let Some(highlight) = target_highlight {
17911 let destination = highlight.start.to_point(buffer);
17912 let autoscroll = Autoscroll::center();
17913
17914 self.unfold_ranges(&[destination..destination], false, false, cx);
17915 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17916 s.select_ranges([destination..destination]);
17917 });
17918 }
17919 }
17920
17921 fn go_to_line<T: 'static>(
17922 &mut self,
17923 position: Anchor,
17924 highlight_color: Option<Hsla>,
17925 window: &mut Window,
17926 cx: &mut Context<Self>,
17927 ) {
17928 let snapshot = self.snapshot(window, cx).display_snapshot;
17929 let position = position.to_point(&snapshot.buffer_snapshot());
17930 let start = snapshot
17931 .buffer_snapshot()
17932 .clip_point(Point::new(position.row, 0), Bias::Left);
17933 let end = start + Point::new(1, 0);
17934 let start = snapshot.buffer_snapshot().anchor_before(start);
17935 let end = snapshot.buffer_snapshot().anchor_before(end);
17936
17937 self.highlight_rows::<T>(
17938 start..end,
17939 highlight_color
17940 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17941 Default::default(),
17942 cx,
17943 );
17944
17945 if self.buffer.read(cx).is_singleton() {
17946 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17947 }
17948 }
17949
17950 pub fn go_to_definition(
17951 &mut self,
17952 _: &GoToDefinition,
17953 window: &mut Window,
17954 cx: &mut Context<Self>,
17955 ) -> Task<Result<Navigated>> {
17956 let definition =
17957 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17958 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17959 cx.spawn_in(window, async move |editor, cx| {
17960 if definition.await? == Navigated::Yes {
17961 return Ok(Navigated::Yes);
17962 }
17963 match fallback_strategy {
17964 GoToDefinitionFallback::None => Ok(Navigated::No),
17965 GoToDefinitionFallback::FindAllReferences => {
17966 match editor.update_in(cx, |editor, window, cx| {
17967 editor.find_all_references(&FindAllReferences::default(), window, cx)
17968 })? {
17969 Some(references) => references.await,
17970 None => Ok(Navigated::No),
17971 }
17972 }
17973 }
17974 })
17975 }
17976
17977 pub fn go_to_declaration(
17978 &mut self,
17979 _: &GoToDeclaration,
17980 window: &mut Window,
17981 cx: &mut Context<Self>,
17982 ) -> Task<Result<Navigated>> {
17983 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17984 }
17985
17986 pub fn go_to_declaration_split(
17987 &mut self,
17988 _: &GoToDeclaration,
17989 window: &mut Window,
17990 cx: &mut Context<Self>,
17991 ) -> Task<Result<Navigated>> {
17992 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17993 }
17994
17995 pub fn go_to_implementation(
17996 &mut self,
17997 _: &GoToImplementation,
17998 window: &mut Window,
17999 cx: &mut Context<Self>,
18000 ) -> Task<Result<Navigated>> {
18001 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
18002 }
18003
18004 pub fn go_to_implementation_split(
18005 &mut self,
18006 _: &GoToImplementationSplit,
18007 window: &mut Window,
18008 cx: &mut Context<Self>,
18009 ) -> Task<Result<Navigated>> {
18010 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
18011 }
18012
18013 pub fn go_to_type_definition(
18014 &mut self,
18015 _: &GoToTypeDefinition,
18016 window: &mut Window,
18017 cx: &mut Context<Self>,
18018 ) -> Task<Result<Navigated>> {
18019 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
18020 }
18021
18022 pub fn go_to_definition_split(
18023 &mut self,
18024 _: &GoToDefinitionSplit,
18025 window: &mut Window,
18026 cx: &mut Context<Self>,
18027 ) -> Task<Result<Navigated>> {
18028 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
18029 }
18030
18031 pub fn go_to_type_definition_split(
18032 &mut self,
18033 _: &GoToTypeDefinitionSplit,
18034 window: &mut Window,
18035 cx: &mut Context<Self>,
18036 ) -> Task<Result<Navigated>> {
18037 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18038 }
18039
18040 fn go_to_definition_of_kind(
18041 &mut self,
18042 kind: GotoDefinitionKind,
18043 split: bool,
18044 window: &mut Window,
18045 cx: &mut Context<Self>,
18046 ) -> Task<Result<Navigated>> {
18047 let Some(provider) = self.semantics_provider.clone() else {
18048 return Task::ready(Ok(Navigated::No));
18049 };
18050 let head = self
18051 .selections
18052 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18053 .head();
18054 let buffer = self.buffer.read(cx);
18055 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18056 return Task::ready(Ok(Navigated::No));
18057 };
18058 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18059 return Task::ready(Ok(Navigated::No));
18060 };
18061
18062 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18063
18064 cx.spawn_in(window, async move |editor, cx| {
18065 let Some(definitions) = definitions.await? else {
18066 return Ok(Navigated::No);
18067 };
18068 let navigated = editor
18069 .update_in(cx, |editor, window, cx| {
18070 editor.navigate_to_hover_links(
18071 Some(kind),
18072 definitions
18073 .into_iter()
18074 .filter(|location| {
18075 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18076 })
18077 .map(HoverLink::Text)
18078 .collect::<Vec<_>>(),
18079 nav_entry,
18080 split,
18081 window,
18082 cx,
18083 )
18084 })?
18085 .await?;
18086 anyhow::Ok(navigated)
18087 })
18088 }
18089
18090 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18091 let selection = self.selections.newest_anchor();
18092 let head = selection.head();
18093 let tail = selection.tail();
18094
18095 let Some((buffer, start_position)) =
18096 self.buffer.read(cx).text_anchor_for_position(head, cx)
18097 else {
18098 return;
18099 };
18100
18101 let end_position = if head != tail {
18102 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18103 return;
18104 };
18105 Some(pos)
18106 } else {
18107 None
18108 };
18109
18110 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18111 let url = if let Some(end_pos) = end_position {
18112 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18113 } else {
18114 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18115 };
18116
18117 if let Some(url) = url {
18118 cx.update(|window, cx| {
18119 if parse_zed_link(&url, cx).is_some() {
18120 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18121 } else {
18122 cx.open_url(&url);
18123 }
18124 })?;
18125 }
18126
18127 anyhow::Ok(())
18128 });
18129
18130 url_finder.detach();
18131 }
18132
18133 pub fn open_selected_filename(
18134 &mut self,
18135 _: &OpenSelectedFilename,
18136 window: &mut Window,
18137 cx: &mut Context<Self>,
18138 ) {
18139 let Some(workspace) = self.workspace() else {
18140 return;
18141 };
18142
18143 let position = self.selections.newest_anchor().head();
18144
18145 let Some((buffer, buffer_position)) =
18146 self.buffer.read(cx).text_anchor_for_position(position, cx)
18147 else {
18148 return;
18149 };
18150
18151 let project = self.project.clone();
18152
18153 cx.spawn_in(window, async move |_, cx| {
18154 let result = find_file(&buffer, project, buffer_position, cx).await;
18155
18156 if let Some((_, path)) = result {
18157 workspace
18158 .update_in(cx, |workspace, window, cx| {
18159 workspace.open_resolved_path(path, window, cx)
18160 })?
18161 .await?;
18162 }
18163 anyhow::Ok(())
18164 })
18165 .detach();
18166 }
18167
18168 pub(crate) fn navigate_to_hover_links(
18169 &mut self,
18170 kind: Option<GotoDefinitionKind>,
18171 definitions: Vec<HoverLink>,
18172 origin: Option<NavigationEntry>,
18173 split: bool,
18174 window: &mut Window,
18175 cx: &mut Context<Editor>,
18176 ) -> Task<Result<Navigated>> {
18177 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18178 let mut first_url_or_file = None;
18179 let definitions: Vec<_> = definitions
18180 .into_iter()
18181 .filter_map(|def| match def {
18182 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18183 HoverLink::InlayHint(lsp_location, server_id) => {
18184 let computation =
18185 self.compute_target_location(lsp_location, server_id, window, cx);
18186 Some(cx.background_spawn(computation))
18187 }
18188 HoverLink::Url(url) => {
18189 first_url_or_file = Some(Either::Left(url));
18190 None
18191 }
18192 HoverLink::File(path) => {
18193 first_url_or_file = Some(Either::Right(path));
18194 None
18195 }
18196 })
18197 .collect();
18198
18199 let workspace = self.workspace();
18200
18201 let excerpt_context_lines = multi_buffer::excerpt_context_lines(cx);
18202 cx.spawn_in(window, async move |editor, cx| {
18203 let locations: Vec<Location> = future::join_all(definitions)
18204 .await
18205 .into_iter()
18206 .filter_map(|location| location.transpose())
18207 .collect::<Result<_>>()
18208 .context("location tasks")?;
18209 let mut locations = cx.update(|_, cx| {
18210 locations
18211 .into_iter()
18212 .map(|location| {
18213 let buffer = location.buffer.read(cx);
18214 (location.buffer, location.range.to_point(buffer))
18215 })
18216 .into_group_map()
18217 })?;
18218 let mut num_locations = 0;
18219 for ranges in locations.values_mut() {
18220 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18221 ranges.dedup();
18222 let fits_in_one_excerpt = ranges
18223 .iter()
18224 .tuple_windows()
18225 .all(|(a, b)| b.start.row - a.end.row <= 2 * excerpt_context_lines);
18226 num_locations += if fits_in_one_excerpt { 1 } else { ranges.len() };
18227 }
18228
18229 if num_locations > 1 {
18230 let tab_kind = match kind {
18231 Some(GotoDefinitionKind::Implementation) => "Implementations",
18232 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18233 Some(GotoDefinitionKind::Declaration) => "Declarations",
18234 Some(GotoDefinitionKind::Type) => "Types",
18235 };
18236 let title = editor
18237 .update_in(cx, |_, _, cx| {
18238 let target = locations
18239 .iter()
18240 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18241 .map(|(buffer, location)| {
18242 buffer
18243 .read(cx)
18244 .text_for_range(location.clone())
18245 .collect::<String>()
18246 })
18247 .filter(|text| !text.contains('\n'))
18248 .unique()
18249 .take(3)
18250 .join(", ");
18251 if target.is_empty() {
18252 tab_kind.to_owned()
18253 } else {
18254 format!("{tab_kind} for {target}")
18255 }
18256 })
18257 .context("buffer title")?;
18258
18259 let Some(workspace) = workspace else {
18260 return Ok(Navigated::No);
18261 };
18262
18263 let opened = workspace
18264 .update_in(cx, |workspace, window, cx| {
18265 let allow_preview = PreviewTabsSettings::get_global(cx)
18266 .enable_preview_multibuffer_from_code_navigation;
18267 if let Some((target_editor, target_pane)) =
18268 Self::open_locations_in_multibuffer(
18269 workspace,
18270 locations,
18271 title,
18272 split,
18273 allow_preview,
18274 MultibufferSelectionMode::First,
18275 window,
18276 cx,
18277 )
18278 {
18279 // We create our own nav history instead of using
18280 // `target_editor.nav_history` because `nav_history`
18281 // seems to be populated asynchronously when an item
18282 // is added to a pane
18283 let mut nav_history = target_pane
18284 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18285 target_editor.update(cx, |editor, cx| {
18286 let nav_data = editor
18287 .navigation_data(editor.selections.newest_anchor().head(), cx);
18288 let target =
18289 Some(nav_history.navigation_entry(Some(
18290 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18291 )));
18292 nav_history.push_tag(origin, target);
18293 })
18294 }
18295 })
18296 .is_ok();
18297
18298 anyhow::Ok(Navigated::from_bool(opened))
18299 } else if num_locations == 0 {
18300 // If there is one url or file, open it directly
18301 match first_url_or_file {
18302 Some(Either::Left(url)) => {
18303 cx.update(|window, cx| {
18304 if parse_zed_link(&url, cx).is_some() {
18305 window
18306 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18307 } else {
18308 cx.open_url(&url);
18309 }
18310 })?;
18311 Ok(Navigated::Yes)
18312 }
18313 Some(Either::Right(path)) => {
18314 // TODO(andrew): respect preview tab settings
18315 // `enable_keep_preview_on_code_navigation` and
18316 // `enable_preview_file_from_code_navigation`
18317 let Some(workspace) = workspace else {
18318 return Ok(Navigated::No);
18319 };
18320 workspace
18321 .update_in(cx, |workspace, window, cx| {
18322 workspace.open_resolved_path(path, window, cx)
18323 })?
18324 .await?;
18325 Ok(Navigated::Yes)
18326 }
18327 None => Ok(Navigated::No),
18328 }
18329 } else {
18330 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18331
18332 editor.update_in(cx, |editor, window, cx| {
18333 let target_ranges = target_ranges
18334 .into_iter()
18335 .map(|r| editor.range_for_match(&r))
18336 .map(collapse_multiline_range)
18337 .collect::<Vec<_>>();
18338 if !split
18339 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18340 {
18341 let multibuffer = editor.buffer.read(cx);
18342 let target_ranges = target_ranges
18343 .into_iter()
18344 .filter_map(|r| {
18345 let start = multibuffer.buffer_point_to_anchor(
18346 &target_buffer,
18347 r.start,
18348 cx,
18349 )?;
18350 let end = multibuffer.buffer_point_to_anchor(
18351 &target_buffer,
18352 r.end,
18353 cx,
18354 )?;
18355 Some(start..end)
18356 })
18357 .collect::<Vec<_>>();
18358 if target_ranges.is_empty() {
18359 return Navigated::No;
18360 }
18361
18362 editor.change_selections(
18363 SelectionEffects::default().nav_history(true),
18364 window,
18365 cx,
18366 |s| s.select_anchor_ranges(target_ranges),
18367 );
18368
18369 let target =
18370 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18371 if let Some(mut nav_history) = editor.nav_history.clone() {
18372 nav_history.push_tag(origin, target);
18373 }
18374 } else {
18375 let Some(workspace) = workspace else {
18376 return Navigated::No;
18377 };
18378 let pane = workspace.read(cx).active_pane().clone();
18379 window.defer(cx, move |window, cx| {
18380 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18381 workspace.update(cx, |workspace, cx| {
18382 let pane = if split {
18383 workspace.adjacent_pane(window, cx)
18384 } else {
18385 workspace.active_pane().clone()
18386 };
18387
18388 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18389 let keep_old_preview = preview_tabs_settings
18390 .enable_keep_preview_on_code_navigation;
18391 let allow_new_preview = preview_tabs_settings
18392 .enable_preview_file_from_code_navigation;
18393
18394 let editor = workspace.open_project_item(
18395 pane.clone(),
18396 target_buffer.clone(),
18397 true,
18398 true,
18399 keep_old_preview,
18400 allow_new_preview,
18401 window,
18402 cx,
18403 );
18404 (editor, pane)
18405 });
18406 // We create our own nav history instead of using
18407 // `target_editor.nav_history` because `nav_history`
18408 // seems to be populated asynchronously when an item
18409 // is added to a pane
18410 let mut nav_history = target_pane
18411 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18412 target_editor.update(cx, |target_editor, cx| {
18413 // When selecting a definition in a different buffer, disable the nav history
18414 // to avoid creating a history entry at the previous cursor location.
18415 pane.update(cx, |pane, _| pane.disable_history());
18416
18417 let multibuffer = target_editor.buffer.read(cx);
18418 let Some(target_buffer) = multibuffer.as_singleton() else {
18419 return Navigated::No;
18420 };
18421 let target_ranges = target_ranges
18422 .into_iter()
18423 .filter_map(|r| {
18424 let start = multibuffer.buffer_point_to_anchor(
18425 &target_buffer,
18426 r.start,
18427 cx,
18428 )?;
18429 let end = multibuffer.buffer_point_to_anchor(
18430 &target_buffer,
18431 r.end,
18432 cx,
18433 )?;
18434 Some(start..end)
18435 })
18436 .collect::<Vec<_>>();
18437 if target_ranges.is_empty() {
18438 return Navigated::No;
18439 }
18440
18441 target_editor.change_selections(
18442 SelectionEffects::default().nav_history(true),
18443 window,
18444 cx,
18445 |s| s.select_anchor_ranges(target_ranges),
18446 );
18447
18448 let nav_data = target_editor.navigation_data(
18449 target_editor.selections.newest_anchor().head(),
18450 cx,
18451 );
18452 let target =
18453 Some(nav_history.navigation_entry(Some(
18454 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18455 )));
18456 nav_history.push_tag(origin, target);
18457 pane.update(cx, |pane, _| pane.enable_history());
18458 Navigated::Yes
18459 });
18460 });
18461 }
18462 Navigated::Yes
18463 })
18464 }
18465 })
18466 }
18467
18468 fn compute_target_location(
18469 &self,
18470 lsp_location: lsp::Location,
18471 server_id: LanguageServerId,
18472 window: &mut Window,
18473 cx: &mut Context<Self>,
18474 ) -> Task<anyhow::Result<Option<Location>>> {
18475 let Some(project) = self.project.clone() else {
18476 return Task::ready(Ok(None));
18477 };
18478
18479 cx.spawn_in(window, async move |editor, cx| {
18480 let location_task = editor.update(cx, |_, cx| {
18481 project.update(cx, |project, cx| {
18482 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18483 })
18484 })?;
18485 let location = Some({
18486 let target_buffer_handle = location_task.await.context("open local buffer")?;
18487 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18488 let target_start = target_buffer
18489 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18490 let target_end = target_buffer
18491 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18492 target_buffer.anchor_after(target_start)
18493 ..target_buffer.anchor_before(target_end)
18494 });
18495 Location {
18496 buffer: target_buffer_handle,
18497 range,
18498 }
18499 });
18500 Ok(location)
18501 })
18502 }
18503
18504 fn go_to_next_reference(
18505 &mut self,
18506 _: &GoToNextReference,
18507 window: &mut Window,
18508 cx: &mut Context<Self>,
18509 ) {
18510 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18511 if let Some(task) = task {
18512 task.detach();
18513 };
18514 }
18515
18516 fn go_to_prev_reference(
18517 &mut self,
18518 _: &GoToPreviousReference,
18519 window: &mut Window,
18520 cx: &mut Context<Self>,
18521 ) {
18522 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18523 if let Some(task) = task {
18524 task.detach();
18525 };
18526 }
18527
18528 fn go_to_symbol_by_offset(
18529 &mut self,
18530 window: &mut Window,
18531 cx: &mut Context<Self>,
18532 offset: i8,
18533 ) -> Task<Result<()>> {
18534 let editor_snapshot = self.snapshot(window, cx);
18535
18536 // We don't care about multi-buffer symbols
18537 let Some((excerpt_id, _, _)) = editor_snapshot.as_singleton() else {
18538 return Task::ready(Ok(()));
18539 };
18540
18541 let cursor_offset = self
18542 .selections
18543 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18544 .head();
18545
18546 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18547 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18548 let buffer = ed.buffer.read(cx).as_singleton()?;
18549 Some(buffer.read(cx).remote_id())
18550 }) else {
18551 return Ok(());
18552 };
18553
18554 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18555 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18556
18557 let multi_snapshot = editor_snapshot.buffer();
18558 let buffer_range = |range: &Range<_>| {
18559 Anchor::range_in_buffer(excerpt_id, range.clone()).to_offset(multi_snapshot)
18560 };
18561
18562 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18563 let current_idx = outline_items
18564 .iter()
18565 .enumerate()
18566 .filter_map(|(idx, item)| {
18567 // Find the closest outline item by distance between outline text and cursor location
18568 let source_range = buffer_range(&item.source_range_for_text);
18569 let distance_to_closest_endpoint = cmp::min(
18570 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18571 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18572 );
18573
18574 let item_towards_offset =
18575 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18576 == (offset as isize).signum();
18577
18578 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18579
18580 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18581 // we should not already be within the outline's source range. We then pick the closest outline
18582 // item.
18583 (item_towards_offset && !source_range_contains_cursor)
18584 .then_some((distance_to_closest_endpoint, idx))
18585 })
18586 .min()
18587 .map(|(_, idx)| idx);
18588
18589 let Some(idx) = current_idx else {
18590 return;
18591 };
18592
18593 let range = buffer_range(&outline_items[idx].source_range_for_text);
18594 let selection = [range.start..range.start];
18595
18596 let _ = editor
18597 .update(acx, |editor, ecx| {
18598 editor.change_selections(
18599 SelectionEffects::scroll(Autoscroll::newest()),
18600 window,
18601 ecx,
18602 |s| s.select_ranges(selection),
18603 );
18604 })
18605 .ok();
18606 })?;
18607
18608 Ok(())
18609 })
18610 }
18611
18612 fn go_to_next_symbol(
18613 &mut self,
18614 _: &GoToNextSymbol,
18615 window: &mut Window,
18616 cx: &mut Context<Self>,
18617 ) {
18618 self.go_to_symbol_by_offset(window, cx, 1).detach();
18619 }
18620
18621 fn go_to_previous_symbol(
18622 &mut self,
18623 _: &GoToPreviousSymbol,
18624 window: &mut Window,
18625 cx: &mut Context<Self>,
18626 ) {
18627 self.go_to_symbol_by_offset(window, cx, -1).detach();
18628 }
18629
18630 pub fn go_to_reference_before_or_after_position(
18631 &mut self,
18632 direction: Direction,
18633 count: usize,
18634 window: &mut Window,
18635 cx: &mut Context<Self>,
18636 ) -> Option<Task<Result<()>>> {
18637 let selection = self.selections.newest_anchor();
18638 let head = selection.head();
18639
18640 let multi_buffer = self.buffer.read(cx);
18641
18642 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18643 let workspace = self.workspace()?;
18644 let project = workspace.read(cx).project().clone();
18645 let references =
18646 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18647 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18648 let Some(locations) = references.await? else {
18649 return Ok(());
18650 };
18651
18652 if locations.is_empty() {
18653 // totally normal - the cursor may be on something which is not
18654 // a symbol (e.g. a keyword)
18655 log::info!("no references found under cursor");
18656 return Ok(());
18657 }
18658
18659 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18660
18661 let (locations, current_location_index) =
18662 multi_buffer.update(cx, |multi_buffer, cx| {
18663 let mut locations = locations
18664 .into_iter()
18665 .filter_map(|loc| {
18666 let start = multi_buffer.buffer_anchor_to_anchor(
18667 &loc.buffer,
18668 loc.range.start,
18669 cx,
18670 )?;
18671 let end = multi_buffer.buffer_anchor_to_anchor(
18672 &loc.buffer,
18673 loc.range.end,
18674 cx,
18675 )?;
18676 Some(start..end)
18677 })
18678 .collect::<Vec<_>>();
18679
18680 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18681 // There is an O(n) implementation, but given this list will be
18682 // small (usually <100 items), the extra O(log(n)) factor isn't
18683 // worth the (surprisingly large amount of) extra complexity.
18684 locations
18685 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18686
18687 let head_offset = head.to_offset(&multi_buffer_snapshot);
18688
18689 let current_location_index = locations.iter().position(|loc| {
18690 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18691 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18692 });
18693
18694 (locations, current_location_index)
18695 });
18696
18697 let Some(current_location_index) = current_location_index else {
18698 // This indicates something has gone wrong, because we already
18699 // handle the "no references" case above
18700 log::error!(
18701 "failed to find current reference under cursor. Total references: {}",
18702 locations.len()
18703 );
18704 return Ok(());
18705 };
18706
18707 let destination_location_index = match direction {
18708 Direction::Next => (current_location_index + count) % locations.len(),
18709 Direction::Prev => {
18710 (current_location_index + locations.len() - count % locations.len())
18711 % locations.len()
18712 }
18713 };
18714
18715 // TODO(cameron): is this needed?
18716 // the thinking is to avoid "jumping to the current location" (avoid
18717 // polluting "jumplist" in vim terms)
18718 if current_location_index == destination_location_index {
18719 return Ok(());
18720 }
18721
18722 let Range { start, end } = locations[destination_location_index];
18723
18724 editor.update_in(cx, |editor, window, cx| {
18725 let effects = SelectionEffects::default();
18726
18727 editor.unfold_ranges(&[start..end], false, false, cx);
18728 editor.change_selections(effects, window, cx, |s| {
18729 s.select_ranges([start..start]);
18730 });
18731 })?;
18732
18733 Ok(())
18734 }))
18735 }
18736
18737 pub fn find_all_references(
18738 &mut self,
18739 action: &FindAllReferences,
18740 window: &mut Window,
18741 cx: &mut Context<Self>,
18742 ) -> Option<Task<Result<Navigated>>> {
18743 let always_open_multibuffer = action.always_open_multibuffer;
18744 let selection = self.selections.newest_anchor();
18745 let multi_buffer = self.buffer.read(cx);
18746 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18747 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18748 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18749 let head = selection_offset.head();
18750
18751 let head_anchor = multi_buffer_snapshot.anchor_at(
18752 head,
18753 if head < selection_offset.tail() {
18754 Bias::Right
18755 } else {
18756 Bias::Left
18757 },
18758 );
18759
18760 match self
18761 .find_all_references_task_sources
18762 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18763 {
18764 Ok(_) => {
18765 log::info!(
18766 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18767 );
18768 return None;
18769 }
18770 Err(i) => {
18771 self.find_all_references_task_sources.insert(i, head_anchor);
18772 }
18773 }
18774
18775 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18776 let workspace = self.workspace()?;
18777 let project = workspace.read(cx).project().clone();
18778 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18779 Some(cx.spawn_in(window, async move |editor, cx| {
18780 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18781 if let Ok(i) = editor
18782 .find_all_references_task_sources
18783 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18784 {
18785 editor.find_all_references_task_sources.remove(i);
18786 }
18787 });
18788
18789 let Some(locations) = references.await? else {
18790 return anyhow::Ok(Navigated::No);
18791 };
18792 let mut locations = cx.update(|_, cx| {
18793 locations
18794 .into_iter()
18795 .map(|location| {
18796 let buffer = location.buffer.read(cx);
18797 (location.buffer, location.range.to_point(buffer))
18798 })
18799 // if special-casing the single-match case, remove ranges
18800 // that intersect current selection
18801 .filter(|(location_buffer, location)| {
18802 if always_open_multibuffer || &buffer != location_buffer {
18803 return true;
18804 }
18805
18806 !location.contains_inclusive(&selection_point.range())
18807 })
18808 .into_group_map()
18809 })?;
18810 if locations.is_empty() {
18811 return anyhow::Ok(Navigated::No);
18812 }
18813 for ranges in locations.values_mut() {
18814 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18815 ranges.dedup();
18816 }
18817 let mut num_locations = 0;
18818 for ranges in locations.values_mut() {
18819 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18820 ranges.dedup();
18821 num_locations += ranges.len();
18822 }
18823
18824 if num_locations == 1 && !always_open_multibuffer {
18825 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18826 let target_range = target_ranges.first().unwrap().clone();
18827
18828 return editor.update_in(cx, |editor, window, cx| {
18829 let range = target_range.to_point(target_buffer.read(cx));
18830 let range = editor.range_for_match(&range);
18831 let range = range.start..range.start;
18832
18833 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18834 editor.go_to_singleton_buffer_range(range, window, cx);
18835 } else {
18836 let pane = workspace.read(cx).active_pane().clone();
18837 window.defer(cx, move |window, cx| {
18838 let target_editor: Entity<Self> =
18839 workspace.update(cx, |workspace, cx| {
18840 let pane = workspace.active_pane().clone();
18841
18842 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18843 let keep_old_preview = preview_tabs_settings
18844 .enable_keep_preview_on_code_navigation;
18845 let allow_new_preview = preview_tabs_settings
18846 .enable_preview_file_from_code_navigation;
18847
18848 workspace.open_project_item(
18849 pane,
18850 target_buffer.clone(),
18851 true,
18852 true,
18853 keep_old_preview,
18854 allow_new_preview,
18855 window,
18856 cx,
18857 )
18858 });
18859 target_editor.update(cx, |target_editor, cx| {
18860 // When selecting a definition in a different buffer, disable the nav history
18861 // to avoid creating a history entry at the previous cursor location.
18862 pane.update(cx, |pane, _| pane.disable_history());
18863 target_editor.go_to_singleton_buffer_range(range, window, cx);
18864 pane.update(cx, |pane, _| pane.enable_history());
18865 });
18866 });
18867 }
18868 Navigated::No
18869 });
18870 }
18871
18872 workspace.update_in(cx, |workspace, window, cx| {
18873 let target = locations
18874 .iter()
18875 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18876 .map(|(buffer, location)| {
18877 buffer
18878 .read(cx)
18879 .text_for_range(location.clone())
18880 .collect::<String>()
18881 })
18882 .filter(|text| !text.contains('\n'))
18883 .unique()
18884 .take(3)
18885 .join(", ");
18886 let title = if target.is_empty() {
18887 "References".to_owned()
18888 } else {
18889 format!("References to {target}")
18890 };
18891 let allow_preview = PreviewTabsSettings::get_global(cx)
18892 .enable_preview_multibuffer_from_code_navigation;
18893 Self::open_locations_in_multibuffer(
18894 workspace,
18895 locations,
18896 title,
18897 false,
18898 allow_preview,
18899 MultibufferSelectionMode::First,
18900 window,
18901 cx,
18902 );
18903 Navigated::Yes
18904 })
18905 }))
18906 }
18907
18908 /// Opens a multibuffer with the given project locations in it.
18909 pub fn open_locations_in_multibuffer(
18910 workspace: &mut Workspace,
18911 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18912 title: String,
18913 split: bool,
18914 allow_preview: bool,
18915 multibuffer_selection_mode: MultibufferSelectionMode,
18916 window: &mut Window,
18917 cx: &mut Context<Workspace>,
18918 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18919 if locations.is_empty() {
18920 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18921 return None;
18922 }
18923
18924 let capability = workspace.project().read(cx).capability();
18925 let mut ranges = <Vec<Range<Anchor>>>::new();
18926
18927 // a key to find existing multibuffer editors with the same set of locations
18928 // to prevent us from opening more and more multibuffer tabs for searches and the like
18929 let mut key = (title.clone(), vec![]);
18930 let excerpt_buffer = cx.new(|cx| {
18931 let key = &mut key.1;
18932 let mut multibuffer = MultiBuffer::new(capability);
18933 for (buffer, mut ranges_for_buffer) in locations {
18934 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18935 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18936 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18937 PathKey::for_buffer(&buffer, cx),
18938 buffer.clone(),
18939 ranges_for_buffer,
18940 multibuffer_context_lines(cx),
18941 cx,
18942 );
18943 ranges.extend(new_ranges)
18944 }
18945
18946 multibuffer.with_title(title)
18947 });
18948 let existing = workspace.active_pane().update(cx, |pane, cx| {
18949 pane.items()
18950 .filter_map(|item| item.downcast::<Editor>())
18951 .find(|editor| {
18952 editor
18953 .read(cx)
18954 .lookup_key
18955 .as_ref()
18956 .and_then(|it| {
18957 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18958 })
18959 .is_some_and(|it| *it == key)
18960 })
18961 });
18962 let was_existing = existing.is_some();
18963 let editor = existing.unwrap_or_else(|| {
18964 cx.new(|cx| {
18965 let mut editor = Editor::for_multibuffer(
18966 excerpt_buffer,
18967 Some(workspace.project().clone()),
18968 window,
18969 cx,
18970 );
18971 editor.lookup_key = Some(Box::new(key));
18972 editor
18973 })
18974 });
18975 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18976 MultibufferSelectionMode::First => {
18977 if let Some(first_range) = ranges.first() {
18978 editor.change_selections(
18979 SelectionEffects::no_scroll(),
18980 window,
18981 cx,
18982 |selections| {
18983 selections.clear_disjoint();
18984 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18985 },
18986 );
18987 }
18988 editor.highlight_background(
18989 HighlightKey::Editor,
18990 &ranges,
18991 |_, theme| theme.colors().editor_highlighted_line_background,
18992 cx,
18993 );
18994 }
18995 MultibufferSelectionMode::All => {
18996 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18997 selections.clear_disjoint();
18998 selections.select_anchor_ranges(ranges);
18999 });
19000 }
19001 });
19002
19003 let item = Box::new(editor.clone());
19004
19005 let pane = if split {
19006 workspace.adjacent_pane(window, cx)
19007 } else {
19008 workspace.active_pane().clone()
19009 };
19010 let activate_pane = split;
19011
19012 let mut destination_index = None;
19013 pane.update(cx, |pane, cx| {
19014 if allow_preview && !was_existing {
19015 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
19016 }
19017 if was_existing && !allow_preview {
19018 pane.unpreview_item_if_preview(item.item_id());
19019 }
19020 pane.add_item(item, activate_pane, true, destination_index, window, cx);
19021 });
19022
19023 Some((editor, pane))
19024 }
19025
19026 pub fn rename(
19027 &mut self,
19028 _: &Rename,
19029 window: &mut Window,
19030 cx: &mut Context<Self>,
19031 ) -> Option<Task<Result<()>>> {
19032 use language::ToOffset as _;
19033
19034 let provider = self.semantics_provider.clone()?;
19035 let selection = self.selections.newest_anchor().clone();
19036 let (cursor_buffer, cursor_buffer_position) = self
19037 .buffer
19038 .read(cx)
19039 .text_anchor_for_position(selection.head(), cx)?;
19040 let (tail_buffer, cursor_buffer_position_end) = self
19041 .buffer
19042 .read(cx)
19043 .text_anchor_for_position(selection.tail(), cx)?;
19044 if tail_buffer != cursor_buffer {
19045 return None;
19046 }
19047
19048 let snapshot = cursor_buffer.read(cx).snapshot();
19049 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
19050 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
19051 let prepare_rename = provider
19052 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
19053 .unwrap_or_else(|| Task::ready(Ok(None)));
19054 drop(snapshot);
19055
19056 Some(cx.spawn_in(window, async move |this, cx| {
19057 let rename_range = if let Some(range) = prepare_rename.await? {
19058 Some(range)
19059 } else {
19060 this.update(cx, |this, cx| {
19061 let buffer = this.buffer.read(cx).snapshot(cx);
19062 let mut buffer_highlights = this
19063 .document_highlights_for_position(selection.head(), &buffer)
19064 .filter(|highlight| {
19065 highlight.start.excerpt_id == selection.head().excerpt_id
19066 && highlight.end.excerpt_id == selection.head().excerpt_id
19067 });
19068 buffer_highlights
19069 .next()
19070 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
19071 })?
19072 };
19073 if let Some(rename_range) = rename_range {
19074 this.update_in(cx, |this, window, cx| {
19075 let snapshot = cursor_buffer.read(cx).snapshot();
19076 let rename_buffer_range = rename_range.to_offset(&snapshot);
19077 let cursor_offset_in_rename_range =
19078 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
19079 let cursor_offset_in_rename_range_end =
19080 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
19081
19082 this.take_rename(false, window, cx);
19083 let buffer = this.buffer.read(cx).read(cx);
19084 let cursor_offset = selection.head().to_offset(&buffer);
19085 let rename_start =
19086 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
19087 let rename_end = rename_start + rename_buffer_range.len();
19088 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
19089 let mut old_highlight_id = None;
19090 let old_name: Arc<str> = buffer
19091 .chunks(rename_start..rename_end, true)
19092 .map(|chunk| {
19093 if old_highlight_id.is_none() {
19094 old_highlight_id = chunk.syntax_highlight_id;
19095 }
19096 chunk.text
19097 })
19098 .collect::<String>()
19099 .into();
19100
19101 drop(buffer);
19102
19103 // Position the selection in the rename editor so that it matches the current selection.
19104 this.show_local_selections = false;
19105 let rename_editor = cx.new(|cx| {
19106 let mut editor = Editor::single_line(window, cx);
19107 editor.buffer.update(cx, |buffer, cx| {
19108 buffer.edit(
19109 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
19110 None,
19111 cx,
19112 )
19113 });
19114 let cursor_offset_in_rename_range =
19115 MultiBufferOffset(cursor_offset_in_rename_range);
19116 let cursor_offset_in_rename_range_end =
19117 MultiBufferOffset(cursor_offset_in_rename_range_end);
19118 let rename_selection_range = match cursor_offset_in_rename_range
19119 .cmp(&cursor_offset_in_rename_range_end)
19120 {
19121 Ordering::Equal => {
19122 editor.select_all(&SelectAll, window, cx);
19123 return editor;
19124 }
19125 Ordering::Less => {
19126 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
19127 }
19128 Ordering::Greater => {
19129 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
19130 }
19131 };
19132 if rename_selection_range.end.0 > old_name.len() {
19133 editor.select_all(&SelectAll, window, cx);
19134 } else {
19135 editor.change_selections(Default::default(), window, cx, |s| {
19136 s.select_ranges([rename_selection_range]);
19137 });
19138 }
19139 editor
19140 });
19141 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19142 if e == &EditorEvent::Focused {
19143 cx.emit(EditorEvent::FocusedIn)
19144 }
19145 })
19146 .detach();
19147
19148 let write_highlights =
19149 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19150 let read_highlights =
19151 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19152 let ranges = write_highlights
19153 .iter()
19154 .flat_map(|(_, ranges)| ranges.iter())
19155 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19156 .cloned()
19157 .collect();
19158
19159 this.highlight_text(
19160 HighlightKey::Rename,
19161 ranges,
19162 HighlightStyle {
19163 fade_out: Some(0.6),
19164 ..Default::default()
19165 },
19166 cx,
19167 );
19168 let rename_focus_handle = rename_editor.focus_handle(cx);
19169 window.focus(&rename_focus_handle, cx);
19170 let block_id = this.insert_blocks(
19171 [BlockProperties {
19172 style: BlockStyle::Flex,
19173 placement: BlockPlacement::Below(range.start),
19174 height: Some(1),
19175 render: Arc::new({
19176 let rename_editor = rename_editor.clone();
19177 move |cx: &mut BlockContext| {
19178 let mut text_style = cx.editor_style.text.clone();
19179 if let Some(highlight_style) = old_highlight_id
19180 .and_then(|h| cx.editor_style.syntax.get(h).cloned())
19181 {
19182 text_style = text_style.highlight(highlight_style);
19183 }
19184 div()
19185 .block_mouse_except_scroll()
19186 .pl(cx.anchor_x)
19187 .child(EditorElement::new(
19188 &rename_editor,
19189 EditorStyle {
19190 background: cx.theme().system().transparent,
19191 local_player: cx.editor_style.local_player,
19192 text: text_style,
19193 scrollbar_width: cx.editor_style.scrollbar_width,
19194 syntax: cx.editor_style.syntax.clone(),
19195 status: cx.editor_style.status.clone(),
19196 inlay_hints_style: HighlightStyle {
19197 font_weight: Some(FontWeight::BOLD),
19198 ..make_inlay_hints_style(cx.app)
19199 },
19200 edit_prediction_styles: make_suggestion_styles(
19201 cx.app,
19202 ),
19203 ..EditorStyle::default()
19204 },
19205 ))
19206 .into_any_element()
19207 }
19208 }),
19209 priority: 0,
19210 }],
19211 Some(Autoscroll::fit()),
19212 cx,
19213 )[0];
19214 this.pending_rename = Some(RenameState {
19215 range,
19216 old_name,
19217 editor: rename_editor,
19218 block_id,
19219 });
19220 })?;
19221 }
19222
19223 Ok(())
19224 }))
19225 }
19226
19227 pub fn confirm_rename(
19228 &mut self,
19229 _: &ConfirmRename,
19230 window: &mut Window,
19231 cx: &mut Context<Self>,
19232 ) -> Option<Task<Result<()>>> {
19233 let rename = self.take_rename(false, window, cx)?;
19234 let workspace = self.workspace()?.downgrade();
19235 let (buffer, start) = self
19236 .buffer
19237 .read(cx)
19238 .text_anchor_for_position(rename.range.start, cx)?;
19239 let (end_buffer, _) = self
19240 .buffer
19241 .read(cx)
19242 .text_anchor_for_position(rename.range.end, cx)?;
19243 if buffer != end_buffer {
19244 return None;
19245 }
19246
19247 let old_name = rename.old_name;
19248 let new_name = rename.editor.read(cx).text(cx);
19249
19250 let rename = self.semantics_provider.as_ref()?.perform_rename(
19251 &buffer,
19252 start,
19253 new_name.clone(),
19254 cx,
19255 )?;
19256
19257 Some(cx.spawn_in(window, async move |editor, cx| {
19258 let project_transaction = rename.await?;
19259 Self::open_project_transaction(
19260 &editor,
19261 workspace,
19262 project_transaction,
19263 format!("Rename: {} → {}", old_name, new_name),
19264 cx,
19265 )
19266 .await?;
19267
19268 editor.update(cx, |editor, cx| {
19269 editor.refresh_document_highlights(cx);
19270 })?;
19271 Ok(())
19272 }))
19273 }
19274
19275 fn take_rename(
19276 &mut self,
19277 moving_cursor: bool,
19278 window: &mut Window,
19279 cx: &mut Context<Self>,
19280 ) -> Option<RenameState> {
19281 let rename = self.pending_rename.take()?;
19282 if rename.editor.focus_handle(cx).is_focused(window) {
19283 window.focus(&self.focus_handle, cx);
19284 }
19285
19286 self.remove_blocks(
19287 [rename.block_id].into_iter().collect(),
19288 Some(Autoscroll::fit()),
19289 cx,
19290 );
19291 self.clear_highlights(HighlightKey::Rename, cx);
19292 self.show_local_selections = true;
19293
19294 if moving_cursor {
19295 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19296 editor
19297 .selections
19298 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19299 .head()
19300 });
19301
19302 // Update the selection to match the position of the selection inside
19303 // the rename editor.
19304 let snapshot = self.buffer.read(cx).read(cx);
19305 let rename_range = rename.range.to_offset(&snapshot);
19306 let cursor_in_editor = snapshot
19307 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19308 .min(rename_range.end);
19309 drop(snapshot);
19310
19311 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19312 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19313 });
19314 } else {
19315 self.refresh_document_highlights(cx);
19316 }
19317
19318 Some(rename)
19319 }
19320
19321 pub fn pending_rename(&self) -> Option<&RenameState> {
19322 self.pending_rename.as_ref()
19323 }
19324
19325 fn format(
19326 &mut self,
19327 _: &Format,
19328 window: &mut Window,
19329 cx: &mut Context<Self>,
19330 ) -> Option<Task<Result<()>>> {
19331 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19332
19333 let project = match &self.project {
19334 Some(project) => project.clone(),
19335 None => return None,
19336 };
19337
19338 Some(self.perform_format(
19339 project,
19340 FormatTrigger::Manual,
19341 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19342 window,
19343 cx,
19344 ))
19345 }
19346
19347 fn format_selections(
19348 &mut self,
19349 _: &FormatSelections,
19350 window: &mut Window,
19351 cx: &mut Context<Self>,
19352 ) -> Option<Task<Result<()>>> {
19353 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19354
19355 let project = match &self.project {
19356 Some(project) => project.clone(),
19357 None => return None,
19358 };
19359
19360 let ranges = self
19361 .selections
19362 .all_adjusted(&self.display_snapshot(cx))
19363 .into_iter()
19364 .map(|selection| selection.range())
19365 .collect_vec();
19366
19367 Some(self.perform_format(
19368 project,
19369 FormatTrigger::Manual,
19370 FormatTarget::Ranges(ranges),
19371 window,
19372 cx,
19373 ))
19374 }
19375
19376 fn perform_format(
19377 &mut self,
19378 project: Entity<Project>,
19379 trigger: FormatTrigger,
19380 target: FormatTarget,
19381 window: &mut Window,
19382 cx: &mut Context<Self>,
19383 ) -> Task<Result<()>> {
19384 let buffer = self.buffer.clone();
19385 let (buffers, target) = match target {
19386 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19387 FormatTarget::Ranges(selection_ranges) => {
19388 let multi_buffer = buffer.read(cx);
19389 let snapshot = multi_buffer.read(cx);
19390 let mut buffers = HashSet::default();
19391 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19392 BTreeMap::new();
19393 for selection_range in selection_ranges {
19394 for (buffer, buffer_range, _) in
19395 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19396 {
19397 let buffer_id = buffer.remote_id();
19398 let start = buffer.anchor_before(buffer_range.start);
19399 let end = buffer.anchor_after(buffer_range.end);
19400 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19401 buffer_id_to_ranges
19402 .entry(buffer_id)
19403 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19404 .or_insert_with(|| vec![start..end]);
19405 }
19406 }
19407 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19408 }
19409 };
19410
19411 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19412 let selections_prev = transaction_id_prev
19413 .and_then(|transaction_id_prev| {
19414 // default to selections as they were after the last edit, if we have them,
19415 // instead of how they are now.
19416 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19417 // will take you back to where you made the last edit, instead of staying where you scrolled
19418 self.selection_history
19419 .transaction(transaction_id_prev)
19420 .map(|t| t.0.clone())
19421 })
19422 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19423
19424 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19425 let format = project.update(cx, |project, cx| {
19426 project.format(buffers, target, true, trigger, cx)
19427 });
19428
19429 cx.spawn_in(window, async move |editor, cx| {
19430 let transaction = futures::select_biased! {
19431 transaction = format.log_err().fuse() => transaction,
19432 () = timeout => {
19433 log::warn!("timed out waiting for formatting");
19434 None
19435 }
19436 };
19437
19438 buffer.update(cx, |buffer, cx| {
19439 if let Some(transaction) = transaction
19440 && !buffer.is_singleton()
19441 {
19442 buffer.push_transaction(&transaction.0, cx);
19443 }
19444 cx.notify();
19445 });
19446
19447 if let Some(transaction_id_now) =
19448 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19449 {
19450 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19451 if has_new_transaction {
19452 editor
19453 .update(cx, |editor, _| {
19454 editor
19455 .selection_history
19456 .insert_transaction(transaction_id_now, selections_prev);
19457 })
19458 .ok();
19459 }
19460 }
19461
19462 Ok(())
19463 })
19464 }
19465
19466 fn organize_imports(
19467 &mut self,
19468 _: &OrganizeImports,
19469 window: &mut Window,
19470 cx: &mut Context<Self>,
19471 ) -> Option<Task<Result<()>>> {
19472 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19473 let project = match &self.project {
19474 Some(project) => project.clone(),
19475 None => return None,
19476 };
19477 Some(self.perform_code_action_kind(
19478 project,
19479 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19480 window,
19481 cx,
19482 ))
19483 }
19484
19485 fn perform_code_action_kind(
19486 &mut self,
19487 project: Entity<Project>,
19488 kind: CodeActionKind,
19489 window: &mut Window,
19490 cx: &mut Context<Self>,
19491 ) -> Task<Result<()>> {
19492 let buffer = self.buffer.clone();
19493 let buffers = buffer.read(cx).all_buffers();
19494 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19495 let apply_action = project.update(cx, |project, cx| {
19496 project.apply_code_action_kind(buffers, kind, true, cx)
19497 });
19498 cx.spawn_in(window, async move |_, cx| {
19499 let transaction = futures::select_biased! {
19500 () = timeout => {
19501 log::warn!("timed out waiting for executing code action");
19502 None
19503 }
19504 transaction = apply_action.log_err().fuse() => transaction,
19505 };
19506 buffer.update(cx, |buffer, cx| {
19507 // check if we need this
19508 if let Some(transaction) = transaction
19509 && !buffer.is_singleton()
19510 {
19511 buffer.push_transaction(&transaction.0, cx);
19512 }
19513 cx.notify();
19514 });
19515 Ok(())
19516 })
19517 }
19518
19519 pub fn restart_language_server(
19520 &mut self,
19521 _: &RestartLanguageServer,
19522 _: &mut Window,
19523 cx: &mut Context<Self>,
19524 ) {
19525 if let Some(project) = self.project.clone() {
19526 self.buffer.update(cx, |multi_buffer, cx| {
19527 project.update(cx, |project, cx| {
19528 project.restart_language_servers_for_buffers(
19529 multi_buffer.all_buffers().into_iter().collect(),
19530 HashSet::default(),
19531 cx,
19532 );
19533 });
19534 })
19535 }
19536 }
19537
19538 pub fn stop_language_server(
19539 &mut self,
19540 _: &StopLanguageServer,
19541 _: &mut Window,
19542 cx: &mut Context<Self>,
19543 ) {
19544 if let Some(project) = self.project.clone() {
19545 self.buffer.update(cx, |multi_buffer, cx| {
19546 project.update(cx, |project, cx| {
19547 project.stop_language_servers_for_buffers(
19548 multi_buffer.all_buffers().into_iter().collect(),
19549 HashSet::default(),
19550 cx,
19551 );
19552 });
19553 });
19554 }
19555 }
19556
19557 fn cancel_language_server_work(
19558 workspace: &mut Workspace,
19559 _: &actions::CancelLanguageServerWork,
19560 _: &mut Window,
19561 cx: &mut Context<Workspace>,
19562 ) {
19563 let project = workspace.project();
19564 let buffers = workspace
19565 .active_item(cx)
19566 .and_then(|item| item.act_as::<Editor>(cx))
19567 .map_or(HashSet::default(), |editor| {
19568 editor.read(cx).buffer.read(cx).all_buffers()
19569 });
19570 project.update(cx, |project, cx| {
19571 project.cancel_language_server_work_for_buffers(buffers, cx);
19572 });
19573 }
19574
19575 fn show_character_palette(
19576 &mut self,
19577 _: &ShowCharacterPalette,
19578 window: &mut Window,
19579 _: &mut Context<Self>,
19580 ) {
19581 window.show_character_palette();
19582 }
19583
19584 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19585 if !self.diagnostics_enabled() {
19586 return;
19587 }
19588
19589 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19590 let buffer = self.buffer.read(cx).snapshot(cx);
19591 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19592 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19593 let is_valid = buffer
19594 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19595 .any(|entry| {
19596 entry.diagnostic.is_primary
19597 && !entry.range.is_empty()
19598 && entry.range.start == primary_range_start
19599 && entry.diagnostic.message == active_diagnostics.active_message
19600 });
19601
19602 if !is_valid {
19603 self.dismiss_diagnostics(cx);
19604 }
19605 }
19606 }
19607
19608 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19609 match &self.active_diagnostics {
19610 ActiveDiagnostic::Group(group) => Some(group),
19611 _ => None,
19612 }
19613 }
19614
19615 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19616 if !self.diagnostics_enabled() {
19617 return;
19618 }
19619 self.dismiss_diagnostics(cx);
19620 self.active_diagnostics = ActiveDiagnostic::All;
19621 }
19622
19623 fn activate_diagnostics(
19624 &mut self,
19625 buffer_id: BufferId,
19626 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19627 window: &mut Window,
19628 cx: &mut Context<Self>,
19629 ) {
19630 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19631 return;
19632 }
19633 self.dismiss_diagnostics(cx);
19634 let snapshot = self.snapshot(window, cx);
19635 let buffer = self.buffer.read(cx).snapshot(cx);
19636 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19637 return;
19638 };
19639
19640 let diagnostic_group = buffer
19641 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19642 .collect::<Vec<_>>();
19643
19644 let language_registry = self
19645 .project()
19646 .map(|project| project.read(cx).languages().clone());
19647
19648 let blocks = renderer.render_group(
19649 diagnostic_group,
19650 buffer_id,
19651 snapshot,
19652 cx.weak_entity(),
19653 language_registry,
19654 cx,
19655 );
19656
19657 let blocks = self.display_map.update(cx, |display_map, cx| {
19658 display_map.insert_blocks(blocks, cx).into_iter().collect()
19659 });
19660 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19661 active_range: buffer.anchor_before(diagnostic.range.start)
19662 ..buffer.anchor_after(diagnostic.range.end),
19663 active_message: diagnostic.diagnostic.message.clone(),
19664 group_id: diagnostic.diagnostic.group_id,
19665 blocks,
19666 });
19667 cx.notify();
19668 }
19669
19670 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19671 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19672 return;
19673 };
19674
19675 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19676 if let ActiveDiagnostic::Group(group) = prev {
19677 self.display_map.update(cx, |display_map, cx| {
19678 display_map.remove_blocks(group.blocks, cx);
19679 });
19680 cx.notify();
19681 }
19682 }
19683
19684 /// Disable inline diagnostics rendering for this editor.
19685 pub fn disable_inline_diagnostics(&mut self) {
19686 self.inline_diagnostics_enabled = false;
19687 self.inline_diagnostics_update = Task::ready(());
19688 self.inline_diagnostics.clear();
19689 }
19690
19691 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19692 self.diagnostics_enabled = false;
19693 self.dismiss_diagnostics(cx);
19694 self.inline_diagnostics_update = Task::ready(());
19695 self.inline_diagnostics.clear();
19696 }
19697
19698 pub fn disable_word_completions(&mut self) {
19699 self.word_completions_enabled = false;
19700 }
19701
19702 pub fn diagnostics_enabled(&self) -> bool {
19703 self.diagnostics_enabled && self.lsp_data_enabled()
19704 }
19705
19706 pub fn inline_diagnostics_enabled(&self) -> bool {
19707 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19708 }
19709
19710 pub fn show_inline_diagnostics(&self) -> bool {
19711 self.show_inline_diagnostics
19712 }
19713
19714 pub fn toggle_inline_diagnostics(
19715 &mut self,
19716 _: &ToggleInlineDiagnostics,
19717 window: &mut Window,
19718 cx: &mut Context<Editor>,
19719 ) {
19720 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19721 self.refresh_inline_diagnostics(false, window, cx);
19722 }
19723
19724 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19725 self.diagnostics_max_severity = severity;
19726 self.display_map.update(cx, |display_map, _| {
19727 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19728 });
19729 }
19730
19731 pub fn toggle_diagnostics(
19732 &mut self,
19733 _: &ToggleDiagnostics,
19734 window: &mut Window,
19735 cx: &mut Context<Editor>,
19736 ) {
19737 if !self.diagnostics_enabled() {
19738 return;
19739 }
19740
19741 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19742 EditorSettings::get_global(cx)
19743 .diagnostics_max_severity
19744 .filter(|severity| severity != &DiagnosticSeverity::Off)
19745 .unwrap_or(DiagnosticSeverity::Hint)
19746 } else {
19747 DiagnosticSeverity::Off
19748 };
19749 self.set_max_diagnostics_severity(new_severity, cx);
19750 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19751 self.active_diagnostics = ActiveDiagnostic::None;
19752 self.inline_diagnostics_update = Task::ready(());
19753 self.inline_diagnostics.clear();
19754 } else {
19755 self.refresh_inline_diagnostics(false, window, cx);
19756 }
19757
19758 cx.notify();
19759 }
19760
19761 pub fn toggle_minimap(
19762 &mut self,
19763 _: &ToggleMinimap,
19764 window: &mut Window,
19765 cx: &mut Context<Editor>,
19766 ) {
19767 if self.supports_minimap(cx) {
19768 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19769 }
19770 }
19771
19772 fn refresh_inline_diagnostics(
19773 &mut self,
19774 debounce: bool,
19775 window: &mut Window,
19776 cx: &mut Context<Self>,
19777 ) {
19778 let max_severity = ProjectSettings::get_global(cx)
19779 .diagnostics
19780 .inline
19781 .max_severity
19782 .unwrap_or(self.diagnostics_max_severity);
19783
19784 if !self.inline_diagnostics_enabled()
19785 || !self.diagnostics_enabled()
19786 || !self.show_inline_diagnostics
19787 || max_severity == DiagnosticSeverity::Off
19788 {
19789 self.inline_diagnostics_update = Task::ready(());
19790 self.inline_diagnostics.clear();
19791 return;
19792 }
19793
19794 let debounce_ms = ProjectSettings::get_global(cx)
19795 .diagnostics
19796 .inline
19797 .update_debounce_ms;
19798 let debounce = if debounce && debounce_ms > 0 {
19799 Some(Duration::from_millis(debounce_ms))
19800 } else {
19801 None
19802 };
19803 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19804 if let Some(debounce) = debounce {
19805 cx.background_executor().timer(debounce).await;
19806 }
19807 let Some(snapshot) = editor.upgrade().map(|editor| {
19808 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19809 }) else {
19810 return;
19811 };
19812
19813 let new_inline_diagnostics = cx
19814 .background_spawn(async move {
19815 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19816 for diagnostic_entry in
19817 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19818 {
19819 let message = diagnostic_entry
19820 .diagnostic
19821 .message
19822 .split_once('\n')
19823 .map(|(line, _)| line)
19824 .map(SharedString::new)
19825 .unwrap_or_else(|| {
19826 SharedString::new(&*diagnostic_entry.diagnostic.message)
19827 });
19828 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19829 let (Ok(i) | Err(i)) = inline_diagnostics
19830 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19831 inline_diagnostics.insert(
19832 i,
19833 (
19834 start_anchor,
19835 InlineDiagnostic {
19836 message,
19837 group_id: diagnostic_entry.diagnostic.group_id,
19838 start: diagnostic_entry.range.start.to_point(&snapshot),
19839 is_primary: diagnostic_entry.diagnostic.is_primary,
19840 severity: diagnostic_entry.diagnostic.severity,
19841 },
19842 ),
19843 );
19844 }
19845 inline_diagnostics
19846 })
19847 .await;
19848
19849 editor
19850 .update(cx, |editor, cx| {
19851 editor.inline_diagnostics = new_inline_diagnostics;
19852 cx.notify();
19853 })
19854 .ok();
19855 });
19856 }
19857
19858 fn pull_diagnostics(
19859 &mut self,
19860 buffer_id: BufferId,
19861 _window: &Window,
19862 cx: &mut Context<Self>,
19863 ) -> Option<()> {
19864 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19865 // skip any LSP updates for it.
19866
19867 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19868 return None;
19869 }
19870 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19871 .diagnostics
19872 .lsp_pull_diagnostics;
19873 if !pull_diagnostics_settings.enabled {
19874 return None;
19875 }
19876 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19877 let project = self.project()?.downgrade();
19878 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19879
19880 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19881 cx.background_executor().timer(debounce).await;
19882 if let Ok(task) = project.update(cx, |project, cx| {
19883 project.lsp_store().update(cx, |lsp_store, cx| {
19884 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19885 })
19886 }) {
19887 task.await.log_err();
19888 }
19889 project
19890 .update(cx, |project, cx| {
19891 project.lsp_store().update(cx, |lsp_store, cx| {
19892 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19893 })
19894 })
19895 .log_err();
19896 });
19897
19898 Some(())
19899 }
19900
19901 pub fn set_selections_from_remote(
19902 &mut self,
19903 selections: Vec<Selection<Anchor>>,
19904 pending_selection: Option<Selection<Anchor>>,
19905 window: &mut Window,
19906 cx: &mut Context<Self>,
19907 ) {
19908 let old_cursor_position = self.selections.newest_anchor().head();
19909 self.selections
19910 .change_with(&self.display_snapshot(cx), |s| {
19911 s.select_anchors(selections);
19912 if let Some(pending_selection) = pending_selection {
19913 s.set_pending(pending_selection, SelectMode::Character);
19914 } else {
19915 s.clear_pending();
19916 }
19917 });
19918 self.selections_did_change(
19919 false,
19920 &old_cursor_position,
19921 SelectionEffects::default(),
19922 window,
19923 cx,
19924 );
19925 }
19926
19927 pub fn transact(
19928 &mut self,
19929 window: &mut Window,
19930 cx: &mut Context<Self>,
19931 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19932 ) -> Option<TransactionId> {
19933 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19934 this.start_transaction_at(Instant::now(), window, cx);
19935 update(this, window, cx);
19936 this.end_transaction_at(Instant::now(), cx)
19937 })
19938 }
19939
19940 pub fn start_transaction_at(
19941 &mut self,
19942 now: Instant,
19943 window: &mut Window,
19944 cx: &mut Context<Self>,
19945 ) -> Option<TransactionId> {
19946 self.end_selection(window, cx);
19947 if let Some(tx_id) = self
19948 .buffer
19949 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19950 {
19951 self.selection_history
19952 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19953 cx.emit(EditorEvent::TransactionBegun {
19954 transaction_id: tx_id,
19955 });
19956 Some(tx_id)
19957 } else {
19958 None
19959 }
19960 }
19961
19962 pub fn end_transaction_at(
19963 &mut self,
19964 now: Instant,
19965 cx: &mut Context<Self>,
19966 ) -> Option<TransactionId> {
19967 if let Some(transaction_id) = self
19968 .buffer
19969 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19970 {
19971 if let Some((_, end_selections)) =
19972 self.selection_history.transaction_mut(transaction_id)
19973 {
19974 *end_selections = Some(self.selections.disjoint_anchors_arc());
19975 } else {
19976 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19977 }
19978
19979 cx.emit(EditorEvent::Edited { transaction_id });
19980 Some(transaction_id)
19981 } else {
19982 None
19983 }
19984 }
19985
19986 pub fn modify_transaction_selection_history(
19987 &mut self,
19988 transaction_id: TransactionId,
19989 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19990 ) -> bool {
19991 self.selection_history
19992 .transaction_mut(transaction_id)
19993 .map(modify)
19994 .is_some()
19995 }
19996
19997 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19998 if self.selection_mark_mode {
19999 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20000 s.move_with(&mut |_, sel| {
20001 sel.collapse_to(sel.head(), SelectionGoal::None);
20002 });
20003 })
20004 }
20005 self.selection_mark_mode = true;
20006 cx.notify();
20007 }
20008
20009 pub fn swap_selection_ends(
20010 &mut self,
20011 _: &actions::SwapSelectionEnds,
20012 window: &mut Window,
20013 cx: &mut Context<Self>,
20014 ) {
20015 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20016 s.move_with(&mut |_, sel| {
20017 if sel.start != sel.end {
20018 sel.reversed = !sel.reversed
20019 }
20020 });
20021 });
20022 self.request_autoscroll(Autoscroll::newest(), cx);
20023 cx.notify();
20024 }
20025
20026 pub fn toggle_focus(
20027 workspace: &mut Workspace,
20028 _: &actions::ToggleFocus,
20029 window: &mut Window,
20030 cx: &mut Context<Workspace>,
20031 ) {
20032 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
20033 return;
20034 };
20035 workspace.activate_item(&item, true, true, window, cx);
20036 }
20037
20038 pub fn toggle_fold(
20039 &mut self,
20040 _: &actions::ToggleFold,
20041 window: &mut Window,
20042 cx: &mut Context<Self>,
20043 ) {
20044 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20045 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20046 let selection = self.selections.newest::<Point>(&display_map);
20047
20048 let range = if selection.is_empty() {
20049 let point = selection.head().to_display_point(&display_map);
20050 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20051 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20052 .to_point(&display_map);
20053 start..end
20054 } else {
20055 selection.range()
20056 };
20057 if display_map.folds_in_range(range).next().is_some() {
20058 self.unfold_lines(&Default::default(), window, cx)
20059 } else {
20060 self.fold(&Default::default(), window, cx)
20061 }
20062 } else {
20063 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20064 let buffer_ids: HashSet<_> = self
20065 .selections
20066 .disjoint_anchor_ranges()
20067 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20068 .collect();
20069
20070 let should_unfold = buffer_ids
20071 .iter()
20072 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20073
20074 for buffer_id in buffer_ids {
20075 if should_unfold {
20076 self.unfold_buffer(buffer_id, cx);
20077 } else {
20078 self.fold_buffer(buffer_id, cx);
20079 }
20080 }
20081 }
20082 }
20083
20084 pub fn toggle_fold_recursive(
20085 &mut self,
20086 _: &actions::ToggleFoldRecursive,
20087 window: &mut Window,
20088 cx: &mut Context<Self>,
20089 ) {
20090 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20091
20092 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20093 let range = if selection.is_empty() {
20094 let point = selection.head().to_display_point(&display_map);
20095 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20096 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20097 .to_point(&display_map);
20098 start..end
20099 } else {
20100 selection.range()
20101 };
20102 if display_map.folds_in_range(range).next().is_some() {
20103 self.unfold_recursive(&Default::default(), window, cx)
20104 } else {
20105 self.fold_recursive(&Default::default(), window, cx)
20106 }
20107 }
20108
20109 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
20110 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20111 let mut to_fold = Vec::new();
20112 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20113 let selections = self.selections.all_adjusted(&display_map);
20114
20115 for selection in selections {
20116 let range = selection.range().sorted();
20117 let buffer_start_row = range.start.row;
20118
20119 if range.start.row != range.end.row {
20120 let mut found = false;
20121 let mut row = range.start.row;
20122 while row <= range.end.row {
20123 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20124 {
20125 found = true;
20126 row = crease.range().end.row + 1;
20127 to_fold.push(crease);
20128 } else {
20129 row += 1
20130 }
20131 }
20132 if found {
20133 continue;
20134 }
20135 }
20136
20137 for row in (0..=range.start.row).rev() {
20138 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20139 && crease.range().end.row >= buffer_start_row
20140 {
20141 to_fold.push(crease);
20142 if row <= range.start.row {
20143 break;
20144 }
20145 }
20146 }
20147 }
20148
20149 self.fold_creases(to_fold, true, window, cx);
20150 } else {
20151 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20152 let buffer_ids = self
20153 .selections
20154 .disjoint_anchor_ranges()
20155 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20156 .collect::<HashSet<_>>();
20157 for buffer_id in buffer_ids {
20158 self.fold_buffer(buffer_id, cx);
20159 }
20160 }
20161 }
20162
20163 pub fn toggle_fold_all(
20164 &mut self,
20165 _: &actions::ToggleFoldAll,
20166 window: &mut Window,
20167 cx: &mut Context<Self>,
20168 ) {
20169 let has_folds = if self.buffer.read(cx).is_singleton() {
20170 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20171 let has_folds = display_map
20172 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20173 .next()
20174 .is_some();
20175 has_folds
20176 } else {
20177 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
20178 let has_folds = buffer_ids
20179 .iter()
20180 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20181 has_folds
20182 };
20183
20184 if has_folds {
20185 self.unfold_all(&actions::UnfoldAll, window, cx);
20186 } else {
20187 self.fold_all(&actions::FoldAll, window, cx);
20188 }
20189 }
20190
20191 fn fold_at_level(
20192 &mut self,
20193 fold_at: &FoldAtLevel,
20194 window: &mut Window,
20195 cx: &mut Context<Self>,
20196 ) {
20197 if !self.buffer.read(cx).is_singleton() {
20198 return;
20199 }
20200
20201 let fold_at_level = fold_at.0;
20202 let snapshot = self.buffer.read(cx).snapshot(cx);
20203 let mut to_fold = Vec::new();
20204 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20205
20206 let row_ranges_to_keep: Vec<Range<u32>> = self
20207 .selections
20208 .all::<Point>(&self.display_snapshot(cx))
20209 .into_iter()
20210 .map(|sel| sel.start.row..sel.end.row)
20211 .collect();
20212
20213 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20214 while start_row < end_row {
20215 match self
20216 .snapshot(window, cx)
20217 .crease_for_buffer_row(MultiBufferRow(start_row))
20218 {
20219 Some(crease) => {
20220 let nested_start_row = crease.range().start.row + 1;
20221 let nested_end_row = crease.range().end.row;
20222
20223 if current_level < fold_at_level {
20224 stack.push((nested_start_row, nested_end_row, current_level + 1));
20225 } else if current_level == fold_at_level {
20226 // Fold iff there is no selection completely contained within the fold region
20227 if !row_ranges_to_keep.iter().any(|selection| {
20228 selection.end >= nested_start_row
20229 && selection.start <= nested_end_row
20230 }) {
20231 to_fold.push(crease);
20232 }
20233 }
20234
20235 start_row = nested_end_row + 1;
20236 }
20237 None => start_row += 1,
20238 }
20239 }
20240 }
20241
20242 self.fold_creases(to_fold, true, window, cx);
20243 }
20244
20245 pub fn fold_at_level_1(
20246 &mut self,
20247 _: &actions::FoldAtLevel1,
20248 window: &mut Window,
20249 cx: &mut Context<Self>,
20250 ) {
20251 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20252 }
20253
20254 pub fn fold_at_level_2(
20255 &mut self,
20256 _: &actions::FoldAtLevel2,
20257 window: &mut Window,
20258 cx: &mut Context<Self>,
20259 ) {
20260 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20261 }
20262
20263 pub fn fold_at_level_3(
20264 &mut self,
20265 _: &actions::FoldAtLevel3,
20266 window: &mut Window,
20267 cx: &mut Context<Self>,
20268 ) {
20269 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20270 }
20271
20272 pub fn fold_at_level_4(
20273 &mut self,
20274 _: &actions::FoldAtLevel4,
20275 window: &mut Window,
20276 cx: &mut Context<Self>,
20277 ) {
20278 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20279 }
20280
20281 pub fn fold_at_level_5(
20282 &mut self,
20283 _: &actions::FoldAtLevel5,
20284 window: &mut Window,
20285 cx: &mut Context<Self>,
20286 ) {
20287 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20288 }
20289
20290 pub fn fold_at_level_6(
20291 &mut self,
20292 _: &actions::FoldAtLevel6,
20293 window: &mut Window,
20294 cx: &mut Context<Self>,
20295 ) {
20296 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20297 }
20298
20299 pub fn fold_at_level_7(
20300 &mut self,
20301 _: &actions::FoldAtLevel7,
20302 window: &mut Window,
20303 cx: &mut Context<Self>,
20304 ) {
20305 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20306 }
20307
20308 pub fn fold_at_level_8(
20309 &mut self,
20310 _: &actions::FoldAtLevel8,
20311 window: &mut Window,
20312 cx: &mut Context<Self>,
20313 ) {
20314 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20315 }
20316
20317 pub fn fold_at_level_9(
20318 &mut self,
20319 _: &actions::FoldAtLevel9,
20320 window: &mut Window,
20321 cx: &mut Context<Self>,
20322 ) {
20323 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20324 }
20325
20326 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20327 if self.buffer.read(cx).is_singleton() {
20328 let mut fold_ranges = Vec::new();
20329 let snapshot = self.buffer.read(cx).snapshot(cx);
20330
20331 for row in 0..snapshot.max_row().0 {
20332 if let Some(foldable_range) = self
20333 .snapshot(window, cx)
20334 .crease_for_buffer_row(MultiBufferRow(row))
20335 {
20336 fold_ranges.push(foldable_range);
20337 }
20338 }
20339
20340 self.fold_creases(fold_ranges, true, window, cx);
20341 } else {
20342 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20343 editor
20344 .update_in(cx, |editor, _, cx| {
20345 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20346 editor.fold_buffer(buffer_id, cx);
20347 }
20348 })
20349 .ok();
20350 });
20351 }
20352 }
20353
20354 pub fn fold_function_bodies(
20355 &mut self,
20356 _: &actions::FoldFunctionBodies,
20357 window: &mut Window,
20358 cx: &mut Context<Self>,
20359 ) {
20360 let snapshot = self.buffer.read(cx).snapshot(cx);
20361
20362 let ranges = snapshot
20363 .text_object_ranges(
20364 MultiBufferOffset(0)..snapshot.len(),
20365 TreeSitterOptions::default(),
20366 )
20367 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20368 .collect::<Vec<_>>();
20369
20370 let creases = ranges
20371 .into_iter()
20372 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20373 .collect();
20374
20375 self.fold_creases(creases, true, window, cx);
20376 }
20377
20378 pub fn fold_recursive(
20379 &mut self,
20380 _: &actions::FoldRecursive,
20381 window: &mut Window,
20382 cx: &mut Context<Self>,
20383 ) {
20384 let mut to_fold = Vec::new();
20385 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20386 let selections = self.selections.all_adjusted(&display_map);
20387
20388 for selection in selections {
20389 let range = selection.range().sorted();
20390 let buffer_start_row = range.start.row;
20391
20392 if range.start.row != range.end.row {
20393 let mut found = false;
20394 for row in range.start.row..=range.end.row {
20395 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20396 found = true;
20397 to_fold.push(crease);
20398 }
20399 }
20400 if found {
20401 continue;
20402 }
20403 }
20404
20405 for row in (0..=range.start.row).rev() {
20406 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20407 if crease.range().end.row >= buffer_start_row {
20408 to_fold.push(crease);
20409 } else {
20410 break;
20411 }
20412 }
20413 }
20414 }
20415
20416 self.fold_creases(to_fold, true, window, cx);
20417 }
20418
20419 pub fn fold_at(
20420 &mut self,
20421 buffer_row: MultiBufferRow,
20422 window: &mut Window,
20423 cx: &mut Context<Self>,
20424 ) {
20425 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20426
20427 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20428 let autoscroll = self
20429 .selections
20430 .all::<Point>(&display_map)
20431 .iter()
20432 .any(|selection| crease.range().overlaps(&selection.range()));
20433
20434 self.fold_creases(vec![crease], autoscroll, window, cx);
20435 }
20436 }
20437
20438 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20439 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20440 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20441 let buffer = display_map.buffer_snapshot();
20442 let selections = self.selections.all::<Point>(&display_map);
20443 let ranges = selections
20444 .iter()
20445 .map(|s| {
20446 let range = s.display_range(&display_map).sorted();
20447 let mut start = range.start.to_point(&display_map);
20448 let mut end = range.end.to_point(&display_map);
20449 start.column = 0;
20450 end.column = buffer.line_len(MultiBufferRow(end.row));
20451 start..end
20452 })
20453 .collect::<Vec<_>>();
20454
20455 self.unfold_ranges(&ranges, true, true, cx);
20456 } else {
20457 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20458 let buffer_ids = self
20459 .selections
20460 .disjoint_anchor_ranges()
20461 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20462 .collect::<HashSet<_>>();
20463 for buffer_id in buffer_ids {
20464 self.unfold_buffer(buffer_id, cx);
20465 }
20466 }
20467 }
20468
20469 pub fn unfold_recursive(
20470 &mut self,
20471 _: &UnfoldRecursive,
20472 _window: &mut Window,
20473 cx: &mut Context<Self>,
20474 ) {
20475 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20476 let selections = self.selections.all::<Point>(&display_map);
20477 let ranges = selections
20478 .iter()
20479 .map(|s| {
20480 let mut range = s.display_range(&display_map).sorted();
20481 *range.start.column_mut() = 0;
20482 *range.end.column_mut() = display_map.line_len(range.end.row());
20483 let start = range.start.to_point(&display_map);
20484 let end = range.end.to_point(&display_map);
20485 start..end
20486 })
20487 .collect::<Vec<_>>();
20488
20489 self.unfold_ranges(&ranges, true, true, cx);
20490 }
20491
20492 pub fn unfold_at(
20493 &mut self,
20494 buffer_row: MultiBufferRow,
20495 _window: &mut Window,
20496 cx: &mut Context<Self>,
20497 ) {
20498 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20499
20500 let intersection_range = Point::new(buffer_row.0, 0)
20501 ..Point::new(
20502 buffer_row.0,
20503 display_map.buffer_snapshot().line_len(buffer_row),
20504 );
20505
20506 let autoscroll = self
20507 .selections
20508 .all::<Point>(&display_map)
20509 .iter()
20510 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20511
20512 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20513 }
20514
20515 pub fn unfold_all(
20516 &mut self,
20517 _: &actions::UnfoldAll,
20518 _window: &mut Window,
20519 cx: &mut Context<Self>,
20520 ) {
20521 if self.buffer.read(cx).is_singleton() {
20522 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20523 self.unfold_ranges(
20524 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20525 true,
20526 true,
20527 cx,
20528 );
20529 } else {
20530 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20531 editor
20532 .update(cx, |editor, cx| {
20533 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20534 editor.unfold_buffer(buffer_id, cx);
20535 }
20536 })
20537 .ok();
20538 });
20539 }
20540 }
20541
20542 pub fn fold_selected_ranges(
20543 &mut self,
20544 _: &FoldSelectedRanges,
20545 window: &mut Window,
20546 cx: &mut Context<Self>,
20547 ) {
20548 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20549 let selections = self.selections.all_adjusted(&display_map);
20550 let ranges = selections
20551 .into_iter()
20552 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20553 .collect::<Vec<_>>();
20554 self.fold_creases(ranges, true, window, cx);
20555 }
20556
20557 pub fn fold_ranges<T: ToOffset + Clone>(
20558 &mut self,
20559 ranges: Vec<Range<T>>,
20560 auto_scroll: bool,
20561 window: &mut Window,
20562 cx: &mut Context<Self>,
20563 ) {
20564 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20565 let ranges = ranges
20566 .into_iter()
20567 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20568 .collect::<Vec<_>>();
20569 self.fold_creases(ranges, auto_scroll, window, cx);
20570 }
20571
20572 pub fn fold_creases<T: ToOffset + Clone>(
20573 &mut self,
20574 creases: Vec<Crease<T>>,
20575 auto_scroll: bool,
20576 window: &mut Window,
20577 cx: &mut Context<Self>,
20578 ) {
20579 if creases.is_empty() {
20580 return;
20581 }
20582
20583 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20584
20585 if auto_scroll {
20586 self.request_autoscroll(Autoscroll::fit(), cx);
20587 }
20588
20589 cx.notify();
20590
20591 self.scrollbar_marker_state.dirty = true;
20592 self.update_data_on_scroll(window, cx);
20593 self.folds_did_change(cx);
20594 }
20595
20596 /// Removes any folds whose ranges intersect any of the given ranges.
20597 pub fn unfold_ranges<T: ToOffset + Clone>(
20598 &mut self,
20599 ranges: &[Range<T>],
20600 inclusive: bool,
20601 auto_scroll: bool,
20602 cx: &mut Context<Self>,
20603 ) {
20604 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20605 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20606 });
20607 self.folds_did_change(cx);
20608 }
20609
20610 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20611 self.fold_buffers([buffer_id], cx);
20612 }
20613
20614 pub fn fold_buffers(
20615 &mut self,
20616 buffer_ids: impl IntoIterator<Item = BufferId>,
20617 cx: &mut Context<Self>,
20618 ) {
20619 if self.buffer().read(cx).is_singleton() {
20620 return;
20621 }
20622
20623 let ids_to_fold: Vec<BufferId> = buffer_ids
20624 .into_iter()
20625 .filter(|id| !self.is_buffer_folded(*id, cx))
20626 .collect();
20627
20628 if ids_to_fold.is_empty() {
20629 return;
20630 }
20631
20632 let mut all_folded_excerpt_ids = Vec::new();
20633 for buffer_id in &ids_to_fold {
20634 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20635 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id));
20636 }
20637
20638 self.display_map.update(cx, |display_map, cx| {
20639 display_map.fold_buffers(ids_to_fold.clone(), cx)
20640 });
20641
20642 let snapshot = self.display_snapshot(cx);
20643 self.selections.change_with(&snapshot, |selections| {
20644 for buffer_id in ids_to_fold {
20645 selections.remove_selections_from_buffer(buffer_id);
20646 }
20647 });
20648
20649 cx.emit(EditorEvent::BufferFoldToggled {
20650 ids: all_folded_excerpt_ids,
20651 folded: true,
20652 });
20653 cx.notify();
20654 }
20655
20656 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20657 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20658 return;
20659 }
20660 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20661 self.display_map.update(cx, |display_map, cx| {
20662 display_map.unfold_buffers([buffer_id], cx);
20663 });
20664 cx.emit(EditorEvent::BufferFoldToggled {
20665 ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(),
20666 folded: false,
20667 });
20668 cx.notify();
20669 }
20670
20671 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20672 self.display_map.read(cx).is_buffer_folded(buffer)
20673 }
20674
20675 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20676 if self.buffer().read(cx).is_singleton() {
20677 return false;
20678 }
20679 !self.folded_buffers(cx).is_empty()
20680 }
20681
20682 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20683 self.display_map.read(cx).folded_buffers()
20684 }
20685
20686 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20687 self.display_map.update(cx, |display_map, cx| {
20688 display_map.disable_header_for_buffer(buffer_id, cx);
20689 });
20690 cx.notify();
20691 }
20692
20693 /// Removes any folds with the given ranges.
20694 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20695 &mut self,
20696 ranges: &[Range<T>],
20697 type_id: TypeId,
20698 auto_scroll: bool,
20699 cx: &mut Context<Self>,
20700 ) {
20701 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20702 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20703 });
20704 self.folds_did_change(cx);
20705 }
20706
20707 fn remove_folds_with<T: ToOffset + Clone>(
20708 &mut self,
20709 ranges: &[Range<T>],
20710 auto_scroll: bool,
20711 cx: &mut Context<Self>,
20712 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20713 ) {
20714 if ranges.is_empty() {
20715 return;
20716 }
20717
20718 let mut buffers_affected = HashSet::default();
20719 let multi_buffer = self.buffer().read(cx);
20720 for range in ranges {
20721 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20722 buffers_affected.insert(buffer.read(cx).remote_id());
20723 };
20724 }
20725
20726 self.display_map.update(cx, update);
20727
20728 if auto_scroll {
20729 self.request_autoscroll(Autoscroll::fit(), cx);
20730 }
20731
20732 cx.notify();
20733 self.scrollbar_marker_state.dirty = true;
20734 self.active_indent_guides_state.dirty = true;
20735 }
20736
20737 pub fn update_renderer_widths(
20738 &mut self,
20739 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20740 cx: &mut Context<Self>,
20741 ) -> bool {
20742 self.display_map
20743 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20744 }
20745
20746 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20747 self.display_map.read(cx).fold_placeholder.clone()
20748 }
20749
20750 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20751 self.buffer.update(cx, |buffer, cx| {
20752 buffer.set_all_diff_hunks_expanded(cx);
20753 });
20754 }
20755
20756 pub fn expand_all_diff_hunks(
20757 &mut self,
20758 _: &ExpandAllDiffHunks,
20759 _window: &mut Window,
20760 cx: &mut Context<Self>,
20761 ) {
20762 self.buffer.update(cx, |buffer, cx| {
20763 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20764 });
20765 }
20766
20767 pub fn collapse_all_diff_hunks(
20768 &mut self,
20769 _: &CollapseAllDiffHunks,
20770 _window: &mut Window,
20771 cx: &mut Context<Self>,
20772 ) {
20773 self.buffer.update(cx, |buffer, cx| {
20774 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20775 });
20776 }
20777
20778 pub fn toggle_selected_diff_hunks(
20779 &mut self,
20780 _: &ToggleSelectedDiffHunks,
20781 _window: &mut Window,
20782 cx: &mut Context<Self>,
20783 ) {
20784 let ranges: Vec<_> = self
20785 .selections
20786 .disjoint_anchors()
20787 .iter()
20788 .map(|s| s.range())
20789 .collect();
20790 self.toggle_diff_hunks_in_ranges(ranges, cx);
20791 }
20792
20793 pub fn diff_hunks_in_ranges<'a>(
20794 &'a self,
20795 ranges: &'a [Range<Anchor>],
20796 buffer: &'a MultiBufferSnapshot,
20797 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20798 ranges.iter().flat_map(move |range| {
20799 let end_excerpt_id = range.end.excerpt_id;
20800 let range = range.to_point(buffer);
20801 let mut peek_end = range.end;
20802 if range.end.row < buffer.max_row().0 {
20803 peek_end = Point::new(range.end.row + 1, 0);
20804 }
20805 buffer
20806 .diff_hunks_in_range(range.start..peek_end)
20807 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20808 })
20809 }
20810
20811 pub fn has_stageable_diff_hunks_in_ranges(
20812 &self,
20813 ranges: &[Range<Anchor>],
20814 snapshot: &MultiBufferSnapshot,
20815 ) -> bool {
20816 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20817 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20818 }
20819
20820 pub fn toggle_staged_selected_diff_hunks(
20821 &mut self,
20822 _: &::git::ToggleStaged,
20823 _: &mut Window,
20824 cx: &mut Context<Self>,
20825 ) {
20826 let snapshot = self.buffer.read(cx).snapshot(cx);
20827 let ranges: Vec<_> = self
20828 .selections
20829 .disjoint_anchors()
20830 .iter()
20831 .map(|s| s.range())
20832 .collect();
20833 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20834 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20835 }
20836
20837 pub fn set_render_diff_hunk_controls(
20838 &mut self,
20839 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20840 cx: &mut Context<Self>,
20841 ) {
20842 self.render_diff_hunk_controls = render_diff_hunk_controls;
20843 cx.notify();
20844 }
20845
20846 pub fn stage_and_next(
20847 &mut self,
20848 _: &::git::StageAndNext,
20849 window: &mut Window,
20850 cx: &mut Context<Self>,
20851 ) {
20852 self.do_stage_or_unstage_and_next(true, window, cx);
20853 }
20854
20855 pub fn unstage_and_next(
20856 &mut self,
20857 _: &::git::UnstageAndNext,
20858 window: &mut Window,
20859 cx: &mut Context<Self>,
20860 ) {
20861 self.do_stage_or_unstage_and_next(false, window, cx);
20862 }
20863
20864 pub fn stage_or_unstage_diff_hunks(
20865 &mut self,
20866 stage: bool,
20867 ranges: Vec<Range<Anchor>>,
20868 cx: &mut Context<Self>,
20869 ) {
20870 if self.delegate_stage_and_restore {
20871 let snapshot = self.buffer.read(cx).snapshot(cx);
20872 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20873 if !hunks.is_empty() {
20874 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20875 }
20876 return;
20877 }
20878 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20879 cx.spawn(async move |this, cx| {
20880 task.await?;
20881 this.update(cx, |this, cx| {
20882 let snapshot = this.buffer.read(cx).snapshot(cx);
20883 let chunk_by = this
20884 .diff_hunks_in_ranges(&ranges, &snapshot)
20885 .chunk_by(|hunk| hunk.buffer_id);
20886 for (buffer_id, hunks) in &chunk_by {
20887 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20888 }
20889 })
20890 })
20891 .detach_and_log_err(cx);
20892 }
20893
20894 fn save_buffers_for_ranges_if_needed(
20895 &mut self,
20896 ranges: &[Range<Anchor>],
20897 cx: &mut Context<Editor>,
20898 ) -> Task<Result<()>> {
20899 let multibuffer = self.buffer.read(cx);
20900 let snapshot = multibuffer.read(cx);
20901 let buffer_ids: HashSet<_> = ranges
20902 .iter()
20903 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20904 .collect();
20905 drop(snapshot);
20906
20907 let mut buffers = HashSet::default();
20908 for buffer_id in buffer_ids {
20909 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20910 let buffer = buffer_entity.read(cx);
20911 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20912 {
20913 buffers.insert(buffer_entity);
20914 }
20915 }
20916 }
20917
20918 if let Some(project) = &self.project {
20919 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20920 } else {
20921 Task::ready(Ok(()))
20922 }
20923 }
20924
20925 fn do_stage_or_unstage_and_next(
20926 &mut self,
20927 stage: bool,
20928 window: &mut Window,
20929 cx: &mut Context<Self>,
20930 ) {
20931 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20932
20933 if ranges.iter().any(|range| range.start != range.end) {
20934 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20935 return;
20936 }
20937
20938 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20939
20940 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20941 let wrap_around = !all_diff_hunks_expanded;
20942 let snapshot = self.snapshot(window, cx);
20943 let position = self
20944 .selections
20945 .newest::<Point>(&snapshot.display_snapshot)
20946 .head();
20947
20948 self.go_to_hunk_before_or_after_position(
20949 &snapshot,
20950 position,
20951 Direction::Next,
20952 wrap_around,
20953 window,
20954 cx,
20955 );
20956 }
20957
20958 pub(crate) fn do_stage_or_unstage(
20959 &self,
20960 stage: bool,
20961 buffer_id: BufferId,
20962 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20963 cx: &mut App,
20964 ) -> Option<()> {
20965 let project = self.project()?;
20966 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20967 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20968 let buffer_snapshot = buffer.read(cx).snapshot();
20969 let file_exists = buffer_snapshot
20970 .file()
20971 .is_some_and(|file| file.disk_state().exists());
20972 diff.update(cx, |diff, cx| {
20973 diff.stage_or_unstage_hunks(
20974 stage,
20975 &hunks
20976 .map(|hunk| buffer_diff::DiffHunk {
20977 buffer_range: hunk.buffer_range,
20978 // We don't need to pass in word diffs here because they're only used for rendering and
20979 // this function changes internal state
20980 base_word_diffs: Vec::default(),
20981 buffer_word_diffs: Vec::default(),
20982 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20983 ..hunk.diff_base_byte_range.end.0,
20984 secondary_status: hunk.status.secondary,
20985 range: Point::zero()..Point::zero(), // unused
20986 })
20987 .collect::<Vec<_>>(),
20988 &buffer_snapshot,
20989 file_exists,
20990 cx,
20991 )
20992 });
20993 None
20994 }
20995
20996 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20997 let ranges: Vec<_> = self
20998 .selections
20999 .disjoint_anchors()
21000 .iter()
21001 .map(|s| s.range())
21002 .collect();
21003 self.buffer
21004 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
21005 }
21006
21007 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
21008 self.buffer.update(cx, |buffer, cx| {
21009 let ranges = vec![Anchor::min()..Anchor::max()];
21010 if !buffer.all_diff_hunks_expanded()
21011 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
21012 {
21013 buffer.collapse_diff_hunks(ranges, cx);
21014 true
21015 } else {
21016 false
21017 }
21018 })
21019 }
21020
21021 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
21022 if self.buffer.read(cx).all_diff_hunks_expanded() {
21023 return true;
21024 }
21025 let ranges = vec![Anchor::min()..Anchor::max()];
21026 self.buffer
21027 .read(cx)
21028 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
21029 }
21030
21031 fn toggle_diff_hunks_in_ranges(
21032 &mut self,
21033 ranges: Vec<Range<Anchor>>,
21034 cx: &mut Context<Editor>,
21035 ) {
21036 self.buffer.update(cx, |buffer, cx| {
21037 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
21038 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
21039 })
21040 }
21041
21042 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
21043 self.buffer.update(cx, |buffer, cx| {
21044 buffer.toggle_single_diff_hunk(range, cx);
21045 })
21046 }
21047
21048 pub(crate) fn apply_all_diff_hunks(
21049 &mut self,
21050 _: &ApplyAllDiffHunks,
21051 window: &mut Window,
21052 cx: &mut Context<Self>,
21053 ) {
21054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21055
21056 let buffers = self.buffer.read(cx).all_buffers();
21057 for branch_buffer in buffers {
21058 branch_buffer.update(cx, |branch_buffer, cx| {
21059 branch_buffer.merge_into_base(Vec::new(), cx);
21060 });
21061 }
21062
21063 if let Some(project) = self.project.clone() {
21064 self.save(
21065 SaveOptions {
21066 format: true,
21067 autosave: false,
21068 },
21069 project,
21070 window,
21071 cx,
21072 )
21073 .detach_and_log_err(cx);
21074 }
21075 }
21076
21077 pub(crate) fn apply_selected_diff_hunks(
21078 &mut self,
21079 _: &ApplyDiffHunk,
21080 window: &mut Window,
21081 cx: &mut Context<Self>,
21082 ) {
21083 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21084 let snapshot = self.snapshot(window, cx);
21085 let hunks = snapshot.hunks_for_ranges(
21086 self.selections
21087 .all(&snapshot.display_snapshot)
21088 .into_iter()
21089 .map(|selection| selection.range()),
21090 );
21091 let mut ranges_by_buffer = HashMap::default();
21092 self.transact(window, cx, |editor, _window, cx| {
21093 for hunk in hunks {
21094 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
21095 ranges_by_buffer
21096 .entry(buffer.clone())
21097 .or_insert_with(Vec::new)
21098 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
21099 }
21100 }
21101
21102 for (buffer, ranges) in ranges_by_buffer {
21103 buffer.update(cx, |buffer, cx| {
21104 buffer.merge_into_base(ranges, cx);
21105 });
21106 }
21107 });
21108
21109 if let Some(project) = self.project.clone() {
21110 self.save(
21111 SaveOptions {
21112 format: true,
21113 autosave: false,
21114 },
21115 project,
21116 window,
21117 cx,
21118 )
21119 .detach_and_log_err(cx);
21120 }
21121 }
21122
21123 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
21124 if hovered != self.gutter_hovered {
21125 self.gutter_hovered = hovered;
21126 cx.notify();
21127 }
21128 }
21129
21130 pub fn insert_blocks(
21131 &mut self,
21132 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
21133 autoscroll: Option<Autoscroll>,
21134 cx: &mut Context<Self>,
21135 ) -> Vec<CustomBlockId> {
21136 let blocks = self
21137 .display_map
21138 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
21139 if let Some(autoscroll) = autoscroll {
21140 self.request_autoscroll(autoscroll, cx);
21141 }
21142 cx.notify();
21143 blocks
21144 }
21145
21146 pub fn resize_blocks(
21147 &mut self,
21148 heights: HashMap<CustomBlockId, u32>,
21149 autoscroll: Option<Autoscroll>,
21150 cx: &mut Context<Self>,
21151 ) {
21152 self.display_map
21153 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21154 if let Some(autoscroll) = autoscroll {
21155 self.request_autoscroll(autoscroll, cx);
21156 }
21157 cx.notify();
21158 }
21159
21160 pub fn replace_blocks(
21161 &mut self,
21162 renderers: HashMap<CustomBlockId, RenderBlock>,
21163 autoscroll: Option<Autoscroll>,
21164 cx: &mut Context<Self>,
21165 ) {
21166 self.display_map
21167 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21168 if let Some(autoscroll) = autoscroll {
21169 self.request_autoscroll(autoscroll, cx);
21170 }
21171 cx.notify();
21172 }
21173
21174 pub fn remove_blocks(
21175 &mut self,
21176 block_ids: HashSet<CustomBlockId>,
21177 autoscroll: Option<Autoscroll>,
21178 cx: &mut Context<Self>,
21179 ) {
21180 self.display_map.update(cx, |display_map, cx| {
21181 display_map.remove_blocks(block_ids, cx)
21182 });
21183 if let Some(autoscroll) = autoscroll {
21184 self.request_autoscroll(autoscroll, cx);
21185 }
21186 cx.notify();
21187 }
21188
21189 pub fn row_for_block(
21190 &self,
21191 block_id: CustomBlockId,
21192 cx: &mut Context<Self>,
21193 ) -> Option<DisplayRow> {
21194 self.display_map
21195 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21196 }
21197
21198 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21199 self.focused_block = Some(focused_block);
21200 }
21201
21202 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21203 self.focused_block.take()
21204 }
21205
21206 pub fn insert_creases(
21207 &mut self,
21208 creases: impl IntoIterator<Item = Crease<Anchor>>,
21209 cx: &mut Context<Self>,
21210 ) -> Vec<CreaseId> {
21211 self.display_map
21212 .update(cx, |map, cx| map.insert_creases(creases, cx))
21213 }
21214
21215 pub fn remove_creases(
21216 &mut self,
21217 ids: impl IntoIterator<Item = CreaseId>,
21218 cx: &mut Context<Self>,
21219 ) -> Vec<(CreaseId, Range<Anchor>)> {
21220 self.display_map
21221 .update(cx, |map, cx| map.remove_creases(ids, cx))
21222 }
21223
21224 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21225 self.display_map
21226 .update(cx, |map, cx| map.snapshot(cx))
21227 .longest_row()
21228 }
21229
21230 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21231 self.display_map
21232 .update(cx, |map, cx| map.snapshot(cx))
21233 .max_point()
21234 }
21235
21236 pub fn text(&self, cx: &App) -> String {
21237 self.buffer.read(cx).read(cx).text()
21238 }
21239
21240 pub fn is_empty(&self, cx: &App) -> bool {
21241 self.buffer.read(cx).read(cx).is_empty()
21242 }
21243
21244 pub fn text_option(&self, cx: &App) -> Option<String> {
21245 let text = self.text(cx);
21246 let text = text.trim();
21247
21248 if text.is_empty() {
21249 return None;
21250 }
21251
21252 Some(text.to_string())
21253 }
21254
21255 pub fn set_text(
21256 &mut self,
21257 text: impl Into<Arc<str>>,
21258 window: &mut Window,
21259 cx: &mut Context<Self>,
21260 ) {
21261 self.transact(window, cx, |this, _, cx| {
21262 this.buffer
21263 .read(cx)
21264 .as_singleton()
21265 .expect("you can only call set_text on editors for singleton buffers")
21266 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21267 });
21268 }
21269
21270 pub fn display_text(&self, cx: &mut App) -> String {
21271 self.display_map
21272 .update(cx, |map, cx| map.snapshot(cx))
21273 .text()
21274 }
21275
21276 fn create_minimap(
21277 &self,
21278 minimap_settings: MinimapSettings,
21279 window: &mut Window,
21280 cx: &mut Context<Self>,
21281 ) -> Option<Entity<Self>> {
21282 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21283 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21284 }
21285
21286 fn initialize_new_minimap(
21287 &self,
21288 minimap_settings: MinimapSettings,
21289 window: &mut Window,
21290 cx: &mut Context<Self>,
21291 ) -> Entity<Self> {
21292 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21293 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21294
21295 let mut minimap = Editor::new_internal(
21296 EditorMode::Minimap {
21297 parent: cx.weak_entity(),
21298 },
21299 self.buffer.clone(),
21300 None,
21301 Some(self.display_map.clone()),
21302 window,
21303 cx,
21304 );
21305 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21306 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21307 minimap.scroll_manager.clone_state(
21308 &self.scroll_manager,
21309 &my_snapshot,
21310 &minimap_snapshot,
21311 cx,
21312 );
21313 minimap.set_text_style_refinement(TextStyleRefinement {
21314 font_size: Some(MINIMAP_FONT_SIZE),
21315 font_weight: Some(MINIMAP_FONT_WEIGHT),
21316 font_family: Some(MINIMAP_FONT_FAMILY),
21317 ..Default::default()
21318 });
21319 minimap.update_minimap_configuration(minimap_settings, cx);
21320 cx.new(|_| minimap)
21321 }
21322
21323 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21324 let current_line_highlight = minimap_settings
21325 .current_line_highlight
21326 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21327 self.set_current_line_highlight(Some(current_line_highlight));
21328 }
21329
21330 pub fn minimap(&self) -> Option<&Entity<Self>> {
21331 self.minimap
21332 .as_ref()
21333 .filter(|_| self.minimap_visibility.visible())
21334 }
21335
21336 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21337 let mut wrap_guides = smallvec![];
21338
21339 if self.show_wrap_guides == Some(false) {
21340 return wrap_guides;
21341 }
21342
21343 let settings = self.buffer.read(cx).language_settings(cx);
21344 if settings.show_wrap_guides {
21345 match self.soft_wrap_mode(cx) {
21346 SoftWrap::Column(soft_wrap) => {
21347 wrap_guides.push((soft_wrap as usize, true));
21348 }
21349 SoftWrap::Bounded(soft_wrap) => {
21350 wrap_guides.push((soft_wrap as usize, true));
21351 }
21352 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21353 }
21354 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21355 }
21356
21357 wrap_guides
21358 }
21359
21360 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21361 let settings = self.buffer.read(cx).language_settings(cx);
21362 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21363 match mode {
21364 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21365 SoftWrap::None
21366 }
21367 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21368 language_settings::SoftWrap::PreferredLineLength => {
21369 SoftWrap::Column(settings.preferred_line_length)
21370 }
21371 language_settings::SoftWrap::Bounded => {
21372 SoftWrap::Bounded(settings.preferred_line_length)
21373 }
21374 }
21375 }
21376
21377 pub fn set_soft_wrap_mode(
21378 &mut self,
21379 mode: language_settings::SoftWrap,
21380 cx: &mut Context<Self>,
21381 ) {
21382 self.soft_wrap_mode_override = Some(mode);
21383 cx.notify();
21384 }
21385
21386 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21387 self.hard_wrap = hard_wrap;
21388 cx.notify();
21389 }
21390
21391 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21392 self.text_style_refinement = Some(style);
21393 }
21394
21395 /// called by the Element so we know what style we were most recently rendered with.
21396 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21397 // We intentionally do not inform the display map about the minimap style
21398 // so that wrapping is not recalculated and stays consistent for the editor
21399 // and its linked minimap.
21400 if !self.mode.is_minimap() {
21401 let font = style.text.font();
21402 let font_size = style.text.font_size.to_pixels(window.rem_size());
21403 let display_map = self
21404 .placeholder_display_map
21405 .as_ref()
21406 .filter(|_| self.is_empty(cx))
21407 .unwrap_or(&self.display_map);
21408
21409 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21410 }
21411 self.style = Some(style);
21412 }
21413
21414 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21415 if self.style.is_none() {
21416 self.style = Some(self.create_style(cx));
21417 }
21418 self.style.as_ref().unwrap()
21419 }
21420
21421 // Called by the element. This method is not designed to be called outside of the editor
21422 // element's layout code because it does not notify when rewrapping is computed synchronously.
21423 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21424 if self.is_empty(cx) {
21425 self.placeholder_display_map
21426 .as_ref()
21427 .map_or(false, |display_map| {
21428 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21429 })
21430 } else {
21431 self.display_map
21432 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21433 }
21434 }
21435
21436 pub fn set_soft_wrap(&mut self) {
21437 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21438 }
21439
21440 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21441 if self.soft_wrap_mode_override.is_some() {
21442 self.soft_wrap_mode_override.take();
21443 } else {
21444 let soft_wrap = match self.soft_wrap_mode(cx) {
21445 SoftWrap::GitDiff => return,
21446 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21447 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21448 language_settings::SoftWrap::None
21449 }
21450 };
21451 self.soft_wrap_mode_override = Some(soft_wrap);
21452 }
21453 cx.notify();
21454 }
21455
21456 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21457 let Some(workspace) = self.workspace() else {
21458 return;
21459 };
21460 let fs = workspace.read(cx).app_state().fs.clone();
21461 let current_show = TabBarSettings::get_global(cx).show;
21462 update_settings_file(fs, cx, move |setting, _| {
21463 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21464 });
21465 }
21466
21467 pub fn toggle_indent_guides(
21468 &mut self,
21469 _: &ToggleIndentGuides,
21470 _: &mut Window,
21471 cx: &mut Context<Self>,
21472 ) {
21473 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21474 self.buffer
21475 .read(cx)
21476 .language_settings(cx)
21477 .indent_guides
21478 .enabled
21479 });
21480 self.show_indent_guides = Some(!currently_enabled);
21481 cx.notify();
21482 }
21483
21484 fn should_show_indent_guides(&self) -> Option<bool> {
21485 self.show_indent_guides
21486 }
21487
21488 pub fn disable_indent_guides_for_buffer(
21489 &mut self,
21490 buffer_id: BufferId,
21491 cx: &mut Context<Self>,
21492 ) {
21493 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21494 cx.notify();
21495 }
21496
21497 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21498 self.buffers_with_disabled_indent_guides
21499 .contains(&buffer_id)
21500 }
21501
21502 pub fn toggle_line_numbers(
21503 &mut self,
21504 _: &ToggleLineNumbers,
21505 _: &mut Window,
21506 cx: &mut Context<Self>,
21507 ) {
21508 let mut editor_settings = EditorSettings::get_global(cx).clone();
21509 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21510 EditorSettings::override_global(editor_settings, cx);
21511 }
21512
21513 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21514 if let Some(show_line_numbers) = self.show_line_numbers {
21515 return show_line_numbers;
21516 }
21517 EditorSettings::get_global(cx).gutter.line_numbers
21518 }
21519
21520 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21521 match (
21522 self.use_relative_line_numbers,
21523 EditorSettings::get_global(cx).relative_line_numbers,
21524 ) {
21525 (None, setting) => setting,
21526 (Some(false), _) => RelativeLineNumbers::Disabled,
21527 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21528 (Some(true), _) => RelativeLineNumbers::Enabled,
21529 }
21530 }
21531
21532 pub fn toggle_relative_line_numbers(
21533 &mut self,
21534 _: &ToggleRelativeLineNumbers,
21535 _: &mut Window,
21536 cx: &mut Context<Self>,
21537 ) {
21538 let is_relative = self.relative_line_numbers(cx);
21539 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21540 }
21541
21542 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21543 self.use_relative_line_numbers = is_relative;
21544 cx.notify();
21545 }
21546
21547 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21548 self.show_gutter = show_gutter;
21549 cx.notify();
21550 }
21551
21552 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21553 self.show_scrollbars = ScrollbarAxes {
21554 horizontal: show,
21555 vertical: show,
21556 };
21557 cx.notify();
21558 }
21559
21560 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21561 self.show_scrollbars.vertical = show;
21562 cx.notify();
21563 }
21564
21565 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21566 self.show_scrollbars.horizontal = show;
21567 cx.notify();
21568 }
21569
21570 pub fn set_minimap_visibility(
21571 &mut self,
21572 minimap_visibility: MinimapVisibility,
21573 window: &mut Window,
21574 cx: &mut Context<Self>,
21575 ) {
21576 if self.minimap_visibility != minimap_visibility {
21577 if minimap_visibility.visible() && self.minimap.is_none() {
21578 let minimap_settings = EditorSettings::get_global(cx).minimap;
21579 self.minimap =
21580 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21581 }
21582 self.minimap_visibility = minimap_visibility;
21583 cx.notify();
21584 }
21585 }
21586
21587 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21588 self.set_show_scrollbars(false, cx);
21589 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21590 }
21591
21592 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21593 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21594 }
21595
21596 /// Normally the text in full mode and auto height editors is padded on the
21597 /// left side by roughly half a character width for improved hit testing.
21598 ///
21599 /// Use this method to disable this for cases where this is not wanted (e.g.
21600 /// if you want to align the editor text with some other text above or below)
21601 /// or if you want to add this padding to single-line editors.
21602 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21603 self.offset_content = offset_content;
21604 cx.notify();
21605 }
21606
21607 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21608 self.show_line_numbers = Some(show_line_numbers);
21609 cx.notify();
21610 }
21611
21612 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21613 self.disable_expand_excerpt_buttons = true;
21614 cx.notify();
21615 }
21616
21617 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21618 self.number_deleted_lines = number;
21619 cx.notify();
21620 }
21621
21622 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21623 self.delegate_expand_excerpts = delegate;
21624 }
21625
21626 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21627 self.delegate_stage_and_restore = delegate;
21628 }
21629
21630 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21631 self.delegate_open_excerpts = delegate;
21632 }
21633
21634 pub fn set_on_local_selections_changed(
21635 &mut self,
21636 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21637 ) {
21638 self.on_local_selections_changed = callback;
21639 }
21640
21641 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21642 self.suppress_selection_callback = suppress;
21643 }
21644
21645 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21646 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21647 cx.notify();
21648 }
21649
21650 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21651 self.show_code_actions = Some(show_code_actions);
21652 cx.notify();
21653 }
21654
21655 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21656 self.show_runnables = Some(show_runnables);
21657 cx.notify();
21658 }
21659
21660 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21661 self.show_breakpoints = Some(show_breakpoints);
21662 cx.notify();
21663 }
21664
21665 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21666 self.show_diff_review_button = show;
21667 cx.notify();
21668 }
21669
21670 pub fn show_diff_review_button(&self) -> bool {
21671 self.show_diff_review_button
21672 }
21673
21674 pub fn render_diff_review_button(
21675 &self,
21676 display_row: DisplayRow,
21677 width: Pixels,
21678 cx: &mut Context<Self>,
21679 ) -> impl IntoElement {
21680 let text_color = cx.theme().colors().text;
21681 let icon_color = cx.theme().colors().icon_accent;
21682
21683 h_flex()
21684 .id("diff_review_button")
21685 .cursor_pointer()
21686 .w(width - px(1.))
21687 .h(relative(0.9))
21688 .justify_center()
21689 .rounded_sm()
21690 .border_1()
21691 .border_color(text_color.opacity(0.1))
21692 .bg(text_color.opacity(0.15))
21693 .hover(|s| {
21694 s.bg(icon_color.opacity(0.4))
21695 .border_color(icon_color.opacity(0.5))
21696 })
21697 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21698 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21699 .on_mouse_down(
21700 gpui::MouseButton::Left,
21701 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21702 editor.start_diff_review_drag(display_row, window, cx);
21703 }),
21704 )
21705 }
21706
21707 pub fn start_diff_review_drag(
21708 &mut self,
21709 display_row: DisplayRow,
21710 window: &mut Window,
21711 cx: &mut Context<Self>,
21712 ) {
21713 let snapshot = self.snapshot(window, cx);
21714 let point = snapshot
21715 .display_snapshot
21716 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21717 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21718 self.diff_review_drag_state = Some(DiffReviewDragState {
21719 start_anchor: anchor,
21720 current_anchor: anchor,
21721 });
21722 cx.notify();
21723 }
21724
21725 pub fn update_diff_review_drag(
21726 &mut self,
21727 display_row: DisplayRow,
21728 window: &mut Window,
21729 cx: &mut Context<Self>,
21730 ) {
21731 if self.diff_review_drag_state.is_none() {
21732 return;
21733 }
21734 let snapshot = self.snapshot(window, cx);
21735 let point = snapshot
21736 .display_snapshot
21737 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21738 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21739 if let Some(drag_state) = &mut self.diff_review_drag_state {
21740 drag_state.current_anchor = anchor;
21741 cx.notify();
21742 }
21743 }
21744
21745 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21746 if let Some(drag_state) = self.diff_review_drag_state.take() {
21747 let snapshot = self.snapshot(window, cx);
21748 let range = drag_state.row_range(&snapshot.display_snapshot);
21749 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21750 }
21751 cx.notify();
21752 }
21753
21754 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21755 self.diff_review_drag_state = None;
21756 cx.notify();
21757 }
21758
21759 /// Calculates the appropriate block height for the diff review overlay.
21760 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21761 /// and 2 lines per comment when expanded.
21762 fn calculate_overlay_height(
21763 &self,
21764 hunk_key: &DiffHunkKey,
21765 comments_expanded: bool,
21766 snapshot: &MultiBufferSnapshot,
21767 ) -> u32 {
21768 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21769 let base_height: u32 = 2; // Input row with avatar and buttons
21770
21771 if comment_count == 0 {
21772 base_height
21773 } else if comments_expanded {
21774 // Header (1 line) + 2 lines per comment
21775 base_height + 1 + (comment_count as u32 * 2)
21776 } else {
21777 // Just header when collapsed
21778 base_height + 1
21779 }
21780 }
21781
21782 pub fn show_diff_review_overlay(
21783 &mut self,
21784 display_range: Range<DisplayRow>,
21785 window: &mut Window,
21786 cx: &mut Context<Self>,
21787 ) {
21788 let Range { start, end } = display_range.sorted();
21789
21790 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21791 let editor_snapshot = self.snapshot(window, cx);
21792
21793 // Convert display rows to multibuffer points
21794 let start_point = editor_snapshot
21795 .display_snapshot
21796 .display_point_to_point(start.as_display_point(), Bias::Left);
21797 let end_point = editor_snapshot
21798 .display_snapshot
21799 .display_point_to_point(end.as_display_point(), Bias::Left);
21800 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21801
21802 // Create anchor range for the selected lines (start of first line to end of last line)
21803 let line_end = Point::new(
21804 end_point.row,
21805 buffer_snapshot.line_len(end_multi_buffer_row),
21806 );
21807 let anchor_range =
21808 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21809
21810 // Compute the hunk key for this display row
21811 let file_path = buffer_snapshot
21812 .file_at(start_point)
21813 .map(|file: &Arc<dyn language::File>| file.path().clone())
21814 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21815 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21816 let new_hunk_key = DiffHunkKey {
21817 file_path,
21818 hunk_start_anchor,
21819 };
21820
21821 // Check if we already have an overlay for this hunk
21822 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21823 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21824 }) {
21825 // Just focus the existing overlay's prompt editor
21826 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21827 window.focus(&focus_handle, cx);
21828 return;
21829 }
21830
21831 // Dismiss overlays that have no comments for their hunks
21832 self.dismiss_overlays_without_comments(cx);
21833
21834 // Get the current user's avatar URI from the project's user_store
21835 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21836 let user_store = project.read(cx).user_store();
21837 user_store
21838 .read(cx)
21839 .current_user()
21840 .map(|user| user.avatar_uri.clone())
21841 });
21842
21843 // Create anchor at the end of the last row so the block appears immediately below it
21844 // Use multibuffer coordinates for anchor creation
21845 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21846 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21847
21848 // Use the hunk key we already computed
21849 let hunk_key = new_hunk_key;
21850
21851 // Create the prompt editor for the review input
21852 let prompt_editor = cx.new(|cx| {
21853 let mut editor = Editor::single_line(window, cx);
21854 editor.set_placeholder_text("Add a review comment...", window, cx);
21855 editor
21856 });
21857
21858 // Register the Newline action on the prompt editor to submit the review
21859 let parent_editor = cx.entity().downgrade();
21860 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21861 prompt_editor.register_action({
21862 let parent_editor = parent_editor.clone();
21863 move |_: &crate::actions::Newline, window, cx| {
21864 if let Some(editor) = parent_editor.upgrade() {
21865 editor.update(cx, |editor, cx| {
21866 editor.submit_diff_review_comment(window, cx);
21867 });
21868 }
21869 }
21870 })
21871 });
21872
21873 // Calculate initial height based on existing comments for this hunk
21874 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21875
21876 // Create the overlay block
21877 let prompt_editor_for_render = prompt_editor.clone();
21878 let hunk_key_for_render = hunk_key.clone();
21879 let editor_handle = cx.entity().downgrade();
21880 let block = BlockProperties {
21881 style: BlockStyle::Sticky,
21882 placement: BlockPlacement::Below(anchor),
21883 height: Some(initial_height),
21884 render: Arc::new(move |cx| {
21885 Self::render_diff_review_overlay(
21886 &prompt_editor_for_render,
21887 &hunk_key_for_render,
21888 &editor_handle,
21889 cx,
21890 )
21891 }),
21892 priority: 0,
21893 };
21894
21895 let block_ids = self.insert_blocks([block], None, cx);
21896 let Some(block_id) = block_ids.into_iter().next() else {
21897 log::error!("Failed to insert diff review overlay block");
21898 return;
21899 };
21900
21901 self.diff_review_overlays.push(DiffReviewOverlay {
21902 anchor_range,
21903 block_id,
21904 prompt_editor: prompt_editor.clone(),
21905 hunk_key,
21906 comments_expanded: true,
21907 inline_edit_editors: HashMap::default(),
21908 inline_edit_subscriptions: HashMap::default(),
21909 user_avatar_uri,
21910 _subscription: subscription,
21911 });
21912
21913 // Focus the prompt editor
21914 let focus_handle = prompt_editor.focus_handle(cx);
21915 window.focus(&focus_handle, cx);
21916
21917 cx.notify();
21918 }
21919
21920 /// Dismisses all diff review overlays.
21921 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21922 if self.diff_review_overlays.is_empty() {
21923 return;
21924 }
21925 let block_ids: HashSet<_> = self
21926 .diff_review_overlays
21927 .drain(..)
21928 .map(|overlay| overlay.block_id)
21929 .collect();
21930 self.remove_blocks(block_ids, None, cx);
21931 cx.notify();
21932 }
21933
21934 /// Dismisses overlays that have no comments stored for their hunks.
21935 /// Keeps overlays that have at least one comment.
21936 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21937 let snapshot = self.buffer.read(cx).snapshot(cx);
21938
21939 // First, compute which overlays have comments (to avoid borrow issues with retain)
21940 let overlays_with_comments: Vec<bool> = self
21941 .diff_review_overlays
21942 .iter()
21943 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21944 .collect();
21945
21946 // Now collect block IDs to remove and retain overlays
21947 let mut block_ids_to_remove = HashSet::default();
21948 let mut index = 0;
21949 self.diff_review_overlays.retain(|overlay| {
21950 let has_comments = overlays_with_comments[index];
21951 index += 1;
21952 if !has_comments {
21953 block_ids_to_remove.insert(overlay.block_id);
21954 }
21955 has_comments
21956 });
21957
21958 if !block_ids_to_remove.is_empty() {
21959 self.remove_blocks(block_ids_to_remove, None, cx);
21960 cx.notify();
21961 }
21962 }
21963
21964 /// Refreshes the diff review overlay block to update its height and render function.
21965 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21966 fn refresh_diff_review_overlay_height(
21967 &mut self,
21968 hunk_key: &DiffHunkKey,
21969 _window: &mut Window,
21970 cx: &mut Context<Self>,
21971 ) {
21972 // Extract all needed data from overlay first to avoid borrow conflicts
21973 let snapshot = self.buffer.read(cx).snapshot(cx);
21974 let (comments_expanded, block_id, prompt_editor) = {
21975 let Some(overlay) = self
21976 .diff_review_overlays
21977 .iter()
21978 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21979 else {
21980 return;
21981 };
21982
21983 (
21984 overlay.comments_expanded,
21985 overlay.block_id,
21986 overlay.prompt_editor.clone(),
21987 )
21988 };
21989
21990 // Calculate new height
21991 let snapshot = self.buffer.read(cx).snapshot(cx);
21992 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21993
21994 // Update the block height using resize_blocks (avoids flicker)
21995 let mut heights = HashMap::default();
21996 heights.insert(block_id, new_height);
21997 self.resize_blocks(heights, None, cx);
21998
21999 // Update the render function using replace_blocks (avoids flicker)
22000 let hunk_key_for_render = hunk_key.clone();
22001 let editor_handle = cx.entity().downgrade();
22002 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
22003 Arc::new(move |cx| {
22004 Self::render_diff_review_overlay(
22005 &prompt_editor,
22006 &hunk_key_for_render,
22007 &editor_handle,
22008 cx,
22009 )
22010 });
22011
22012 let mut renderers = HashMap::default();
22013 renderers.insert(block_id, render);
22014 self.replace_blocks(renderers, None, cx);
22015 }
22016
22017 /// Action handler for SubmitDiffReviewComment.
22018 pub fn submit_diff_review_comment_action(
22019 &mut self,
22020 _: &SubmitDiffReviewComment,
22021 window: &mut Window,
22022 cx: &mut Context<Self>,
22023 ) {
22024 self.submit_diff_review_comment(window, cx);
22025 }
22026
22027 /// Stores the diff review comment locally.
22028 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
22029 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22030 // Find the overlay that currently has focus
22031 let overlay_index = self
22032 .diff_review_overlays
22033 .iter()
22034 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
22035 let Some(overlay_index) = overlay_index else {
22036 return;
22037 };
22038 let overlay = &self.diff_review_overlays[overlay_index];
22039
22040 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
22041 if comment_text.is_empty() {
22042 return;
22043 }
22044
22045 let anchor_range = overlay.anchor_range.clone();
22046 let hunk_key = overlay.hunk_key.clone();
22047
22048 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
22049
22050 // Clear the prompt editor but keep the overlay open
22051 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
22052 overlay.prompt_editor.update(cx, |editor, cx| {
22053 editor.clear(window, cx);
22054 });
22055 }
22056
22057 // Refresh the overlay to update the block height for the new comment
22058 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22059
22060 cx.notify();
22061 }
22062
22063 /// Returns the prompt editor for the diff review overlay, if one is active.
22064 /// This is primarily used for testing.
22065 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
22066 self.diff_review_overlays
22067 .first()
22068 .map(|overlay| &overlay.prompt_editor)
22069 }
22070
22071 /// Returns the line range for the first diff review overlay, if one is active.
22072 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
22073 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
22074 let overlay = self.diff_review_overlays.first()?;
22075 let snapshot = self.buffer.read(cx).snapshot(cx);
22076 let start_point = overlay.anchor_range.start.to_point(&snapshot);
22077 let end_point = overlay.anchor_range.end.to_point(&snapshot);
22078 let start_row = snapshot
22079 .point_to_buffer_point(start_point)
22080 .map(|(_, p, _)| p.row)
22081 .unwrap_or(start_point.row);
22082 let end_row = snapshot
22083 .point_to_buffer_point(end_point)
22084 .map(|(_, p, _)| p.row)
22085 .unwrap_or(end_point.row);
22086 Some((start_row, end_row))
22087 }
22088
22089 /// Sets whether the comments section is expanded in the diff review overlay.
22090 /// This is primarily used for testing.
22091 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
22092 for overlay in &mut self.diff_review_overlays {
22093 overlay.comments_expanded = expanded;
22094 }
22095 cx.notify();
22096 }
22097
22098 /// Compares two DiffHunkKeys for equality by resolving their anchors.
22099 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
22100 a.file_path == b.file_path
22101 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
22102 }
22103
22104 /// Returns comments for a specific hunk, ordered by creation time.
22105 pub fn comments_for_hunk<'a>(
22106 &'a self,
22107 key: &DiffHunkKey,
22108 snapshot: &MultiBufferSnapshot,
22109 ) -> &'a [StoredReviewComment] {
22110 let key_point = key.hunk_start_anchor.to_point(snapshot);
22111 self.stored_review_comments
22112 .iter()
22113 .find(|(k, _)| {
22114 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22115 })
22116 .map(|(_, comments)| comments.as_slice())
22117 .unwrap_or(&[])
22118 }
22119
22120 /// Returns the total count of stored review comments across all hunks.
22121 pub fn total_review_comment_count(&self) -> usize {
22122 self.stored_review_comments
22123 .iter()
22124 .map(|(_, v)| v.len())
22125 .sum()
22126 }
22127
22128 /// Returns the count of comments for a specific hunk.
22129 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
22130 let key_point = key.hunk_start_anchor.to_point(snapshot);
22131 self.stored_review_comments
22132 .iter()
22133 .find(|(k, _)| {
22134 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22135 })
22136 .map(|(_, v)| v.len())
22137 .unwrap_or(0)
22138 }
22139
22140 /// Adds a new review comment to a specific hunk.
22141 pub fn add_review_comment(
22142 &mut self,
22143 hunk_key: DiffHunkKey,
22144 comment: String,
22145 anchor_range: Range<Anchor>,
22146 cx: &mut Context<Self>,
22147 ) -> usize {
22148 let id = self.next_review_comment_id;
22149 self.next_review_comment_id += 1;
22150
22151 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22152
22153 let snapshot = self.buffer.read(cx).snapshot(cx);
22154 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22155
22156 // Find existing entry for this hunk or add a new one
22157 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22158 k.file_path == hunk_key.file_path
22159 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22160 }) {
22161 comments.push(stored_comment);
22162 } else {
22163 self.stored_review_comments
22164 .push((hunk_key, vec![stored_comment]));
22165 }
22166
22167 cx.emit(EditorEvent::ReviewCommentsChanged {
22168 total_count: self.total_review_comment_count(),
22169 });
22170 cx.notify();
22171 id
22172 }
22173
22174 /// Removes a review comment by ID from any hunk.
22175 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22176 for (_, comments) in self.stored_review_comments.iter_mut() {
22177 if let Some(index) = comments.iter().position(|c| c.id == id) {
22178 comments.remove(index);
22179 cx.emit(EditorEvent::ReviewCommentsChanged {
22180 total_count: self.total_review_comment_count(),
22181 });
22182 cx.notify();
22183 return true;
22184 }
22185 }
22186 false
22187 }
22188
22189 /// Updates a review comment's text by ID.
22190 pub fn update_review_comment(
22191 &mut self,
22192 id: usize,
22193 new_comment: String,
22194 cx: &mut Context<Self>,
22195 ) -> bool {
22196 for (_, comments) in self.stored_review_comments.iter_mut() {
22197 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22198 comment.comment = new_comment;
22199 comment.is_editing = false;
22200 cx.emit(EditorEvent::ReviewCommentsChanged {
22201 total_count: self.total_review_comment_count(),
22202 });
22203 cx.notify();
22204 return true;
22205 }
22206 }
22207 false
22208 }
22209
22210 /// Sets a comment's editing state.
22211 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22212 for (_, comments) in self.stored_review_comments.iter_mut() {
22213 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22214 comment.is_editing = is_editing;
22215 cx.notify();
22216 return;
22217 }
22218 }
22219 }
22220
22221 /// Takes all stored comments from all hunks, clearing the storage.
22222 /// Returns a Vec of (hunk_key, comments) pairs.
22223 pub fn take_all_review_comments(
22224 &mut self,
22225 cx: &mut Context<Self>,
22226 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22227 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22228 self.dismiss_all_diff_review_overlays(cx);
22229 let comments = std::mem::take(&mut self.stored_review_comments);
22230 // Reset the ID counter since all comments have been taken
22231 self.next_review_comment_id = 0;
22232 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22233 cx.notify();
22234 comments
22235 }
22236
22237 /// Removes review comments whose anchors are no longer valid or whose
22238 /// associated diff hunks no longer exist.
22239 ///
22240 /// This should be called when the buffer changes to prevent orphaned comments
22241 /// from accumulating.
22242 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22243 let snapshot = self.buffer.read(cx).snapshot(cx);
22244 let original_count = self.total_review_comment_count();
22245
22246 // Remove comments with invalid hunk anchors
22247 self.stored_review_comments
22248 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22249
22250 // Also clean up individual comments with invalid anchor ranges
22251 for (_, comments) in &mut self.stored_review_comments {
22252 comments.retain(|comment| {
22253 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22254 });
22255 }
22256
22257 // Remove empty hunk entries
22258 self.stored_review_comments
22259 .retain(|(_, comments)| !comments.is_empty());
22260
22261 let new_count = self.total_review_comment_count();
22262 if new_count != original_count {
22263 cx.emit(EditorEvent::ReviewCommentsChanged {
22264 total_count: new_count,
22265 });
22266 cx.notify();
22267 }
22268 }
22269
22270 /// Toggles the expanded state of the comments section in the overlay.
22271 pub fn toggle_review_comments_expanded(
22272 &mut self,
22273 _: &ToggleReviewCommentsExpanded,
22274 window: &mut Window,
22275 cx: &mut Context<Self>,
22276 ) {
22277 // Find the overlay that currently has focus, or use the first one
22278 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22279 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22280 overlay.comments_expanded = !overlay.comments_expanded;
22281 Some(overlay.hunk_key.clone())
22282 } else {
22283 None
22284 }
22285 });
22286
22287 // If no focused overlay found, toggle the first one
22288 let hunk_key = overlay_info.or_else(|| {
22289 self.diff_review_overlays.first_mut().map(|overlay| {
22290 overlay.comments_expanded = !overlay.comments_expanded;
22291 overlay.hunk_key.clone()
22292 })
22293 });
22294
22295 if let Some(hunk_key) = hunk_key {
22296 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22297 cx.notify();
22298 }
22299 }
22300
22301 /// Handles the EditReviewComment action - sets a comment into editing mode.
22302 pub fn edit_review_comment(
22303 &mut self,
22304 action: &EditReviewComment,
22305 window: &mut Window,
22306 cx: &mut Context<Self>,
22307 ) {
22308 let comment_id = action.id;
22309
22310 // Set the comment to editing mode
22311 self.set_comment_editing(comment_id, true, cx);
22312
22313 // Find the overlay that contains this comment and create an inline editor if needed
22314 // First, find which hunk this comment belongs to
22315 let hunk_key = self
22316 .stored_review_comments
22317 .iter()
22318 .find_map(|(key, comments)| {
22319 if comments.iter().any(|c| c.id == comment_id) {
22320 Some(key.clone())
22321 } else {
22322 None
22323 }
22324 });
22325
22326 let snapshot = self.buffer.read(cx).snapshot(cx);
22327 if let Some(hunk_key) = hunk_key {
22328 if let Some(overlay) = self
22329 .diff_review_overlays
22330 .iter_mut()
22331 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22332 {
22333 if let std::collections::hash_map::Entry::Vacant(entry) =
22334 overlay.inline_edit_editors.entry(comment_id)
22335 {
22336 // Find the comment text
22337 let comment_text = self
22338 .stored_review_comments
22339 .iter()
22340 .flat_map(|(_, comments)| comments)
22341 .find(|c| c.id == comment_id)
22342 .map(|c| c.comment.clone())
22343 .unwrap_or_default();
22344
22345 // Create inline editor
22346 let parent_editor = cx.entity().downgrade();
22347 let inline_editor = cx.new(|cx| {
22348 let mut editor = Editor::single_line(window, cx);
22349 editor.set_text(&*comment_text, window, cx);
22350 // Select all text for easy replacement
22351 editor.select_all(&crate::actions::SelectAll, window, cx);
22352 editor
22353 });
22354
22355 // Register the Newline action to confirm the edit
22356 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22357 inline_editor.register_action({
22358 let parent_editor = parent_editor.clone();
22359 move |_: &crate::actions::Newline, window, cx| {
22360 if let Some(editor) = parent_editor.upgrade() {
22361 editor.update(cx, |editor, cx| {
22362 editor.confirm_edit_review_comment(comment_id, window, cx);
22363 });
22364 }
22365 }
22366 })
22367 });
22368
22369 // Store the subscription to keep the action handler alive
22370 overlay
22371 .inline_edit_subscriptions
22372 .insert(comment_id, subscription);
22373
22374 // Focus the inline editor
22375 let focus_handle = inline_editor.focus_handle(cx);
22376 window.focus(&focus_handle, cx);
22377
22378 entry.insert(inline_editor);
22379 }
22380 }
22381 }
22382
22383 cx.notify();
22384 }
22385
22386 /// Confirms an inline edit of a review comment.
22387 pub fn confirm_edit_review_comment(
22388 &mut self,
22389 comment_id: usize,
22390 _window: &mut Window,
22391 cx: &mut Context<Self>,
22392 ) {
22393 // Get the new text from the inline editor
22394 // Find the overlay containing this comment's inline editor
22395 let snapshot = self.buffer.read(cx).snapshot(cx);
22396 let hunk_key = self
22397 .stored_review_comments
22398 .iter()
22399 .find_map(|(key, comments)| {
22400 if comments.iter().any(|c| c.id == comment_id) {
22401 Some(key.clone())
22402 } else {
22403 None
22404 }
22405 });
22406
22407 let new_text = hunk_key
22408 .as_ref()
22409 .and_then(|hunk_key| {
22410 self.diff_review_overlays
22411 .iter()
22412 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22413 })
22414 .as_ref()
22415 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22416 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22417
22418 if let Some(new_text) = new_text {
22419 if !new_text.is_empty() {
22420 self.update_review_comment(comment_id, new_text, cx);
22421 }
22422 }
22423
22424 // Remove the inline editor and its subscription
22425 if let Some(hunk_key) = hunk_key {
22426 if let Some(overlay) = self
22427 .diff_review_overlays
22428 .iter_mut()
22429 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22430 {
22431 overlay.inline_edit_editors.remove(&comment_id);
22432 overlay.inline_edit_subscriptions.remove(&comment_id);
22433 }
22434 }
22435
22436 // Clear editing state
22437 self.set_comment_editing(comment_id, false, cx);
22438 }
22439
22440 /// Cancels an inline edit of a review comment.
22441 pub fn cancel_edit_review_comment(
22442 &mut self,
22443 comment_id: usize,
22444 _window: &mut Window,
22445 cx: &mut Context<Self>,
22446 ) {
22447 // Find which hunk this comment belongs to
22448 let hunk_key = self
22449 .stored_review_comments
22450 .iter()
22451 .find_map(|(key, comments)| {
22452 if comments.iter().any(|c| c.id == comment_id) {
22453 Some(key.clone())
22454 } else {
22455 None
22456 }
22457 });
22458
22459 // Remove the inline editor and its subscription
22460 if let Some(hunk_key) = hunk_key {
22461 let snapshot = self.buffer.read(cx).snapshot(cx);
22462 if let Some(overlay) = self
22463 .diff_review_overlays
22464 .iter_mut()
22465 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22466 {
22467 overlay.inline_edit_editors.remove(&comment_id);
22468 overlay.inline_edit_subscriptions.remove(&comment_id);
22469 }
22470 }
22471
22472 // Clear editing state
22473 self.set_comment_editing(comment_id, false, cx);
22474 }
22475
22476 /// Action handler for ConfirmEditReviewComment.
22477 pub fn confirm_edit_review_comment_action(
22478 &mut self,
22479 action: &ConfirmEditReviewComment,
22480 window: &mut Window,
22481 cx: &mut Context<Self>,
22482 ) {
22483 self.confirm_edit_review_comment(action.id, window, cx);
22484 }
22485
22486 /// Action handler for CancelEditReviewComment.
22487 pub fn cancel_edit_review_comment_action(
22488 &mut self,
22489 action: &CancelEditReviewComment,
22490 window: &mut Window,
22491 cx: &mut Context<Self>,
22492 ) {
22493 self.cancel_edit_review_comment(action.id, window, cx);
22494 }
22495
22496 /// Handles the DeleteReviewComment action - removes a comment.
22497 pub fn delete_review_comment(
22498 &mut self,
22499 action: &DeleteReviewComment,
22500 window: &mut Window,
22501 cx: &mut Context<Self>,
22502 ) {
22503 // Get the hunk key before removing the comment
22504 // Find the hunk key from the comment itself
22505 let comment_id = action.id;
22506 let hunk_key = self
22507 .stored_review_comments
22508 .iter()
22509 .find_map(|(key, comments)| {
22510 if comments.iter().any(|c| c.id == comment_id) {
22511 Some(key.clone())
22512 } else {
22513 None
22514 }
22515 });
22516
22517 // Also get it from the overlay for refresh purposes
22518 let overlay_hunk_key = self
22519 .diff_review_overlays
22520 .first()
22521 .map(|o| o.hunk_key.clone());
22522
22523 self.remove_review_comment(action.id, cx);
22524
22525 // Refresh the overlay height after removing a comment
22526 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22527 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22528 }
22529 }
22530
22531 fn render_diff_review_overlay(
22532 prompt_editor: &Entity<Editor>,
22533 hunk_key: &DiffHunkKey,
22534 editor_handle: &WeakEntity<Editor>,
22535 cx: &mut BlockContext,
22536 ) -> AnyElement {
22537 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22538 if ranges.is_empty() {
22539 return None;
22540 }
22541 let formatted: Vec<String> = ranges
22542 .iter()
22543 .map(|(start, end)| {
22544 let start_line = start + 1;
22545 let end_line = end + 1;
22546 if start_line == end_line {
22547 format!("Line {start_line}")
22548 } else {
22549 format!("Lines {start_line}-{end_line}")
22550 }
22551 })
22552 .collect();
22553 // Don't show label for single line in single excerpt
22554 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22555 return None;
22556 }
22557 Some(formatted.join(" ⋯ "))
22558 }
22559
22560 let theme = cx.theme();
22561 let colors = theme.colors();
22562
22563 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22564 editor_handle
22565 .upgrade()
22566 .map(|editor| {
22567 let editor = editor.read(cx);
22568 let snapshot = editor.buffer().read(cx).snapshot(cx);
22569 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22570 let (expanded, editors, avatar_uri, line_ranges) = editor
22571 .diff_review_overlays
22572 .iter()
22573 .find(|overlay| {
22574 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22575 })
22576 .map(|o| {
22577 let start_point = o.anchor_range.start.to_point(&snapshot);
22578 let end_point = o.anchor_range.end.to_point(&snapshot);
22579 // Get line ranges per excerpt to detect discontinuities
22580 let buffer_ranges =
22581 snapshot.range_to_buffer_ranges(start_point..end_point);
22582 let ranges: Vec<(u32, u32)> = buffer_ranges
22583 .iter()
22584 .map(|(buffer, range, _)| {
22585 let start = buffer.offset_to_point(range.start.0).row;
22586 let end = buffer.offset_to_point(range.end.0).row;
22587 (start, end)
22588 })
22589 .collect();
22590 (
22591 o.comments_expanded,
22592 o.inline_edit_editors.clone(),
22593 o.user_avatar_uri.clone(),
22594 if ranges.is_empty() {
22595 None
22596 } else {
22597 Some(ranges)
22598 },
22599 )
22600 })
22601 .unwrap_or((true, HashMap::default(), None, None));
22602 (comments, expanded, editors, avatar_uri, line_ranges)
22603 })
22604 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22605
22606 let comment_count = comments.len();
22607 let avatar_size = px(20.);
22608 let action_icon_size = IconSize::XSmall;
22609
22610 v_flex()
22611 .w_full()
22612 .bg(colors.editor_background)
22613 .border_b_1()
22614 .border_color(colors.border)
22615 .px_2()
22616 .pb_2()
22617 .gap_2()
22618 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22619 .when_some(line_ranges, |el, ranges| {
22620 let label = format_line_ranges(&ranges);
22621 if let Some(label) = label {
22622 el.child(
22623 h_flex()
22624 .w_full()
22625 .px_2()
22626 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22627 )
22628 } else {
22629 el
22630 }
22631 })
22632 // Top row: editable input with user's avatar
22633 .child(
22634 h_flex()
22635 .w_full()
22636 .items_center()
22637 .gap_2()
22638 .px_2()
22639 .py_1p5()
22640 .rounded_md()
22641 .bg(colors.surface_background)
22642 .child(
22643 div()
22644 .size(avatar_size)
22645 .flex_shrink_0()
22646 .rounded_full()
22647 .overflow_hidden()
22648 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22649 Avatar::new(avatar_uri.clone())
22650 .size(avatar_size)
22651 .into_any_element()
22652 } else {
22653 Icon::new(IconName::Person)
22654 .size(IconSize::Small)
22655 .color(ui::Color::Muted)
22656 .into_any_element()
22657 }),
22658 )
22659 .child(
22660 div()
22661 .flex_1()
22662 .border_1()
22663 .border_color(colors.border)
22664 .rounded_md()
22665 .bg(colors.editor_background)
22666 .px_2()
22667 .py_1()
22668 .child(prompt_editor.clone()),
22669 )
22670 .child(
22671 h_flex()
22672 .flex_shrink_0()
22673 .gap_1()
22674 .child(
22675 IconButton::new("diff-review-close", IconName::Close)
22676 .icon_color(ui::Color::Muted)
22677 .icon_size(action_icon_size)
22678 .tooltip(Tooltip::text("Close"))
22679 .on_click(|_, window, cx| {
22680 window
22681 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22682 }),
22683 )
22684 .child(
22685 IconButton::new("diff-review-add", IconName::Return)
22686 .icon_color(ui::Color::Muted)
22687 .icon_size(action_icon_size)
22688 .tooltip(Tooltip::text("Add comment"))
22689 .on_click(|_, window, cx| {
22690 window.dispatch_action(
22691 Box::new(crate::actions::SubmitDiffReviewComment),
22692 cx,
22693 );
22694 }),
22695 ),
22696 ),
22697 )
22698 // Expandable comments section (only shown when there are comments)
22699 .when(comment_count > 0, |el| {
22700 el.child(Self::render_comments_section(
22701 comments,
22702 comments_expanded,
22703 inline_editors,
22704 user_avatar_uri,
22705 avatar_size,
22706 action_icon_size,
22707 colors,
22708 ))
22709 })
22710 .into_any_element()
22711 }
22712
22713 fn render_comments_section(
22714 comments: Vec<StoredReviewComment>,
22715 expanded: bool,
22716 inline_editors: HashMap<usize, Entity<Editor>>,
22717 user_avatar_uri: Option<SharedUri>,
22718 avatar_size: Pixels,
22719 action_icon_size: IconSize,
22720 colors: &theme::ThemeColors,
22721 ) -> impl IntoElement {
22722 let comment_count = comments.len();
22723
22724 v_flex()
22725 .w_full()
22726 .gap_1()
22727 // Header with expand/collapse toggle
22728 .child(
22729 h_flex()
22730 .id("review-comments-header")
22731 .w_full()
22732 .items_center()
22733 .gap_1()
22734 .px_2()
22735 .py_1()
22736 .cursor_pointer()
22737 .rounded_md()
22738 .hover(|style| style.bg(colors.ghost_element_hover))
22739 .on_click(|_, window: &mut Window, cx| {
22740 window.dispatch_action(
22741 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22742 cx,
22743 );
22744 })
22745 .child(
22746 Icon::new(if expanded {
22747 IconName::ChevronDown
22748 } else {
22749 IconName::ChevronRight
22750 })
22751 .size(IconSize::Small)
22752 .color(ui::Color::Muted),
22753 )
22754 .child(
22755 Label::new(format!(
22756 "{} Comment{}",
22757 comment_count,
22758 if comment_count == 1 { "" } else { "s" }
22759 ))
22760 .size(LabelSize::Small)
22761 .color(Color::Muted),
22762 ),
22763 )
22764 // Comments list (when expanded)
22765 .when(expanded, |el| {
22766 el.children(comments.into_iter().map(|comment| {
22767 let inline_editor = inline_editors.get(&comment.id).cloned();
22768 Self::render_comment_row(
22769 comment,
22770 inline_editor,
22771 user_avatar_uri.clone(),
22772 avatar_size,
22773 action_icon_size,
22774 colors,
22775 )
22776 }))
22777 })
22778 }
22779
22780 fn render_comment_row(
22781 comment: StoredReviewComment,
22782 inline_editor: Option<Entity<Editor>>,
22783 user_avatar_uri: Option<SharedUri>,
22784 avatar_size: Pixels,
22785 action_icon_size: IconSize,
22786 colors: &theme::ThemeColors,
22787 ) -> impl IntoElement {
22788 let comment_id = comment.id;
22789 let is_editing = inline_editor.is_some();
22790
22791 h_flex()
22792 .w_full()
22793 .items_center()
22794 .gap_2()
22795 .px_2()
22796 .py_1p5()
22797 .rounded_md()
22798 .bg(colors.surface_background)
22799 .child(
22800 div()
22801 .size(avatar_size)
22802 .flex_shrink_0()
22803 .rounded_full()
22804 .overflow_hidden()
22805 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22806 Avatar::new(avatar_uri.clone())
22807 .size(avatar_size)
22808 .into_any_element()
22809 } else {
22810 Icon::new(IconName::Person)
22811 .size(IconSize::Small)
22812 .color(ui::Color::Muted)
22813 .into_any_element()
22814 }),
22815 )
22816 .child(if let Some(editor) = inline_editor {
22817 // Inline edit mode: show an editable text field
22818 div()
22819 .flex_1()
22820 .border_1()
22821 .border_color(colors.border)
22822 .rounded_md()
22823 .bg(colors.editor_background)
22824 .px_2()
22825 .py_1()
22826 .child(editor)
22827 .into_any_element()
22828 } else {
22829 // Display mode: show the comment text
22830 div()
22831 .flex_1()
22832 .text_sm()
22833 .text_color(colors.text)
22834 .child(comment.comment)
22835 .into_any_element()
22836 })
22837 .child(if is_editing {
22838 // Editing mode: show close and confirm buttons
22839 h_flex()
22840 .gap_1()
22841 .child(
22842 IconButton::new(
22843 format!("diff-review-cancel-edit-{comment_id}"),
22844 IconName::Close,
22845 )
22846 .icon_color(ui::Color::Muted)
22847 .icon_size(action_icon_size)
22848 .tooltip(Tooltip::text("Cancel"))
22849 .on_click(move |_, window, cx| {
22850 window.dispatch_action(
22851 Box::new(crate::actions::CancelEditReviewComment {
22852 id: comment_id,
22853 }),
22854 cx,
22855 );
22856 }),
22857 )
22858 .child(
22859 IconButton::new(
22860 format!("diff-review-confirm-edit-{comment_id}"),
22861 IconName::Return,
22862 )
22863 .icon_color(ui::Color::Muted)
22864 .icon_size(action_icon_size)
22865 .tooltip(Tooltip::text("Confirm"))
22866 .on_click(move |_, window, cx| {
22867 window.dispatch_action(
22868 Box::new(crate::actions::ConfirmEditReviewComment {
22869 id: comment_id,
22870 }),
22871 cx,
22872 );
22873 }),
22874 )
22875 .into_any_element()
22876 } else {
22877 // Display mode: no action buttons for now (edit/delete not yet implemented)
22878 gpui::Empty.into_any_element()
22879 })
22880 }
22881
22882 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22883 if self.display_map.read(cx).masked != masked {
22884 self.display_map.update(cx, |map, _| map.masked = masked);
22885 }
22886 cx.notify()
22887 }
22888
22889 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22890 self.show_wrap_guides = Some(show_wrap_guides);
22891 cx.notify();
22892 }
22893
22894 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22895 self.show_indent_guides = Some(show_indent_guides);
22896 cx.notify();
22897 }
22898
22899 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22900 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22901 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22902 && let Some(dir) = file.abs_path(cx).parent()
22903 {
22904 return Some(dir.to_owned());
22905 }
22906 }
22907
22908 None
22909 }
22910
22911 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22912 self.active_excerpt(cx)?
22913 .1
22914 .read(cx)
22915 .file()
22916 .and_then(|f| f.as_local())
22917 }
22918
22919 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22920 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22921 let buffer = buffer.read(cx);
22922 if let Some(project_path) = buffer.project_path(cx) {
22923 let project = self.project()?.read(cx);
22924 project.absolute_path(&project_path, cx)
22925 } else {
22926 buffer
22927 .file()
22928 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22929 }
22930 })
22931 }
22932
22933 pub fn reveal_in_finder(
22934 &mut self,
22935 _: &RevealInFileManager,
22936 _window: &mut Window,
22937 cx: &mut Context<Self>,
22938 ) {
22939 if let Some(path) = self.target_file_abs_path(cx) {
22940 if let Some(project) = self.project() {
22941 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22942 } else {
22943 cx.reveal_path(&path);
22944 }
22945 }
22946 }
22947
22948 pub fn copy_path(
22949 &mut self,
22950 _: &zed_actions::workspace::CopyPath,
22951 _window: &mut Window,
22952 cx: &mut Context<Self>,
22953 ) {
22954 if let Some(path) = self.target_file_abs_path(cx)
22955 && let Some(path) = path.to_str()
22956 {
22957 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22958 } else {
22959 cx.propagate();
22960 }
22961 }
22962
22963 pub fn copy_relative_path(
22964 &mut self,
22965 _: &zed_actions::workspace::CopyRelativePath,
22966 _window: &mut Window,
22967 cx: &mut Context<Self>,
22968 ) {
22969 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22970 let project = self.project()?.read(cx);
22971 let path = buffer.read(cx).file()?.path();
22972 let path = path.display(project.path_style(cx));
22973 Some(path)
22974 }) {
22975 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22976 } else {
22977 cx.propagate();
22978 }
22979 }
22980
22981 /// Returns the project path for the editor's buffer, if any buffer is
22982 /// opened in the editor.
22983 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22984 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22985 buffer.read(cx).project_path(cx)
22986 } else {
22987 None
22988 }
22989 }
22990
22991 // Returns true if the editor handled a go-to-line request
22992 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22993 maybe!({
22994 let breakpoint_store = self.breakpoint_store.as_ref()?;
22995
22996 let (active_stack_frame, debug_line_pane_id) = {
22997 let store = breakpoint_store.read(cx);
22998 let active_stack_frame = store.active_position().cloned();
22999 let debug_line_pane_id = store.active_debug_line_pane_id();
23000 (active_stack_frame, debug_line_pane_id)
23001 };
23002
23003 let Some(active_stack_frame) = active_stack_frame else {
23004 self.clear_row_highlights::<ActiveDebugLine>();
23005 return None;
23006 };
23007
23008 if let Some(debug_line_pane_id) = debug_line_pane_id {
23009 if let Some(workspace) = self
23010 .workspace
23011 .as_ref()
23012 .and_then(|(workspace, _)| workspace.upgrade())
23013 {
23014 let editor_pane_id = workspace
23015 .read(cx)
23016 .pane_for_item_id(cx.entity_id())
23017 .map(|pane| pane.entity_id());
23018
23019 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
23020 self.clear_row_highlights::<ActiveDebugLine>();
23021 return None;
23022 }
23023 }
23024 }
23025
23026 let position = active_stack_frame.position;
23027 let buffer_id = position.buffer_id?;
23028 let snapshot = self
23029 .project
23030 .as_ref()?
23031 .read(cx)
23032 .buffer_for_id(buffer_id, cx)?
23033 .read(cx)
23034 .snapshot();
23035
23036 let mut handled = false;
23037 for (id, _, ExcerptRange { context, .. }) in
23038 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
23039 {
23040 if context.start.cmp(&position, &snapshot).is_ge()
23041 || context.end.cmp(&position, &snapshot).is_lt()
23042 {
23043 continue;
23044 }
23045 let snapshot = self.buffer.read(cx).snapshot(cx);
23046 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
23047
23048 handled = true;
23049 self.clear_row_highlights::<ActiveDebugLine>();
23050
23051 self.go_to_line::<ActiveDebugLine>(
23052 multibuffer_anchor,
23053 Some(cx.theme().colors().editor_debugger_active_line_background),
23054 window,
23055 cx,
23056 );
23057
23058 cx.notify();
23059 }
23060
23061 handled.then_some(())
23062 })
23063 .is_some()
23064 }
23065
23066 pub fn copy_file_name_without_extension(
23067 &mut self,
23068 _: &CopyFileNameWithoutExtension,
23069 _: &mut Window,
23070 cx: &mut Context<Self>,
23071 ) {
23072 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23073 let file = buffer.read(cx).file()?;
23074 file.path().file_stem()
23075 }) {
23076 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
23077 }
23078 }
23079
23080 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
23081 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23082 let file = buffer.read(cx).file()?;
23083 Some(file.file_name(cx))
23084 }) {
23085 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
23086 }
23087 }
23088
23089 pub fn toggle_git_blame(
23090 &mut self,
23091 _: &::git::Blame,
23092 window: &mut Window,
23093 cx: &mut Context<Self>,
23094 ) {
23095 self.show_git_blame_gutter = !self.show_git_blame_gutter;
23096
23097 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
23098 self.start_git_blame(true, window, cx);
23099 }
23100
23101 cx.notify();
23102 }
23103
23104 pub fn toggle_git_blame_inline(
23105 &mut self,
23106 _: &ToggleGitBlameInline,
23107 window: &mut Window,
23108 cx: &mut Context<Self>,
23109 ) {
23110 self.toggle_git_blame_inline_internal(true, window, cx);
23111 cx.notify();
23112 }
23113
23114 pub fn open_git_blame_commit(
23115 &mut self,
23116 _: &OpenGitBlameCommit,
23117 window: &mut Window,
23118 cx: &mut Context<Self>,
23119 ) {
23120 self.open_git_blame_commit_internal(window, cx);
23121 }
23122
23123 fn open_git_blame_commit_internal(
23124 &mut self,
23125 window: &mut Window,
23126 cx: &mut Context<Self>,
23127 ) -> Option<()> {
23128 let blame = self.blame.as_ref()?;
23129 let snapshot = self.snapshot(window, cx);
23130 let cursor = self
23131 .selections
23132 .newest::<Point>(&snapshot.display_snapshot)
23133 .head();
23134 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
23135 let (_, blame_entry) = blame
23136 .update(cx, |blame, cx| {
23137 blame
23138 .blame_for_rows(
23139 &[RowInfo {
23140 buffer_id: Some(buffer.remote_id()),
23141 buffer_row: Some(point.row),
23142 ..Default::default()
23143 }],
23144 cx,
23145 )
23146 .next()
23147 })
23148 .flatten()?;
23149 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23150 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23151 let workspace = self.workspace()?.downgrade();
23152 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23153 None
23154 }
23155
23156 pub fn git_blame_inline_enabled(&self) -> bool {
23157 self.git_blame_inline_enabled
23158 }
23159
23160 pub fn toggle_selection_menu(
23161 &mut self,
23162 _: &ToggleSelectionMenu,
23163 _: &mut Window,
23164 cx: &mut Context<Self>,
23165 ) {
23166 self.show_selection_menu = self
23167 .show_selection_menu
23168 .map(|show_selections_menu| !show_selections_menu)
23169 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23170
23171 cx.notify();
23172 }
23173
23174 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23175 self.show_selection_menu
23176 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23177 }
23178
23179 fn start_git_blame(
23180 &mut self,
23181 user_triggered: bool,
23182 window: &mut Window,
23183 cx: &mut Context<Self>,
23184 ) {
23185 if let Some(project) = self.project() {
23186 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23187 && buffer.read(cx).file().is_none()
23188 {
23189 return;
23190 }
23191
23192 let focused = self.focus_handle(cx).contains_focused(window, cx);
23193
23194 let project = project.clone();
23195 let blame = cx
23196 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23197 self.blame_subscription =
23198 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23199 self.blame = Some(blame);
23200 }
23201 }
23202
23203 fn toggle_git_blame_inline_internal(
23204 &mut self,
23205 user_triggered: bool,
23206 window: &mut Window,
23207 cx: &mut Context<Self>,
23208 ) {
23209 if self.git_blame_inline_enabled {
23210 self.git_blame_inline_enabled = false;
23211 self.show_git_blame_inline = false;
23212 self.show_git_blame_inline_delay_task.take();
23213 } else {
23214 self.git_blame_inline_enabled = true;
23215 self.start_git_blame_inline(user_triggered, window, cx);
23216 }
23217
23218 cx.notify();
23219 }
23220
23221 fn start_git_blame_inline(
23222 &mut self,
23223 user_triggered: bool,
23224 window: &mut Window,
23225 cx: &mut Context<Self>,
23226 ) {
23227 self.start_git_blame(user_triggered, window, cx);
23228
23229 if ProjectSettings::get_global(cx)
23230 .git
23231 .inline_blame_delay()
23232 .is_some()
23233 {
23234 self.start_inline_blame_timer(window, cx);
23235 } else {
23236 self.show_git_blame_inline = true
23237 }
23238 }
23239
23240 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23241 self.blame.as_ref()
23242 }
23243
23244 pub fn show_git_blame_gutter(&self) -> bool {
23245 self.show_git_blame_gutter
23246 }
23247
23248 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23249 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23250 }
23251
23252 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23253 self.show_git_blame_inline
23254 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23255 && !self.newest_selection_head_on_empty_line(cx)
23256 && self.has_blame_entries(cx)
23257 }
23258
23259 fn has_blame_entries(&self, cx: &App) -> bool {
23260 self.blame()
23261 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23262 }
23263
23264 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23265 let cursor_anchor = self.selections.newest_anchor().head();
23266
23267 let snapshot = self.buffer.read(cx).snapshot(cx);
23268 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23269
23270 snapshot.line_len(buffer_row) == 0
23271 }
23272
23273 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23274 let buffer_and_selection = maybe!({
23275 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23276 let selection_range = selection.range();
23277
23278 let multi_buffer = self.buffer().read(cx);
23279 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23280 let buffer_ranges = multi_buffer_snapshot
23281 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23282
23283 let (buffer, range, _) = if selection.reversed {
23284 buffer_ranges.first()
23285 } else {
23286 buffer_ranges.last()
23287 }?;
23288
23289 let buffer_range = range.to_point(buffer);
23290
23291 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23292 return Some((
23293 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23294 buffer_range.start.row..buffer_range.end.row,
23295 ));
23296 };
23297
23298 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23299 let start =
23300 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23301 let end =
23302 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23303
23304 Some((
23305 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23306 start.row..end.row,
23307 ))
23308 });
23309
23310 let Some((buffer, selection)) = buffer_and_selection else {
23311 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23312 };
23313
23314 let Some(project) = self.project() else {
23315 return Task::ready(Err(anyhow!("editor does not have project")));
23316 };
23317
23318 project.update(cx, |project, cx| {
23319 project.get_permalink_to_line(&buffer, selection, cx)
23320 })
23321 }
23322
23323 pub fn copy_permalink_to_line(
23324 &mut self,
23325 _: &CopyPermalinkToLine,
23326 window: &mut Window,
23327 cx: &mut Context<Self>,
23328 ) {
23329 let permalink_task = self.get_permalink_to_line(cx);
23330 let workspace = self.workspace();
23331
23332 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23333 Ok(permalink) => {
23334 cx.update(|_, cx| {
23335 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23336 })
23337 .ok();
23338 }
23339 Err(err) => {
23340 let message = format!("Failed to copy permalink: {err}");
23341
23342 anyhow::Result::<()>::Err(err).log_err();
23343
23344 if let Some(workspace) = workspace {
23345 workspace
23346 .update_in(cx, |workspace, _, cx| {
23347 struct CopyPermalinkToLine;
23348
23349 workspace.show_toast(
23350 Toast::new(
23351 NotificationId::unique::<CopyPermalinkToLine>(),
23352 message,
23353 ),
23354 cx,
23355 )
23356 })
23357 .ok();
23358 }
23359 }
23360 })
23361 .detach();
23362 }
23363
23364 pub fn copy_file_location(
23365 &mut self,
23366 _: &CopyFileLocation,
23367 _: &mut Window,
23368 cx: &mut Context<Self>,
23369 ) {
23370 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23371
23372 let start_line = selection.start.row + 1;
23373 let end_line = selection.end.row + 1;
23374
23375 let end_line = if selection.end.column == 0 && end_line > start_line {
23376 end_line - 1
23377 } else {
23378 end_line
23379 };
23380
23381 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23382 let project = self.project()?.read(cx);
23383 let file = buffer.read(cx).file()?;
23384 let path = file.path().display(project.path_style(cx));
23385
23386 let location = if start_line == end_line {
23387 format!("{path}:{start_line}")
23388 } else {
23389 format!("{path}:{start_line}-{end_line}")
23390 };
23391 Some(location)
23392 }) {
23393 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23394 }
23395 }
23396
23397 pub fn open_permalink_to_line(
23398 &mut self,
23399 _: &OpenPermalinkToLine,
23400 window: &mut Window,
23401 cx: &mut Context<Self>,
23402 ) {
23403 let permalink_task = self.get_permalink_to_line(cx);
23404 let workspace = self.workspace();
23405
23406 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23407 Ok(permalink) => {
23408 cx.update(|_, cx| {
23409 cx.open_url(permalink.as_ref());
23410 })
23411 .ok();
23412 }
23413 Err(err) => {
23414 let message = format!("Failed to open permalink: {err}");
23415
23416 anyhow::Result::<()>::Err(err).log_err();
23417
23418 if let Some(workspace) = workspace {
23419 workspace.update(cx, |workspace, cx| {
23420 struct OpenPermalinkToLine;
23421
23422 workspace.show_toast(
23423 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23424 cx,
23425 )
23426 });
23427 }
23428 }
23429 })
23430 .detach();
23431 }
23432
23433 pub fn insert_uuid_v4(
23434 &mut self,
23435 _: &InsertUuidV4,
23436 window: &mut Window,
23437 cx: &mut Context<Self>,
23438 ) {
23439 self.insert_uuid(UuidVersion::V4, window, cx);
23440 }
23441
23442 pub fn insert_uuid_v7(
23443 &mut self,
23444 _: &InsertUuidV7,
23445 window: &mut Window,
23446 cx: &mut Context<Self>,
23447 ) {
23448 self.insert_uuid(UuidVersion::V7, window, cx);
23449 }
23450
23451 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23452 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23453 self.transact(window, cx, |this, window, cx| {
23454 let edits = this
23455 .selections
23456 .all::<Point>(&this.display_snapshot(cx))
23457 .into_iter()
23458 .map(|selection| {
23459 let uuid = match version {
23460 UuidVersion::V4 => uuid::Uuid::new_v4(),
23461 UuidVersion::V7 => uuid::Uuid::now_v7(),
23462 };
23463
23464 (selection.range(), uuid.to_string())
23465 });
23466 this.edit(edits, cx);
23467 this.refresh_edit_prediction(true, false, window, cx);
23468 });
23469 }
23470
23471 pub fn open_selections_in_multibuffer(
23472 &mut self,
23473 _: &OpenSelectionsInMultibuffer,
23474 window: &mut Window,
23475 cx: &mut Context<Self>,
23476 ) {
23477 let multibuffer = self.buffer.read(cx);
23478
23479 let Some(buffer) = multibuffer.as_singleton() else {
23480 return;
23481 };
23482
23483 let Some(workspace) = self.workspace() else {
23484 return;
23485 };
23486
23487 let title = multibuffer.title(cx).to_string();
23488
23489 let locations = self
23490 .selections
23491 .all_anchors(&self.display_snapshot(cx))
23492 .iter()
23493 .map(|selection| {
23494 (
23495 buffer.clone(),
23496 (selection.start.text_anchor..selection.end.text_anchor)
23497 .to_point(buffer.read(cx)),
23498 )
23499 })
23500 .into_group_map();
23501
23502 cx.spawn_in(window, async move |_, cx| {
23503 workspace.update_in(cx, |workspace, window, cx| {
23504 Self::open_locations_in_multibuffer(
23505 workspace,
23506 locations,
23507 format!("Selections for '{title}'"),
23508 false,
23509 false,
23510 MultibufferSelectionMode::All,
23511 window,
23512 cx,
23513 );
23514 })
23515 })
23516 .detach();
23517 }
23518
23519 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23520 /// last highlight added will be used.
23521 ///
23522 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23523 pub fn highlight_rows<T: 'static>(
23524 &mut self,
23525 range: Range<Anchor>,
23526 color: Hsla,
23527 options: RowHighlightOptions,
23528 cx: &mut Context<Self>,
23529 ) {
23530 let snapshot = self.buffer().read(cx).snapshot(cx);
23531 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23532 let ix = row_highlights.binary_search_by(|highlight| {
23533 Ordering::Equal
23534 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23535 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23536 });
23537
23538 if let Err(mut ix) = ix {
23539 let index = post_inc(&mut self.highlight_order);
23540
23541 // If this range intersects with the preceding highlight, then merge it with
23542 // the preceding highlight. Otherwise insert a new highlight.
23543 let mut merged = false;
23544 if ix > 0 {
23545 let prev_highlight = &mut row_highlights[ix - 1];
23546 if prev_highlight
23547 .range
23548 .end
23549 .cmp(&range.start, &snapshot)
23550 .is_ge()
23551 {
23552 ix -= 1;
23553 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23554 prev_highlight.range.end = range.end;
23555 }
23556 merged = true;
23557 prev_highlight.index = index;
23558 prev_highlight.color = color;
23559 prev_highlight.options = options;
23560 }
23561 }
23562
23563 if !merged {
23564 row_highlights.insert(
23565 ix,
23566 RowHighlight {
23567 range,
23568 index,
23569 color,
23570 options,
23571 type_id: TypeId::of::<T>(),
23572 },
23573 );
23574 }
23575
23576 // If any of the following highlights intersect with this one, merge them.
23577 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23578 let highlight = &row_highlights[ix];
23579 if next_highlight
23580 .range
23581 .start
23582 .cmp(&highlight.range.end, &snapshot)
23583 .is_le()
23584 {
23585 if next_highlight
23586 .range
23587 .end
23588 .cmp(&highlight.range.end, &snapshot)
23589 .is_gt()
23590 {
23591 row_highlights[ix].range.end = next_highlight.range.end;
23592 }
23593 row_highlights.remove(ix + 1);
23594 } else {
23595 break;
23596 }
23597 }
23598 }
23599 }
23600
23601 /// Remove any highlighted row ranges of the given type that intersect the
23602 /// given ranges.
23603 pub fn remove_highlighted_rows<T: 'static>(
23604 &mut self,
23605 ranges_to_remove: Vec<Range<Anchor>>,
23606 cx: &mut Context<Self>,
23607 ) {
23608 let snapshot = self.buffer().read(cx).snapshot(cx);
23609 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23610 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23611 row_highlights.retain(|highlight| {
23612 while let Some(range_to_remove) = ranges_to_remove.peek() {
23613 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23614 Ordering::Less | Ordering::Equal => {
23615 ranges_to_remove.next();
23616 }
23617 Ordering::Greater => {
23618 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23619 Ordering::Less | Ordering::Equal => {
23620 return false;
23621 }
23622 Ordering::Greater => break,
23623 }
23624 }
23625 }
23626 }
23627
23628 true
23629 })
23630 }
23631
23632 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23633 pub fn clear_row_highlights<T: 'static>(&mut self) {
23634 self.highlighted_rows.remove(&TypeId::of::<T>());
23635 }
23636
23637 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23638 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23639 self.highlighted_rows
23640 .get(&TypeId::of::<T>())
23641 .map_or(&[] as &[_], |vec| vec.as_slice())
23642 .iter()
23643 .map(|highlight| (highlight.range.clone(), highlight.color))
23644 }
23645
23646 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23647 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23648 /// Allows to ignore certain kinds of highlights.
23649 pub fn highlighted_display_rows(
23650 &self,
23651 window: &mut Window,
23652 cx: &mut App,
23653 ) -> BTreeMap<DisplayRow, LineHighlight> {
23654 let snapshot = self.snapshot(window, cx);
23655 let mut used_highlight_orders = HashMap::default();
23656 self.highlighted_rows
23657 .iter()
23658 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23659 .fold(
23660 BTreeMap::<DisplayRow, LineHighlight>::new(),
23661 |mut unique_rows, highlight| {
23662 let start = highlight.range.start.to_display_point(&snapshot);
23663 let end = highlight.range.end.to_display_point(&snapshot);
23664 let start_row = start.row().0;
23665 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23666 {
23667 end.row().0.saturating_sub(1)
23668 } else {
23669 end.row().0
23670 };
23671 for row in start_row..=end_row {
23672 let used_index =
23673 used_highlight_orders.entry(row).or_insert(highlight.index);
23674 if highlight.index >= *used_index {
23675 *used_index = highlight.index;
23676 unique_rows.insert(
23677 DisplayRow(row),
23678 LineHighlight {
23679 include_gutter: highlight.options.include_gutter,
23680 border: None,
23681 background: highlight.color.into(),
23682 type_id: Some(highlight.type_id),
23683 },
23684 );
23685 }
23686 }
23687 unique_rows
23688 },
23689 )
23690 }
23691
23692 pub fn highlighted_display_row_for_autoscroll(
23693 &self,
23694 snapshot: &DisplaySnapshot,
23695 ) -> Option<DisplayRow> {
23696 self.highlighted_rows
23697 .values()
23698 .flat_map(|highlighted_rows| highlighted_rows.iter())
23699 .filter_map(|highlight| {
23700 if highlight.options.autoscroll {
23701 Some(highlight.range.start.to_display_point(snapshot).row())
23702 } else {
23703 None
23704 }
23705 })
23706 .min()
23707 }
23708
23709 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23710 self.highlight_background(
23711 HighlightKey::SearchWithinRange,
23712 ranges,
23713 |_, colors| colors.colors().editor_document_highlight_read_background,
23714 cx,
23715 )
23716 }
23717
23718 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23719 self.breadcrumb_header = Some(new_header);
23720 }
23721
23722 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23723 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23724 }
23725
23726 pub fn highlight_background(
23727 &mut self,
23728 key: HighlightKey,
23729 ranges: &[Range<Anchor>],
23730 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23731 cx: &mut Context<Self>,
23732 ) {
23733 self.background_highlights
23734 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23735 self.scrollbar_marker_state.dirty = true;
23736 cx.notify();
23737 }
23738
23739 pub fn clear_background_highlights(
23740 &mut self,
23741 key: HighlightKey,
23742 cx: &mut Context<Self>,
23743 ) -> Option<BackgroundHighlight> {
23744 let text_highlights = self.background_highlights.remove(&key)?;
23745 if !text_highlights.1.is_empty() {
23746 self.scrollbar_marker_state.dirty = true;
23747 cx.notify();
23748 }
23749 Some(text_highlights)
23750 }
23751
23752 pub fn highlight_gutter<T: 'static>(
23753 &mut self,
23754 ranges: impl Into<Vec<Range<Anchor>>>,
23755 color_fetcher: fn(&App) -> Hsla,
23756 cx: &mut Context<Self>,
23757 ) {
23758 self.gutter_highlights
23759 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23760 cx.notify();
23761 }
23762
23763 pub fn clear_gutter_highlights<T: 'static>(
23764 &mut self,
23765 cx: &mut Context<Self>,
23766 ) -> Option<GutterHighlight> {
23767 cx.notify();
23768 self.gutter_highlights.remove(&TypeId::of::<T>())
23769 }
23770
23771 pub fn insert_gutter_highlight<T: 'static>(
23772 &mut self,
23773 range: Range<Anchor>,
23774 color_fetcher: fn(&App) -> Hsla,
23775 cx: &mut Context<Self>,
23776 ) {
23777 let snapshot = self.buffer().read(cx).snapshot(cx);
23778 let mut highlights = self
23779 .gutter_highlights
23780 .remove(&TypeId::of::<T>())
23781 .map(|(_, highlights)| highlights)
23782 .unwrap_or_default();
23783 let ix = highlights.binary_search_by(|highlight| {
23784 Ordering::Equal
23785 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23786 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23787 });
23788 if let Err(ix) = ix {
23789 highlights.insert(ix, range);
23790 }
23791 self.gutter_highlights
23792 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23793 }
23794
23795 pub fn remove_gutter_highlights<T: 'static>(
23796 &mut self,
23797 ranges_to_remove: Vec<Range<Anchor>>,
23798 cx: &mut Context<Self>,
23799 ) {
23800 let snapshot = self.buffer().read(cx).snapshot(cx);
23801 let Some((color_fetcher, mut gutter_highlights)) =
23802 self.gutter_highlights.remove(&TypeId::of::<T>())
23803 else {
23804 return;
23805 };
23806 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23807 gutter_highlights.retain(|highlight| {
23808 while let Some(range_to_remove) = ranges_to_remove.peek() {
23809 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23810 Ordering::Less | Ordering::Equal => {
23811 ranges_to_remove.next();
23812 }
23813 Ordering::Greater => {
23814 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23815 Ordering::Less | Ordering::Equal => {
23816 return false;
23817 }
23818 Ordering::Greater => break,
23819 }
23820 }
23821 }
23822 }
23823
23824 true
23825 });
23826 self.gutter_highlights
23827 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23828 }
23829
23830 #[cfg(any(test, feature = "test-support"))]
23831 pub fn all_text_highlights(
23832 &self,
23833 window: &mut Window,
23834 cx: &mut Context<Self>,
23835 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23836 let snapshot = self.snapshot(window, cx);
23837 self.display_map.update(cx, |display_map, _| {
23838 display_map
23839 .all_text_highlights()
23840 .map(|(_, highlight)| {
23841 let (style, ranges) = highlight.as_ref();
23842 (
23843 *style,
23844 ranges
23845 .iter()
23846 .map(|range| range.clone().to_display_points(&snapshot))
23847 .collect(),
23848 )
23849 })
23850 .collect()
23851 })
23852 }
23853
23854 #[cfg(any(test, feature = "test-support"))]
23855 pub fn all_text_background_highlights(
23856 &self,
23857 window: &mut Window,
23858 cx: &mut Context<Self>,
23859 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23860 let snapshot = self.snapshot(window, cx);
23861 let buffer = &snapshot.buffer_snapshot();
23862 let start = buffer.anchor_before(MultiBufferOffset(0));
23863 let end = buffer.anchor_after(buffer.len());
23864 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23865 }
23866
23867 #[cfg(any(test, feature = "test-support"))]
23868 pub fn sorted_background_highlights_in_range(
23869 &self,
23870 search_range: Range<Anchor>,
23871 display_snapshot: &DisplaySnapshot,
23872 theme: &Theme,
23873 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23874 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23875 res.sort_by(|a, b| {
23876 a.0.start
23877 .cmp(&b.0.start)
23878 .then_with(|| a.0.end.cmp(&b.0.end))
23879 .then_with(|| a.1.cmp(&b.1))
23880 });
23881 res
23882 }
23883
23884 #[cfg(any(test, feature = "test-support"))]
23885 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23886 let snapshot = self.buffer().read(cx).snapshot(cx);
23887
23888 let highlights = self
23889 .background_highlights
23890 .get(&HighlightKey::BufferSearchHighlights);
23891
23892 if let Some((_color, ranges)) = highlights {
23893 ranges
23894 .iter()
23895 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23896 .collect_vec()
23897 } else {
23898 vec![]
23899 }
23900 }
23901
23902 fn document_highlights_for_position<'a>(
23903 &'a self,
23904 position: Anchor,
23905 buffer: &'a MultiBufferSnapshot,
23906 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23907 let read_highlights = self
23908 .background_highlights
23909 .get(&HighlightKey::DocumentHighlightRead)
23910 .map(|h| &h.1);
23911 let write_highlights = self
23912 .background_highlights
23913 .get(&HighlightKey::DocumentHighlightWrite)
23914 .map(|h| &h.1);
23915 let left_position = position.bias_left(buffer);
23916 let right_position = position.bias_right(buffer);
23917 read_highlights
23918 .into_iter()
23919 .chain(write_highlights)
23920 .flat_map(move |ranges| {
23921 let start_ix = match ranges.binary_search_by(|probe| {
23922 let cmp = probe.end.cmp(&left_position, buffer);
23923 if cmp.is_ge() {
23924 Ordering::Greater
23925 } else {
23926 Ordering::Less
23927 }
23928 }) {
23929 Ok(i) | Err(i) => i,
23930 };
23931
23932 ranges[start_ix..]
23933 .iter()
23934 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23935 })
23936 }
23937
23938 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23939 self.background_highlights
23940 .get(&key)
23941 .is_some_and(|(_, highlights)| !highlights.is_empty())
23942 }
23943
23944 /// Returns all background highlights for a given range.
23945 ///
23946 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23947 pub fn background_highlights_in_range(
23948 &self,
23949 search_range: Range<Anchor>,
23950 display_snapshot: &DisplaySnapshot,
23951 theme: &Theme,
23952 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23953 let mut results = Vec::new();
23954 for (color_fetcher, ranges) in self.background_highlights.values() {
23955 let start_ix = match ranges.binary_search_by(|probe| {
23956 let cmp = probe
23957 .end
23958 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23959 if cmp.is_gt() {
23960 Ordering::Greater
23961 } else {
23962 Ordering::Less
23963 }
23964 }) {
23965 Ok(i) | Err(i) => i,
23966 };
23967 for (index, range) in ranges[start_ix..].iter().enumerate() {
23968 if range
23969 .start
23970 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23971 .is_ge()
23972 {
23973 break;
23974 }
23975
23976 let color = color_fetcher(&(start_ix + index), theme);
23977 let start = range.start.to_display_point(display_snapshot);
23978 let end = range.end.to_display_point(display_snapshot);
23979 results.push((start..end, color))
23980 }
23981 }
23982 results
23983 }
23984
23985 pub fn gutter_highlights_in_range(
23986 &self,
23987 search_range: Range<Anchor>,
23988 display_snapshot: &DisplaySnapshot,
23989 cx: &App,
23990 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23991 let mut results = Vec::new();
23992 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23993 let color = color_fetcher(cx);
23994 let start_ix = match ranges.binary_search_by(|probe| {
23995 let cmp = probe
23996 .end
23997 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23998 if cmp.is_gt() {
23999 Ordering::Greater
24000 } else {
24001 Ordering::Less
24002 }
24003 }) {
24004 Ok(i) | Err(i) => i,
24005 };
24006 for range in &ranges[start_ix..] {
24007 if range
24008 .start
24009 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
24010 .is_ge()
24011 {
24012 break;
24013 }
24014
24015 let start = range.start.to_display_point(display_snapshot);
24016 let end = range.end.to_display_point(display_snapshot);
24017 results.push((start..end, color))
24018 }
24019 }
24020 results
24021 }
24022
24023 /// Get the text ranges corresponding to the redaction query
24024 pub fn redacted_ranges(
24025 &self,
24026 search_range: Range<Anchor>,
24027 display_snapshot: &DisplaySnapshot,
24028 cx: &App,
24029 ) -> Vec<Range<DisplayPoint>> {
24030 display_snapshot
24031 .buffer_snapshot()
24032 .redacted_ranges(search_range, |file| {
24033 if let Some(file) = file {
24034 file.is_private()
24035 && EditorSettings::get(
24036 Some(SettingsLocation {
24037 worktree_id: file.worktree_id(cx),
24038 path: file.path().as_ref(),
24039 }),
24040 cx,
24041 )
24042 .redact_private_values
24043 } else {
24044 false
24045 }
24046 })
24047 .map(|range| {
24048 range.start.to_display_point(display_snapshot)
24049 ..range.end.to_display_point(display_snapshot)
24050 })
24051 .collect()
24052 }
24053
24054 pub fn highlight_text_key(
24055 &mut self,
24056 key: HighlightKey,
24057 ranges: Vec<Range<Anchor>>,
24058 style: HighlightStyle,
24059 merge: bool,
24060 cx: &mut Context<Self>,
24061 ) {
24062 self.display_map.update(cx, |map, cx| {
24063 map.highlight_text(key, ranges, style, merge, cx);
24064 });
24065 cx.notify();
24066 }
24067
24068 pub fn highlight_text(
24069 &mut self,
24070 key: HighlightKey,
24071 ranges: Vec<Range<Anchor>>,
24072 style: HighlightStyle,
24073 cx: &mut Context<Self>,
24074 ) {
24075 self.display_map.update(cx, |map, cx| {
24076 map.highlight_text(key, ranges, style, false, cx)
24077 });
24078 cx.notify();
24079 }
24080
24081 pub fn text_highlights<'a>(
24082 &'a self,
24083 key: HighlightKey,
24084 cx: &'a App,
24085 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
24086 self.display_map.read(cx).text_highlights(key)
24087 }
24088
24089 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
24090 let cleared = self
24091 .display_map
24092 .update(cx, |map, _| map.clear_highlights(key));
24093 if cleared {
24094 cx.notify();
24095 }
24096 }
24097
24098 pub fn clear_highlights_with(
24099 &mut self,
24100 f: &mut dyn FnMut(&HighlightKey) -> bool,
24101 cx: &mut Context<Self>,
24102 ) {
24103 let cleared = self
24104 .display_map
24105 .update(cx, |map, _| map.clear_highlights_with(f));
24106 if cleared {
24107 cx.notify();
24108 }
24109 }
24110
24111 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
24112 (self.read_only(cx) || self.blink_manager.read(cx).visible())
24113 && self.focus_handle.is_focused(window)
24114 }
24115
24116 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
24117 self.show_cursor_when_unfocused = is_enabled;
24118 cx.notify();
24119 }
24120
24121 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
24122 cx.notify();
24123 }
24124
24125 fn on_debug_session_event(
24126 &mut self,
24127 _session: Entity<Session>,
24128 event: &SessionEvent,
24129 cx: &mut Context<Self>,
24130 ) {
24131 if let SessionEvent::InvalidateInlineValue = event {
24132 self.refresh_inline_values(cx);
24133 }
24134 }
24135
24136 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
24137 let Some(semantics) = self.semantics_provider.clone() else {
24138 return;
24139 };
24140
24141 if !self.inline_value_cache.enabled {
24142 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24143 self.splice_inlays(&inlays, Vec::new(), cx);
24144 return;
24145 }
24146
24147 let current_execution_position = self
24148 .highlighted_rows
24149 .get(&TypeId::of::<ActiveDebugLine>())
24150 .and_then(|lines| lines.last().map(|line| line.range.end));
24151
24152 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24153 let inline_values = editor
24154 .update(cx, |editor, cx| {
24155 let Some(current_execution_position) = current_execution_position else {
24156 return Some(Task::ready(Ok(Vec::new())));
24157 };
24158
24159 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
24160 let snapshot = buffer.snapshot(cx);
24161
24162 let excerpt = snapshot.excerpt_containing(
24163 current_execution_position..current_execution_position,
24164 )?;
24165
24166 editor.buffer.read(cx).buffer(excerpt.buffer_id())
24167 })?;
24168
24169 if current_execution_position
24170 .text_anchor
24171 .buffer_id
24172 .is_some_and(|id| id != buffer.read(cx).remote_id())
24173 {
24174 return Some(Task::ready(Ok(Vec::new())));
24175 }
24176
24177 let range =
24178 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
24179
24180 semantics.inline_values(buffer, range, cx)
24181 })
24182 .ok()
24183 .flatten()?
24184 .await
24185 .context("refreshing debugger inlays")
24186 .log_err()?;
24187
24188 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24189
24190 for (buffer_id, inline_value) in inline_values
24191 .into_iter()
24192 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
24193 {
24194 buffer_inline_values
24195 .entry(buffer_id)
24196 .or_default()
24197 .push(inline_value);
24198 }
24199
24200 editor
24201 .update(cx, |editor, cx| {
24202 let snapshot = editor.buffer.read(cx).snapshot(cx);
24203 let mut new_inlays = Vec::default();
24204
24205 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
24206 let buffer_id = buffer_snapshot.remote_id();
24207 buffer_inline_values
24208 .get(&buffer_id)
24209 .into_iter()
24210 .flatten()
24211 .for_each(|hint| {
24212 let inlay = Inlay::debugger(
24213 post_inc(&mut editor.next_inlay_id),
24214 Anchor::in_buffer(excerpt_id, hint.position),
24215 hint.text(),
24216 );
24217 if !inlay.text().chars().contains(&'\n') {
24218 new_inlays.push(inlay);
24219 }
24220 });
24221 }
24222
24223 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24224 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24225
24226 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24227 })
24228 .ok()?;
24229 Some(())
24230 });
24231 }
24232
24233 fn on_buffer_event(
24234 &mut self,
24235 multibuffer: &Entity<MultiBuffer>,
24236 event: &multi_buffer::Event,
24237 window: &mut Window,
24238 cx: &mut Context<Self>,
24239 ) {
24240 match event {
24241 multi_buffer::Event::Edited {
24242 edited_buffer,
24243 is_local,
24244 } => {
24245 self.scrollbar_marker_state.dirty = true;
24246 self.active_indent_guides_state.dirty = true;
24247 self.refresh_active_diagnostics(cx);
24248 self.refresh_code_actions(window, cx);
24249 self.refresh_single_line_folds(window, cx);
24250 let snapshot = self.snapshot(window, cx);
24251 self.refresh_matching_bracket_highlights(&snapshot, cx);
24252 self.refresh_outline_symbols_at_cursor(cx);
24253 self.refresh_sticky_headers(&snapshot, cx);
24254 if *is_local && self.has_active_edit_prediction() {
24255 self.update_visible_edit_prediction(window, cx);
24256 }
24257
24258 // Clean up orphaned review comments after edits
24259 self.cleanup_orphaned_review_comments(cx);
24260
24261 if let Some(buffer) = edited_buffer {
24262 if buffer.read(cx).file().is_none() {
24263 cx.emit(EditorEvent::TitleChanged);
24264 }
24265
24266 if self.project.is_some() {
24267 let buffer_id = buffer.read(cx).remote_id();
24268 self.register_buffer(buffer_id, cx);
24269 self.update_lsp_data(Some(buffer_id), window, cx);
24270 self.refresh_inlay_hints(
24271 InlayHintRefreshReason::BufferEdited(buffer_id),
24272 cx,
24273 );
24274 }
24275 }
24276
24277 cx.emit(EditorEvent::BufferEdited);
24278 cx.emit(SearchEvent::MatchesInvalidated);
24279
24280 let Some(project) = &self.project else { return };
24281 let (telemetry, is_via_ssh) = {
24282 let project = project.read(cx);
24283 let telemetry = project.client().telemetry().clone();
24284 let is_via_ssh = project.is_via_remote_server();
24285 (telemetry, is_via_ssh)
24286 };
24287 telemetry.log_edit_event("editor", is_via_ssh);
24288 }
24289 multi_buffer::Event::ExcerptsAdded {
24290 buffer,
24291 predecessor,
24292 excerpts,
24293 } => {
24294 let buffer_id = buffer.read(cx).remote_id();
24295 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24296 && let Some(project) = &self.project
24297 {
24298 update_uncommitted_diff_for_buffer(
24299 cx.entity(),
24300 project,
24301 [buffer.clone()],
24302 self.buffer.clone(),
24303 cx,
24304 )
24305 .detach();
24306 }
24307 self.semantic_token_state
24308 .invalidate_buffer(&buffer.read(cx).remote_id());
24309 self.update_lsp_data(Some(buffer_id), window, cx);
24310 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24311 self.refresh_runnables(None, window, cx);
24312 self.colorize_brackets(false, cx);
24313 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24314 cx.emit(EditorEvent::ExcerptsAdded {
24315 buffer: buffer.clone(),
24316 predecessor: *predecessor,
24317 excerpts: excerpts.clone(),
24318 });
24319 }
24320 multi_buffer::Event::ExcerptsRemoved {
24321 ids,
24322 removed_buffer_ids,
24323 } => {
24324 if let Some(inlay_hints) = &mut self.inlay_hints {
24325 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24326 }
24327 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24328 for buffer_id in removed_buffer_ids {
24329 self.registered_buffers.remove(buffer_id);
24330 self.clear_runnables(Some(*buffer_id));
24331 self.semantic_token_state.invalidate_buffer(buffer_id);
24332 self.display_map.update(cx, |display_map, cx| {
24333 display_map.invalidate_semantic_highlights(*buffer_id);
24334 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24335 });
24336 }
24337
24338 self.display_map.update(cx, |display_map, cx| {
24339 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24340 });
24341
24342 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24343 cx.emit(EditorEvent::ExcerptsRemoved {
24344 ids: ids.clone(),
24345 removed_buffer_ids: removed_buffer_ids.clone(),
24346 });
24347 }
24348 multi_buffer::Event::ExcerptsEdited {
24349 excerpt_ids,
24350 buffer_ids,
24351 } => {
24352 self.display_map.update(cx, |map, cx| {
24353 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24354 });
24355 cx.emit(EditorEvent::ExcerptsEdited {
24356 ids: excerpt_ids.clone(),
24357 });
24358 }
24359 multi_buffer::Event::ExcerptsExpanded { ids } => {
24360 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24361 self.refresh_document_highlights(cx);
24362 let snapshot = multibuffer.read(cx).snapshot(cx);
24363 for id in ids {
24364 self.bracket_fetched_tree_sitter_chunks.remove(id);
24365 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24366 self.semantic_token_state
24367 .invalidate_buffer(&buffer.remote_id());
24368 }
24369 }
24370 self.colorize_brackets(false, cx);
24371 self.update_lsp_data(None, window, cx);
24372 self.refresh_runnables(None, window, cx);
24373 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24374 }
24375 multi_buffer::Event::Reparsed(buffer_id) => {
24376 self.refresh_runnables(Some(*buffer_id), window, cx);
24377 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24378 self.colorize_brackets(true, cx);
24379 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24380
24381 cx.emit(EditorEvent::Reparsed(*buffer_id));
24382 }
24383 multi_buffer::Event::DiffHunksToggled => {
24384 self.refresh_runnables(None, window, cx);
24385 }
24386 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24387 if !is_fresh_language {
24388 self.registered_buffers.remove(&buffer_id);
24389 }
24390 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24391 cx.emit(EditorEvent::Reparsed(*buffer_id));
24392 self.update_edit_prediction_settings(cx);
24393 cx.notify();
24394 }
24395 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24396 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24397 multi_buffer::Event::FileHandleChanged
24398 | multi_buffer::Event::Reloaded
24399 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24400 multi_buffer::Event::DiagnosticsUpdated => {
24401 self.update_diagnostics_state(window, cx);
24402 }
24403 _ => {}
24404 };
24405 }
24406
24407 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24408 if !self.diagnostics_enabled() {
24409 return;
24410 }
24411 self.refresh_active_diagnostics(cx);
24412 self.refresh_inline_diagnostics(true, window, cx);
24413 self.scrollbar_marker_state.dirty = true;
24414 cx.notify();
24415 }
24416
24417 pub fn start_temporary_diff_override(&mut self) {
24418 self.load_diff_task.take();
24419 self.temporary_diff_override = true;
24420 }
24421
24422 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24423 self.temporary_diff_override = false;
24424 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24425 self.buffer.update(cx, |buffer, cx| {
24426 buffer.set_all_diff_hunks_collapsed(cx);
24427 });
24428
24429 if let Some(project) = self.project.clone() {
24430 self.load_diff_task = Some(
24431 update_uncommitted_diff_for_buffer(
24432 cx.entity(),
24433 &project,
24434 self.buffer.read(cx).all_buffers(),
24435 self.buffer.clone(),
24436 cx,
24437 )
24438 .shared(),
24439 );
24440 }
24441 }
24442
24443 fn on_display_map_changed(
24444 &mut self,
24445 _: Entity<DisplayMap>,
24446 _: &mut Window,
24447 cx: &mut Context<Self>,
24448 ) {
24449 cx.notify();
24450 }
24451
24452 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24453 if !self.mode.is_full() {
24454 return None;
24455 }
24456
24457 let theme_settings = theme_settings::ThemeSettings::get_global(cx);
24458 let theme = cx.theme();
24459 let accent_colors = theme.accents().clone();
24460
24461 let accent_overrides = theme_settings
24462 .theme_overrides
24463 .get(theme.name.as_ref())
24464 .map(|theme_style| &theme_style.accents)
24465 .into_iter()
24466 .flatten()
24467 .chain(
24468 theme_settings
24469 .experimental_theme_overrides
24470 .as_ref()
24471 .map(|overrides| &overrides.accents)
24472 .into_iter()
24473 .flatten(),
24474 )
24475 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24476 .collect();
24477
24478 Some(AccentData {
24479 colors: accent_colors,
24480 overrides: accent_overrides,
24481 })
24482 }
24483
24484 fn fetch_applicable_language_settings(
24485 &self,
24486 cx: &App,
24487 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24488 if !self.mode.is_full() {
24489 return HashMap::default();
24490 }
24491
24492 self.buffer().read(cx).all_buffers().into_iter().fold(
24493 HashMap::default(),
24494 |mut acc, buffer| {
24495 let buffer = buffer.read(cx);
24496 let language = buffer.language().map(|language| language.name());
24497 if let hash_map::Entry::Vacant(v) = acc.entry(language) {
24498 v.insert(LanguageSettings::for_buffer(&buffer, cx).into_owned());
24499 }
24500 acc
24501 },
24502 )
24503 }
24504
24505 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24506 let new_language_settings = self.fetch_applicable_language_settings(cx);
24507 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24508 self.applicable_language_settings = new_language_settings;
24509
24510 let new_accents = self.fetch_accent_data(cx);
24511 let accents_changed = new_accents != self.accent_data;
24512 self.accent_data = new_accents;
24513
24514 if self.diagnostics_enabled() {
24515 let new_severity = EditorSettings::get_global(cx)
24516 .diagnostics_max_severity
24517 .unwrap_or(DiagnosticSeverity::Hint);
24518 self.set_max_diagnostics_severity(new_severity, cx);
24519 }
24520 self.refresh_runnables(None, window, cx);
24521 self.update_edit_prediction_settings(cx);
24522 self.refresh_edit_prediction(true, false, window, cx);
24523 self.refresh_inline_values(cx);
24524
24525 let old_cursor_shape = self.cursor_shape;
24526 let old_show_breadcrumbs = self.show_breadcrumbs;
24527
24528 {
24529 let editor_settings = EditorSettings::get_global(cx);
24530 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24531 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24532 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24533 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24534 }
24535
24536 if old_cursor_shape != self.cursor_shape {
24537 cx.emit(EditorEvent::CursorShapeChanged);
24538 }
24539
24540 if old_show_breadcrumbs != self.show_breadcrumbs {
24541 cx.emit(EditorEvent::BreadcrumbsChanged);
24542 }
24543
24544 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24545 let project_settings = ProjectSettings::get_global(cx);
24546 (
24547 project_settings.session.restore_unsaved_buffers,
24548 project_settings.diagnostics.inline.enabled,
24549 project_settings.git.inline_blame.enabled,
24550 )
24551 };
24552 self.buffer_serialization = self
24553 .should_serialize_buffer()
24554 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24555
24556 if self.mode.is_full() {
24557 if self.show_inline_diagnostics != show_inline_diagnostics {
24558 self.show_inline_diagnostics = show_inline_diagnostics;
24559 self.refresh_inline_diagnostics(false, window, cx);
24560 }
24561
24562 if self.git_blame_inline_enabled != inline_blame_enabled {
24563 self.toggle_git_blame_inline_internal(false, window, cx);
24564 }
24565
24566 let minimap_settings = EditorSettings::get_global(cx).minimap;
24567 if self.minimap_visibility != MinimapVisibility::Disabled {
24568 if self.minimap_visibility.settings_visibility()
24569 != minimap_settings.minimap_enabled()
24570 {
24571 self.set_minimap_visibility(
24572 MinimapVisibility::for_mode(self.mode(), cx),
24573 window,
24574 cx,
24575 );
24576 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24577 minimap_entity.update(cx, |minimap_editor, cx| {
24578 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24579 })
24580 }
24581 }
24582
24583 if language_settings_changed || accents_changed {
24584 self.colorize_brackets(true, cx);
24585 }
24586
24587 if language_settings_changed {
24588 self.clear_disabled_lsp_folding_ranges(window, cx);
24589 self.refresh_document_symbols(None, cx);
24590 }
24591
24592 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24593 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24594 }) {
24595 if !inlay_splice.is_empty() {
24596 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24597 }
24598 self.refresh_document_colors(None, window, cx);
24599 }
24600
24601 self.refresh_inlay_hints(
24602 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24603 self.selections.newest_anchor().head(),
24604 &self.buffer.read(cx).snapshot(cx),
24605 cx,
24606 )),
24607 cx,
24608 );
24609
24610 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24611 .global_lsp_settings
24612 .semantic_token_rules
24613 .clone();
24614 let semantic_token_rules_changed = self
24615 .semantic_token_state
24616 .update_rules(new_semantic_token_rules);
24617 if language_settings_changed || semantic_token_rules_changed {
24618 self.invalidate_semantic_tokens(None);
24619 self.refresh_semantic_tokens(None, None, cx);
24620 }
24621 }
24622
24623 cx.notify();
24624 }
24625
24626 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24627 if !self.mode.is_full() {
24628 return;
24629 }
24630
24631 let new_accents = self.fetch_accent_data(cx);
24632 if new_accents != self.accent_data {
24633 self.accent_data = new_accents;
24634 self.colorize_brackets(true, cx);
24635 }
24636
24637 self.invalidate_semantic_tokens(None);
24638 self.refresh_semantic_tokens(None, None, cx);
24639 }
24640
24641 pub fn set_searchable(&mut self, searchable: bool) {
24642 self.searchable = searchable;
24643 }
24644
24645 pub fn searchable(&self) -> bool {
24646 self.searchable
24647 }
24648
24649 pub fn open_excerpts_in_split(
24650 &mut self,
24651 _: &OpenExcerptsSplit,
24652 window: &mut Window,
24653 cx: &mut Context<Self>,
24654 ) {
24655 self.open_excerpts_common(None, true, window, cx)
24656 }
24657
24658 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24659 self.open_excerpts_common(None, false, window, cx)
24660 }
24661
24662 pub(crate) fn open_excerpts_common(
24663 &mut self,
24664 jump_data: Option<JumpData>,
24665 split: bool,
24666 window: &mut Window,
24667 cx: &mut Context<Self>,
24668 ) {
24669 if self.buffer.read(cx).is_singleton() {
24670 cx.propagate();
24671 return;
24672 }
24673
24674 let mut new_selections_by_buffer = HashMap::default();
24675 match &jump_data {
24676 Some(JumpData::MultiBufferPoint {
24677 excerpt_id,
24678 position,
24679 anchor,
24680 line_offset_from_top,
24681 }) => {
24682 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24683 if let Some(buffer) = multi_buffer_snapshot
24684 .buffer_id_for_excerpt(*excerpt_id)
24685 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24686 {
24687 let buffer_snapshot = buffer.read(cx).snapshot();
24688 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24689 language::ToPoint::to_point(anchor, &buffer_snapshot)
24690 } else {
24691 buffer_snapshot.clip_point(*position, Bias::Left)
24692 };
24693 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24694 new_selections_by_buffer.insert(
24695 buffer,
24696 (
24697 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24698 Some(*line_offset_from_top),
24699 ),
24700 );
24701 }
24702 }
24703 Some(JumpData::MultiBufferRow {
24704 row,
24705 line_offset_from_top,
24706 }) => {
24707 let point = MultiBufferPoint::new(row.0, 0);
24708 if let Some((buffer, buffer_point, _)) =
24709 self.buffer.read(cx).point_to_buffer_point(point, cx)
24710 {
24711 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24712 new_selections_by_buffer
24713 .entry(buffer)
24714 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24715 .0
24716 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24717 }
24718 }
24719 None => {
24720 let selections = self
24721 .selections
24722 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24723 let multi_buffer = self.buffer.read(cx);
24724 for selection in selections {
24725 for (snapshot, range, _, anchor) in multi_buffer
24726 .snapshot(cx)
24727 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24728 {
24729 if let Some(anchor) = anchor {
24730 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24731 else {
24732 continue;
24733 };
24734 let offset = text::ToOffset::to_offset(
24735 &anchor.text_anchor,
24736 &buffer_handle.read(cx).snapshot(),
24737 );
24738 let range = BufferOffset(offset)..BufferOffset(offset);
24739 new_selections_by_buffer
24740 .entry(buffer_handle)
24741 .or_insert((Vec::new(), None))
24742 .0
24743 .push(range)
24744 } else {
24745 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24746 else {
24747 continue;
24748 };
24749 new_selections_by_buffer
24750 .entry(buffer_handle)
24751 .or_insert((Vec::new(), None))
24752 .0
24753 .push(range)
24754 }
24755 }
24756 }
24757 }
24758 }
24759
24760 if self.delegate_open_excerpts {
24761 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24762 .into_iter()
24763 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24764 .collect();
24765 if !selections_by_buffer.is_empty() {
24766 cx.emit(EditorEvent::OpenExcerptsRequested {
24767 selections_by_buffer,
24768 split,
24769 });
24770 }
24771 return;
24772 }
24773
24774 let Some(workspace) = self.workspace() else {
24775 cx.propagate();
24776 return;
24777 };
24778
24779 new_selections_by_buffer
24780 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24781
24782 if new_selections_by_buffer.is_empty() {
24783 return;
24784 }
24785
24786 Self::open_buffers_in_workspace(
24787 workspace.downgrade(),
24788 new_selections_by_buffer,
24789 split,
24790 window,
24791 cx,
24792 );
24793 }
24794
24795 pub(crate) fn open_buffers_in_workspace(
24796 workspace: WeakEntity<Workspace>,
24797 new_selections_by_buffer: HashMap<
24798 Entity<language::Buffer>,
24799 (Vec<Range<BufferOffset>>, Option<u32>),
24800 >,
24801 split: bool,
24802 window: &mut Window,
24803 cx: &mut App,
24804 ) {
24805 // We defer the pane interaction because we ourselves are a workspace item
24806 // and activating a new item causes the pane to call a method on us reentrantly,
24807 // which panics if we're on the stack.
24808 window.defer(cx, move |window, cx| {
24809 workspace
24810 .update(cx, |workspace, cx| {
24811 let pane = if split {
24812 workspace.adjacent_pane(window, cx)
24813 } else {
24814 workspace.active_pane().clone()
24815 };
24816
24817 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24818 let buffer_read = buffer.read(cx);
24819 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24820 (true, project::File::from_dyn(Some(file)).is_some())
24821 } else {
24822 (false, false)
24823 };
24824
24825 // If project file is none workspace.open_project_item will fail to open the excerpt
24826 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24827 // so we check if there's a tab match in that case first
24828 let editor = (!has_file || !is_project_file)
24829 .then(|| {
24830 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24831 // so `workspace.open_project_item` will never find them, always opening a new editor.
24832 // Instead, we try to activate the existing editor in the pane first.
24833 let (editor, pane_item_index, pane_item_id) =
24834 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24835 let editor = item.downcast::<Editor>()?;
24836 let singleton_buffer =
24837 editor.read(cx).buffer().read(cx).as_singleton()?;
24838 if singleton_buffer == buffer {
24839 Some((editor, i, item.item_id()))
24840 } else {
24841 None
24842 }
24843 })?;
24844 pane.update(cx, |pane, cx| {
24845 pane.activate_item(pane_item_index, true, true, window, cx);
24846 if !PreviewTabsSettings::get_global(cx)
24847 .enable_preview_from_multibuffer
24848 {
24849 pane.unpreview_item_if_preview(pane_item_id);
24850 }
24851 });
24852 Some(editor)
24853 })
24854 .flatten()
24855 .unwrap_or_else(|| {
24856 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24857 .enable_keep_preview_on_code_navigation;
24858 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24859 .enable_preview_from_multibuffer;
24860 workspace.open_project_item::<Self>(
24861 pane.clone(),
24862 buffer,
24863 true,
24864 true,
24865 keep_old_preview,
24866 allow_new_preview,
24867 window,
24868 cx,
24869 )
24870 });
24871
24872 editor.update(cx, |editor, cx| {
24873 if has_file && !is_project_file {
24874 editor.set_read_only(true);
24875 }
24876 let autoscroll = match scroll_offset {
24877 Some(scroll_offset) => {
24878 Autoscroll::top_relative(scroll_offset as usize)
24879 }
24880 None => Autoscroll::newest(),
24881 };
24882 let nav_history = editor.nav_history.take();
24883 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24884 let Some((excerpt_id, _, buffer_snapshot)) =
24885 multibuffer_snapshot.as_singleton()
24886 else {
24887 return;
24888 };
24889 editor.change_selections(
24890 SelectionEffects::scroll(autoscroll),
24891 window,
24892 cx,
24893 |s| {
24894 s.select_ranges(ranges.into_iter().map(|range| {
24895 let range = buffer_snapshot.anchor_before(range.start)
24896 ..buffer_snapshot.anchor_after(range.end);
24897 multibuffer_snapshot
24898 .anchor_range_in_excerpt(excerpt_id, range)
24899 .unwrap()
24900 }));
24901 },
24902 );
24903 editor.nav_history = nav_history;
24904 });
24905 }
24906 })
24907 .ok();
24908 });
24909 }
24910
24911 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24912 let snapshot = self.buffer.read(cx).read(cx);
24913 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24914 Some(
24915 ranges
24916 .iter()
24917 .map(move |range| {
24918 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24919 })
24920 .collect(),
24921 )
24922 }
24923
24924 fn selection_replacement_ranges(
24925 &self,
24926 range: Range<MultiBufferOffsetUtf16>,
24927 cx: &mut App,
24928 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24929 let selections = self
24930 .selections
24931 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24932 let newest_selection = selections
24933 .iter()
24934 .max_by_key(|selection| selection.id)
24935 .unwrap();
24936 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24937 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24938 let snapshot = self.buffer.read(cx).read(cx);
24939 selections
24940 .into_iter()
24941 .map(|mut selection| {
24942 selection.start.0.0 =
24943 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24944 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24945 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24946 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24947 })
24948 .collect()
24949 }
24950
24951 fn report_editor_event(
24952 &self,
24953 reported_event: ReportEditorEvent,
24954 file_extension: Option<String>,
24955 cx: &App,
24956 ) {
24957 if cfg!(any(test, feature = "test-support")) {
24958 return;
24959 }
24960
24961 let Some(project) = &self.project else { return };
24962
24963 // If None, we are in a file without an extension
24964 let file = self
24965 .buffer
24966 .read(cx)
24967 .as_singleton()
24968 .and_then(|b| b.read(cx).file());
24969 let file_extension = file_extension.or(file
24970 .as_ref()
24971 .and_then(|file| Path::new(file.file_name(cx)).extension())
24972 .and_then(|e| e.to_str())
24973 .map(|a| a.to_string()));
24974
24975 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24976 .map(|vim_mode| vim_mode.0)
24977 .unwrap_or(false);
24978
24979 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24980 let copilot_enabled = edit_predictions_provider
24981 == language::language_settings::EditPredictionProvider::Copilot;
24982 let copilot_enabled_for_language = self
24983 .buffer
24984 .read(cx)
24985 .language_settings(cx)
24986 .show_edit_predictions;
24987
24988 let project = project.read(cx);
24989 let event_type = reported_event.event_type();
24990
24991 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24992 telemetry::event!(
24993 event_type,
24994 type = if auto_saved {"autosave"} else {"manual"},
24995 file_extension,
24996 vim_mode,
24997 copilot_enabled,
24998 copilot_enabled_for_language,
24999 edit_predictions_provider,
25000 is_via_ssh = project.is_via_remote_server(),
25001 );
25002 } else {
25003 telemetry::event!(
25004 event_type,
25005 file_extension,
25006 vim_mode,
25007 copilot_enabled,
25008 copilot_enabled_for_language,
25009 edit_predictions_provider,
25010 is_via_ssh = project.is_via_remote_server(),
25011 );
25012 };
25013 }
25014
25015 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
25016 /// with each line being an array of {text, highlight} objects.
25017 fn copy_highlight_json(
25018 &mut self,
25019 _: &CopyHighlightJson,
25020 window: &mut Window,
25021 cx: &mut Context<Self>,
25022 ) {
25023 #[derive(Serialize)]
25024 struct Chunk<'a> {
25025 text: String,
25026 highlight: Option<&'a str>,
25027 }
25028
25029 let snapshot = self.buffer.read(cx).snapshot(cx);
25030 let range = self
25031 .selected_text_range(false, window, cx)
25032 .and_then(|selection| {
25033 if selection.range.is_empty() {
25034 None
25035 } else {
25036 Some(
25037 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
25038 selection.range.start,
25039 )))
25040 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
25041 selection.range.end,
25042 ))),
25043 )
25044 }
25045 })
25046 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
25047
25048 let chunks = snapshot.chunks(range, true);
25049 let mut lines = Vec::new();
25050 let mut line: VecDeque<Chunk> = VecDeque::new();
25051
25052 let Some(style) = self.style.as_ref() else {
25053 return;
25054 };
25055
25056 for chunk in chunks {
25057 let highlight = chunk
25058 .syntax_highlight_id
25059 .and_then(|id| style.syntax.get_capture_name(id));
25060
25061 let mut chunk_lines = chunk.text.split('\n').peekable();
25062 while let Some(text) = chunk_lines.next() {
25063 let mut merged_with_last_token = false;
25064 if let Some(last_token) = line.back_mut()
25065 && last_token.highlight == highlight
25066 {
25067 last_token.text.push_str(text);
25068 merged_with_last_token = true;
25069 }
25070
25071 if !merged_with_last_token {
25072 line.push_back(Chunk {
25073 text: text.into(),
25074 highlight,
25075 });
25076 }
25077
25078 if chunk_lines.peek().is_some() {
25079 if line.len() > 1 && line.front().unwrap().text.is_empty() {
25080 line.pop_front();
25081 }
25082 if line.len() > 1 && line.back().unwrap().text.is_empty() {
25083 line.pop_back();
25084 }
25085
25086 lines.push(mem::take(&mut line));
25087 }
25088 }
25089 }
25090
25091 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
25092 return;
25093 };
25094 cx.write_to_clipboard(ClipboardItem::new_string(lines));
25095 }
25096
25097 pub fn open_context_menu(
25098 &mut self,
25099 _: &OpenContextMenu,
25100 window: &mut Window,
25101 cx: &mut Context<Self>,
25102 ) {
25103 self.request_autoscroll(Autoscroll::newest(), cx);
25104 let position = self
25105 .selections
25106 .newest_display(&self.display_snapshot(cx))
25107 .start;
25108 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
25109 }
25110
25111 pub fn replay_insert_event(
25112 &mut self,
25113 text: &str,
25114 relative_utf16_range: Option<Range<isize>>,
25115 window: &mut Window,
25116 cx: &mut Context<Self>,
25117 ) {
25118 if !self.input_enabled {
25119 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25120 return;
25121 }
25122 if let Some(relative_utf16_range) = relative_utf16_range {
25123 let selections = self
25124 .selections
25125 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25126 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25127 let new_ranges = selections.into_iter().map(|range| {
25128 let start = MultiBufferOffsetUtf16(OffsetUtf16(
25129 range
25130 .head()
25131 .0
25132 .0
25133 .saturating_add_signed(relative_utf16_range.start),
25134 ));
25135 let end = MultiBufferOffsetUtf16(OffsetUtf16(
25136 range
25137 .head()
25138 .0
25139 .0
25140 .saturating_add_signed(relative_utf16_range.end),
25141 ));
25142 start..end
25143 });
25144 s.select_ranges(new_ranges);
25145 });
25146 }
25147
25148 self.handle_input(text, window, cx);
25149 }
25150
25151 pub fn is_focused(&self, window: &Window) -> bool {
25152 self.focus_handle.is_focused(window)
25153 }
25154
25155 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25156 cx.emit(EditorEvent::Focused);
25157
25158 if let Some(descendant) = self
25159 .last_focused_descendant
25160 .take()
25161 .and_then(|descendant| descendant.upgrade())
25162 {
25163 window.focus(&descendant, cx);
25164 } else {
25165 if let Some(blame) = self.blame.as_ref() {
25166 blame.update(cx, GitBlame::focus)
25167 }
25168
25169 self.blink_manager.update(cx, BlinkManager::enable);
25170 self.show_cursor_names(window, cx);
25171 self.buffer.update(cx, |buffer, cx| {
25172 buffer.finalize_last_transaction(cx);
25173 if self.leader_id.is_none() {
25174 buffer.set_active_selections(
25175 &self.selections.disjoint_anchors_arc(),
25176 self.selections.line_mode(),
25177 self.cursor_shape,
25178 cx,
25179 );
25180 }
25181 });
25182
25183 if let Some(position_map) = self.last_position_map.clone()
25184 && !self.mouse_cursor_hidden
25185 {
25186 EditorElement::mouse_moved(
25187 self,
25188 &MouseMoveEvent {
25189 position: window.mouse_position(),
25190 pressed_button: None,
25191 modifiers: window.modifiers(),
25192 },
25193 &position_map,
25194 None,
25195 window,
25196 cx,
25197 );
25198 }
25199 }
25200 }
25201
25202 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25203 cx.emit(EditorEvent::FocusedIn)
25204 }
25205
25206 fn handle_focus_out(
25207 &mut self,
25208 event: FocusOutEvent,
25209 _window: &mut Window,
25210 cx: &mut Context<Self>,
25211 ) {
25212 if event.blurred != self.focus_handle {
25213 self.last_focused_descendant = Some(event.blurred);
25214 }
25215 self.selection_drag_state = SelectionDragState::None;
25216 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25217 }
25218
25219 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25220 self.blink_manager.update(cx, BlinkManager::disable);
25221 self.buffer
25222 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25223
25224 if let Some(blame) = self.blame.as_ref() {
25225 blame.update(cx, GitBlame::blur)
25226 }
25227 if !self.hover_state.focused(window, cx) {
25228 hide_hover(self, cx);
25229 }
25230 if !self
25231 .context_menu
25232 .borrow()
25233 .as_ref()
25234 .is_some_and(|context_menu| context_menu.focused(window, cx))
25235 {
25236 self.hide_context_menu(window, cx);
25237 }
25238 self.take_active_edit_prediction(true, cx);
25239 cx.emit(EditorEvent::Blurred);
25240 cx.notify();
25241 }
25242
25243 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25244 let mut pending: String = window
25245 .pending_input_keystrokes()
25246 .into_iter()
25247 .flatten()
25248 .filter_map(|keystroke| keystroke.key_char.clone())
25249 .collect();
25250
25251 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25252 pending = "".to_string();
25253 }
25254
25255 let existing_pending = self
25256 .text_highlights(HighlightKey::PendingInput, cx)
25257 .map(|(_, ranges)| ranges.to_vec());
25258 if existing_pending.is_none() && pending.is_empty() {
25259 return;
25260 }
25261 let transaction =
25262 self.transact(window, cx, |this, window, cx| {
25263 let selections = this
25264 .selections
25265 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25266 let edits = selections
25267 .iter()
25268 .map(|selection| (selection.end..selection.end, pending.clone()));
25269 this.edit(edits, cx);
25270 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25271 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25272 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25273 }));
25274 });
25275 if let Some(existing_ranges) = existing_pending {
25276 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25277 this.edit(edits, cx);
25278 }
25279 });
25280
25281 let snapshot = self.snapshot(window, cx);
25282 let ranges = self
25283 .selections
25284 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25285 .into_iter()
25286 .map(|selection| {
25287 snapshot.buffer_snapshot().anchor_after(selection.end)
25288 ..snapshot
25289 .buffer_snapshot()
25290 .anchor_before(selection.end + pending.len())
25291 })
25292 .collect();
25293
25294 if pending.is_empty() {
25295 self.clear_highlights(HighlightKey::PendingInput, cx);
25296 } else {
25297 self.highlight_text(
25298 HighlightKey::PendingInput,
25299 ranges,
25300 HighlightStyle {
25301 underline: Some(UnderlineStyle {
25302 thickness: px(1.),
25303 color: None,
25304 wavy: false,
25305 }),
25306 ..Default::default()
25307 },
25308 cx,
25309 );
25310 }
25311
25312 self.ime_transaction = self.ime_transaction.or(transaction);
25313 if let Some(transaction) = self.ime_transaction {
25314 self.buffer.update(cx, |buffer, cx| {
25315 buffer.group_until_transaction(transaction, cx);
25316 });
25317 }
25318
25319 if self
25320 .text_highlights(HighlightKey::PendingInput, cx)
25321 .is_none()
25322 {
25323 self.ime_transaction.take();
25324 }
25325 }
25326
25327 pub fn register_action_renderer(
25328 &mut self,
25329 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25330 ) -> Subscription {
25331 let id = self.next_editor_action_id.post_inc();
25332 self.editor_actions
25333 .borrow_mut()
25334 .insert(id, Box::new(listener));
25335
25336 let editor_actions = self.editor_actions.clone();
25337 Subscription::new(move || {
25338 editor_actions.borrow_mut().remove(&id);
25339 })
25340 }
25341
25342 pub fn register_action<A: Action>(
25343 &mut self,
25344 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25345 ) -> Subscription {
25346 let id = self.next_editor_action_id.post_inc();
25347 let listener = Arc::new(listener);
25348 self.editor_actions.borrow_mut().insert(
25349 id,
25350 Box::new(move |_, window, _| {
25351 let listener = listener.clone();
25352 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25353 let action = action.downcast_ref().unwrap();
25354 if phase == DispatchPhase::Bubble {
25355 listener(action, window, cx)
25356 }
25357 })
25358 }),
25359 );
25360
25361 let editor_actions = self.editor_actions.clone();
25362 Subscription::new(move || {
25363 editor_actions.borrow_mut().remove(&id);
25364 })
25365 }
25366
25367 pub fn file_header_size(&self) -> u32 {
25368 FILE_HEADER_HEIGHT
25369 }
25370
25371 pub fn restore(
25372 &mut self,
25373 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25374 window: &mut Window,
25375 cx: &mut Context<Self>,
25376 ) {
25377 self.buffer().update(cx, |multi_buffer, cx| {
25378 for (buffer_id, changes) in revert_changes {
25379 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25380 buffer.update(cx, |buffer, cx| {
25381 buffer.edit(
25382 changes
25383 .into_iter()
25384 .map(|(range, text)| (range, text.to_string())),
25385 None,
25386 cx,
25387 );
25388 });
25389 }
25390 }
25391 });
25392 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25393 selections.refresh()
25394 });
25395 }
25396
25397 pub fn to_pixel_point(
25398 &mut self,
25399 source: Anchor,
25400 editor_snapshot: &EditorSnapshot,
25401 window: &mut Window,
25402 cx: &mut App,
25403 ) -> Option<gpui::Point<Pixels>> {
25404 let source_point = source.to_display_point(editor_snapshot);
25405 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25406 }
25407
25408 pub fn display_to_pixel_point(
25409 &mut self,
25410 source: DisplayPoint,
25411 editor_snapshot: &EditorSnapshot,
25412 window: &mut Window,
25413 cx: &mut App,
25414 ) -> Option<gpui::Point<Pixels>> {
25415 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25416 let text_layout_details = self.text_layout_details(window, cx);
25417 let scroll_top = text_layout_details
25418 .scroll_anchor
25419 .scroll_position(editor_snapshot)
25420 .y;
25421
25422 if source.row().as_f64() < scroll_top.floor() {
25423 return None;
25424 }
25425 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25426 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25427 Some(gpui::Point::new(source_x, source_y))
25428 }
25429
25430 pub fn has_visible_completions_menu(&self) -> bool {
25431 !self.edit_prediction_preview_is_active()
25432 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25433 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25434 })
25435 }
25436
25437 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25438 if self.mode.is_minimap() {
25439 return;
25440 }
25441 self.addons
25442 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25443 }
25444
25445 pub fn unregister_addon<T: Addon>(&mut self) {
25446 self.addons.remove(&std::any::TypeId::of::<T>());
25447 }
25448
25449 pub fn addon<T: Addon>(&self) -> Option<&T> {
25450 let type_id = std::any::TypeId::of::<T>();
25451 self.addons
25452 .get(&type_id)
25453 .and_then(|item| item.to_any().downcast_ref::<T>())
25454 }
25455
25456 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25457 let type_id = std::any::TypeId::of::<T>();
25458 self.addons
25459 .get_mut(&type_id)
25460 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25461 }
25462
25463 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25464 let text_layout_details = self.text_layout_details(window, cx);
25465 let style = &text_layout_details.editor_style;
25466 let font_id = window.text_system().resolve_font(&style.text.font());
25467 let font_size = style.text.font_size.to_pixels(window.rem_size());
25468 let line_height = style.text.line_height_in_pixels(window.rem_size());
25469 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25470 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25471
25472 CharacterDimensions {
25473 em_width,
25474 em_advance,
25475 line_height,
25476 }
25477 }
25478
25479 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25480 self.load_diff_task.clone()
25481 }
25482
25483 fn read_metadata_from_db(
25484 &mut self,
25485 item_id: u64,
25486 workspace_id: WorkspaceId,
25487 window: &mut Window,
25488 cx: &mut Context<Editor>,
25489 ) {
25490 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25491 && !self.mode.is_minimap()
25492 && WorkspaceSettings::get(None, cx).restore_on_startup
25493 != RestoreOnStartupBehavior::EmptyTab
25494 {
25495 let buffer_snapshot = OnceCell::new();
25496
25497 // Get file path for path-based fold lookup
25498 let file_path: Option<Arc<Path>> =
25499 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25500 project::File::from_dyn(buffer.read(cx).file())
25501 .map(|file| Arc::from(file.abs_path(cx)))
25502 });
25503
25504 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25505 let db = EditorDb::global(cx);
25506 let (folds, needs_migration) = if let Some(ref path) = file_path {
25507 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
25508 && !folds.is_empty()
25509 {
25510 (Some(folds), false)
25511 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
25512 && !folds.is_empty()
25513 {
25514 // Found old editor_folds data, will migrate to file_folds
25515 (Some(folds), true)
25516 } else {
25517 (None, false)
25518 }
25519 } else {
25520 // No file path, try editor_folds as fallback
25521 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
25522 (folds.filter(|f| !f.is_empty()), false)
25523 };
25524
25525 if let Some(folds) = folds {
25526 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25527 let snapshot_len = snapshot.len().0;
25528
25529 // Helper: search for fingerprint in buffer, return offset if found
25530 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25531 // Ensure we start at a character boundary (defensive)
25532 let search_start = snapshot
25533 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25534 .0;
25535 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25536
25537 let mut byte_offset = search_start;
25538 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25539 if byte_offset > search_end {
25540 break;
25541 }
25542 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25543 return Some(byte_offset);
25544 }
25545 byte_offset += ch.len_utf8();
25546 }
25547 None
25548 };
25549
25550 // Track search position to handle duplicate fingerprints correctly.
25551 // Folds are stored in document order, so we advance after each match.
25552 let mut search_start = 0usize;
25553
25554 // Collect db_folds for migration (only folds with valid fingerprints)
25555 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25556
25557 let valid_folds: Vec<_> = folds
25558 .into_iter()
25559 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25560 // Skip folds without fingerprints (old data before migration)
25561 let sfp = start_fp?;
25562 let efp = end_fp?;
25563 let efp_len = efp.len();
25564
25565 // Fast path: check if fingerprints match at stored offsets
25566 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25567 let start_matches = stored_start < snapshot_len
25568 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25569 let efp_check_pos = stored_end.saturating_sub(efp_len);
25570 let end_matches = efp_check_pos >= stored_start
25571 && stored_end <= snapshot_len
25572 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25573
25574 let (new_start, new_end) = if start_matches && end_matches {
25575 // Offsets unchanged, use stored values
25576 (stored_start, stored_end)
25577 } else if sfp == efp {
25578 // Short fold: identical fingerprints can only match once per search
25579 // Use stored fold length to compute new_end
25580 let new_start = find_fingerprint(&sfp, search_start)?;
25581 let fold_len = stored_end - stored_start;
25582 let new_end = new_start + fold_len;
25583 (new_start, new_end)
25584 } else {
25585 // Slow path: search for fingerprints in buffer
25586 let new_start = find_fingerprint(&sfp, search_start)?;
25587 // Search for end_fp after start, then add efp_len to get actual fold end
25588 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25589 let new_end = efp_pos + efp_len;
25590 (new_start, new_end)
25591 };
25592
25593 // Advance search position for next fold
25594 search_start = new_end;
25595
25596 // Validate fold makes sense (end must be after start)
25597 if new_end <= new_start {
25598 return None;
25599 }
25600
25601 // Collect for migration if needed
25602 if needs_migration {
25603 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25604 }
25605
25606 Some(
25607 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25608 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25609 )
25610 })
25611 .collect();
25612
25613 if !valid_folds.is_empty() {
25614 self.fold_ranges(valid_folds, false, window, cx);
25615
25616 // Migrate from editor_folds to file_folds if we loaded from old table
25617 if needs_migration {
25618 if let Some(ref path) = file_path {
25619 let path = path.clone();
25620 let db = EditorDb::global(cx);
25621 cx.spawn(async move |_, _| {
25622 db.save_file_folds(workspace_id, path, db_folds_for_migration)
25623 .await
25624 .log_err();
25625 })
25626 .detach();
25627 }
25628 }
25629 }
25630 }
25631
25632 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
25633 && !selections.is_empty()
25634 {
25635 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25636 // skip adding the initial selection to selection history
25637 self.selection_history.mode = SelectionHistoryMode::Skipping;
25638 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25639 s.select_ranges(selections.into_iter().map(|(start, end)| {
25640 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25641 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25642 }));
25643 });
25644 self.selection_history.mode = SelectionHistoryMode::Normal;
25645 };
25646 }
25647
25648 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25649 }
25650
25651 /// Load folds from the file_folds database table by file path.
25652 /// Used when manually opening a file that was previously closed.
25653 fn load_folds_from_db(
25654 &mut self,
25655 workspace_id: WorkspaceId,
25656 file_path: PathBuf,
25657 window: &mut Window,
25658 cx: &mut Context<Editor>,
25659 ) {
25660 if self.mode.is_minimap()
25661 || WorkspaceSettings::get(None, cx).restore_on_startup
25662 == RestoreOnStartupBehavior::EmptyTab
25663 {
25664 return;
25665 }
25666
25667 let Some(folds) = EditorDb::global(cx)
25668 .get_file_folds(workspace_id, &file_path)
25669 .log_err()
25670 else {
25671 return;
25672 };
25673 if folds.is_empty() {
25674 return;
25675 }
25676
25677 let snapshot = self.buffer.read(cx).snapshot(cx);
25678 let snapshot_len = snapshot.len().0;
25679
25680 // Helper: search for fingerprint in buffer, return offset if found
25681 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25682 let search_start = snapshot
25683 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25684 .0;
25685 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25686
25687 let mut byte_offset = search_start;
25688 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25689 if byte_offset > search_end {
25690 break;
25691 }
25692 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25693 return Some(byte_offset);
25694 }
25695 byte_offset += ch.len_utf8();
25696 }
25697 None
25698 };
25699
25700 let mut search_start = 0usize;
25701
25702 let valid_folds: Vec<_> = folds
25703 .into_iter()
25704 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25705 let sfp = start_fp?;
25706 let efp = end_fp?;
25707 let efp_len = efp.len();
25708
25709 let start_matches = stored_start < snapshot_len
25710 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25711 let efp_check_pos = stored_end.saturating_sub(efp_len);
25712 let end_matches = efp_check_pos >= stored_start
25713 && stored_end <= snapshot_len
25714 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25715
25716 let (new_start, new_end) = if start_matches && end_matches {
25717 (stored_start, stored_end)
25718 } else if sfp == efp {
25719 let new_start = find_fingerprint(&sfp, search_start)?;
25720 let fold_len = stored_end - stored_start;
25721 let new_end = new_start + fold_len;
25722 (new_start, new_end)
25723 } else {
25724 let new_start = find_fingerprint(&sfp, search_start)?;
25725 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25726 let new_end = efp_pos + efp_len;
25727 (new_start, new_end)
25728 };
25729
25730 search_start = new_end;
25731
25732 if new_end <= new_start {
25733 return None;
25734 }
25735
25736 Some(
25737 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25738 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25739 )
25740 })
25741 .collect();
25742
25743 if !valid_folds.is_empty() {
25744 self.fold_ranges(valid_folds, false, window, cx);
25745 }
25746 }
25747
25748 fn lsp_data_enabled(&self) -> bool {
25749 self.enable_lsp_data && self.mode().is_full()
25750 }
25751
25752 fn update_lsp_data(
25753 &mut self,
25754 for_buffer: Option<BufferId>,
25755 window: &mut Window,
25756 cx: &mut Context<'_, Self>,
25757 ) {
25758 if !self.lsp_data_enabled() {
25759 return;
25760 }
25761
25762 if let Some(buffer_id) = for_buffer {
25763 self.pull_diagnostics(buffer_id, window, cx);
25764 }
25765 self.refresh_semantic_tokens(for_buffer, None, cx);
25766 self.refresh_document_colors(for_buffer, window, cx);
25767 self.refresh_folding_ranges(for_buffer, window, cx);
25768 self.refresh_document_symbols(for_buffer, cx);
25769 }
25770
25771 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25772 if !self.lsp_data_enabled() {
25773 return;
25774 }
25775 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25776 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25777 }
25778 }
25779
25780 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25781 if !self.lsp_data_enabled() {
25782 return;
25783 }
25784
25785 if !self.registered_buffers.contains_key(&buffer_id)
25786 && let Some(project) = self.project.as_ref()
25787 {
25788 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25789 project.update(cx, |project, cx| {
25790 self.registered_buffers.insert(
25791 buffer_id,
25792 project.register_buffer_with_language_servers(&buffer, cx),
25793 );
25794 });
25795 } else {
25796 self.registered_buffers.remove(&buffer_id);
25797 }
25798 }
25799 }
25800
25801 fn create_style(&self, cx: &App) -> EditorStyle {
25802 let settings = ThemeSettings::get_global(cx);
25803
25804 let mut text_style = match self.mode {
25805 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25806 color: cx.theme().colors().editor_foreground,
25807 font_family: settings.ui_font.family.clone(),
25808 font_features: settings.ui_font.features.clone(),
25809 font_fallbacks: settings.ui_font.fallbacks.clone(),
25810 font_size: rems(0.875).into(),
25811 font_weight: settings.ui_font.weight,
25812 line_height: relative(settings.buffer_line_height.value()),
25813 ..Default::default()
25814 },
25815 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25816 color: cx.theme().colors().editor_foreground,
25817 font_family: settings.buffer_font.family.clone(),
25818 font_features: settings.buffer_font.features.clone(),
25819 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25820 font_size: settings.buffer_font_size(cx).into(),
25821 font_weight: settings.buffer_font.weight,
25822 line_height: relative(settings.buffer_line_height.value()),
25823 ..Default::default()
25824 },
25825 };
25826 if let Some(text_style_refinement) = &self.text_style_refinement {
25827 text_style.refine(text_style_refinement)
25828 }
25829
25830 let background = match self.mode {
25831 EditorMode::SingleLine => cx.theme().system().transparent,
25832 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25833 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25834 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25835 };
25836
25837 EditorStyle {
25838 background,
25839 border: cx.theme().colors().border,
25840 local_player: cx.theme().players().local(),
25841 text: text_style,
25842 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25843 syntax: cx.theme().syntax().clone(),
25844 status: cx.theme().status().clone(),
25845 inlay_hints_style: make_inlay_hints_style(cx),
25846 edit_prediction_styles: make_suggestion_styles(cx),
25847 unnecessary_code_fade: settings.unnecessary_code_fade,
25848 show_underlines: self.diagnostics_enabled(),
25849 }
25850 }
25851
25852 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
25853 let multibuffer = self.buffer().read(cx);
25854 let is_singleton = multibuffer.is_singleton();
25855 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25856 let buffer = multibuffer.buffer(*buffer_id)?;
25857
25858 let buffer = buffer.read(cx);
25859 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25860 let mut breadcrumbs = if is_singleton {
25861 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25862 buffer
25863 .snapshot()
25864 .resolve_file_path(
25865 self.project
25866 .as_ref()
25867 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25868 .unwrap_or_default(),
25869 cx,
25870 )
25871 .unwrap_or_else(|| {
25872 if multibuffer.is_singleton() {
25873 multibuffer.title(cx).to_string()
25874 } else {
25875 "untitled".to_string()
25876 }
25877 })
25878 });
25879 vec![HighlightedText {
25880 text: text.into(),
25881 highlights: vec![],
25882 }]
25883 } else {
25884 vec![]
25885 };
25886
25887 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
25888 text: symbol.text.clone().into(),
25889 highlights: symbol.highlight_ranges.clone(),
25890 }));
25891 Some(breadcrumbs)
25892 }
25893
25894 fn disable_lsp_data(&mut self) {
25895 self.enable_lsp_data = false;
25896 }
25897
25898 fn disable_runnables(&mut self) {
25899 self.enable_runnables = false;
25900 }
25901
25902 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
25903 self.register_visible_buffers(cx);
25904 self.colorize_brackets(false, cx);
25905 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
25906 if !self.buffer().read(cx).is_singleton() {
25907 self.update_lsp_data(None, window, cx);
25908 self.refresh_runnables(None, window, cx);
25909 }
25910 }
25911}
25912
25913fn edit_for_markdown_paste<'a>(
25914 buffer: &MultiBufferSnapshot,
25915 range: Range<MultiBufferOffset>,
25916 to_insert: &'a str,
25917 url: Option<url::Url>,
25918) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25919 if url.is_none() {
25920 return (range, Cow::Borrowed(to_insert));
25921 };
25922
25923 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25924
25925 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25926 Cow::Borrowed(to_insert)
25927 } else {
25928 Cow::Owned(format!("[{old_text}]({to_insert})"))
25929 };
25930 (range, new_text)
25931}
25932
25933fn process_completion_for_edit(
25934 completion: &Completion,
25935 intent: CompletionIntent,
25936 buffer: &Entity<Buffer>,
25937 cursor_position: &text::Anchor,
25938 cx: &mut Context<Editor>,
25939) -> CompletionEdit {
25940 let buffer = buffer.read(cx);
25941 let buffer_snapshot = buffer.snapshot();
25942 let (snippet, new_text) = if completion.is_snippet() {
25943 let mut snippet_source = completion.new_text.clone();
25944 // Workaround for typescript language server issues so that methods don't expand within
25945 // strings and functions with type expressions. The previous point is used because the query
25946 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25947 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25948 let previous_point = if previous_point.column > 0 {
25949 cursor_position.to_previous_offset(&buffer_snapshot)
25950 } else {
25951 cursor_position.to_offset(&buffer_snapshot)
25952 };
25953 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25954 && scope.prefers_label_for_snippet_in_completion()
25955 && let Some(label) = completion.label()
25956 && matches!(
25957 completion.kind(),
25958 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25959 )
25960 {
25961 snippet_source = label;
25962 }
25963 match Snippet::parse(&snippet_source).log_err() {
25964 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25965 None => (None, completion.new_text.clone()),
25966 }
25967 } else {
25968 (None, completion.new_text.clone())
25969 };
25970
25971 let mut range_to_replace = {
25972 let replace_range = &completion.replace_range;
25973 if let CompletionSource::Lsp {
25974 insert_range: Some(insert_range),
25975 ..
25976 } = &completion.source
25977 {
25978 debug_assert_eq!(
25979 insert_range.start, replace_range.start,
25980 "insert_range and replace_range should start at the same position"
25981 );
25982 debug_assert!(
25983 insert_range
25984 .start
25985 .cmp(cursor_position, &buffer_snapshot)
25986 .is_le(),
25987 "insert_range should start before or at cursor position"
25988 );
25989 debug_assert!(
25990 replace_range
25991 .start
25992 .cmp(cursor_position, &buffer_snapshot)
25993 .is_le(),
25994 "replace_range should start before or at cursor position"
25995 );
25996
25997 let should_replace = match intent {
25998 CompletionIntent::CompleteWithInsert => false,
25999 CompletionIntent::CompleteWithReplace => true,
26000 CompletionIntent::Complete | CompletionIntent::Compose => {
26001 let insert_mode = LanguageSettings::for_buffer(&buffer, cx)
26002 .completions
26003 .lsp_insert_mode;
26004 match insert_mode {
26005 LspInsertMode::Insert => false,
26006 LspInsertMode::Replace => true,
26007 LspInsertMode::ReplaceSubsequence => {
26008 let mut text_to_replace = buffer.chars_for_range(
26009 buffer.anchor_before(replace_range.start)
26010 ..buffer.anchor_after(replace_range.end),
26011 );
26012 let mut current_needle = text_to_replace.next();
26013 for haystack_ch in completion.label.text.chars() {
26014 if let Some(needle_ch) = current_needle
26015 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
26016 {
26017 current_needle = text_to_replace.next();
26018 }
26019 }
26020 current_needle.is_none()
26021 }
26022 LspInsertMode::ReplaceSuffix => {
26023 if replace_range
26024 .end
26025 .cmp(cursor_position, &buffer_snapshot)
26026 .is_gt()
26027 {
26028 let range_after_cursor = *cursor_position..replace_range.end;
26029 let text_after_cursor = buffer
26030 .text_for_range(
26031 buffer.anchor_before(range_after_cursor.start)
26032 ..buffer.anchor_after(range_after_cursor.end),
26033 )
26034 .collect::<String>()
26035 .to_ascii_lowercase();
26036 completion
26037 .label
26038 .text
26039 .to_ascii_lowercase()
26040 .ends_with(&text_after_cursor)
26041 } else {
26042 true
26043 }
26044 }
26045 }
26046 }
26047 };
26048
26049 if should_replace {
26050 replace_range.clone()
26051 } else {
26052 insert_range.clone()
26053 }
26054 } else {
26055 replace_range.clone()
26056 }
26057 };
26058
26059 if range_to_replace
26060 .end
26061 .cmp(cursor_position, &buffer_snapshot)
26062 .is_lt()
26063 {
26064 range_to_replace.end = *cursor_position;
26065 }
26066
26067 let replace_range = range_to_replace.to_offset(buffer);
26068 CompletionEdit {
26069 new_text,
26070 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
26071 snippet,
26072 }
26073}
26074
26075struct CompletionEdit {
26076 new_text: String,
26077 replace_range: Range<BufferOffset>,
26078 snippet: Option<Snippet>,
26079}
26080
26081fn comment_delimiter_for_newline(
26082 start_point: &Point,
26083 buffer: &MultiBufferSnapshot,
26084 language: &LanguageScope,
26085) -> Option<Arc<str>> {
26086 let delimiters = language.line_comment_prefixes();
26087 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
26088 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26089
26090 let num_of_whitespaces = snapshot
26091 .chars_for_range(range.clone())
26092 .take_while(|c| c.is_whitespace())
26093 .count();
26094 let comment_candidate = snapshot
26095 .chars_for_range(range.clone())
26096 .skip(num_of_whitespaces)
26097 .take(max_len_of_delimiter + 2)
26098 .collect::<String>();
26099 let (delimiter, trimmed_len, is_repl) = delimiters
26100 .iter()
26101 .filter_map(|delimiter| {
26102 let prefix = delimiter.trim_end();
26103 if comment_candidate.starts_with(prefix) {
26104 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
26105 {
26106 stripped_comment.starts_with(" %%")
26107 } else {
26108 false
26109 };
26110 Some((delimiter, prefix.len(), is_repl))
26111 } else {
26112 None
26113 }
26114 })
26115 .max_by_key(|(_, len, _)| *len)?;
26116
26117 if let Some(BlockCommentConfig {
26118 start: block_start, ..
26119 }) = language.block_comment()
26120 {
26121 let block_start_trimmed = block_start.trim_end();
26122 if block_start_trimmed.starts_with(delimiter.trim_end()) {
26123 let line_content = snapshot
26124 .chars_for_range(range.clone())
26125 .skip(num_of_whitespaces)
26126 .take(block_start_trimmed.len())
26127 .collect::<String>();
26128
26129 if line_content.starts_with(block_start_trimmed) {
26130 return None;
26131 }
26132 }
26133 }
26134
26135 let cursor_is_placed_after_comment_marker =
26136 num_of_whitespaces + trimmed_len <= start_point.column as usize;
26137 if cursor_is_placed_after_comment_marker {
26138 if !is_repl {
26139 return Some(delimiter.clone());
26140 }
26141
26142 let line_content_after_cursor: String = snapshot
26143 .chars_for_range(range)
26144 .skip(start_point.column as usize)
26145 .collect();
26146
26147 if line_content_after_cursor.trim().is_empty() {
26148 return None;
26149 } else {
26150 return Some(delimiter.clone());
26151 }
26152 } else {
26153 None
26154 }
26155}
26156
26157fn documentation_delimiter_for_newline(
26158 start_point: &Point,
26159 buffer: &MultiBufferSnapshot,
26160 language: &LanguageScope,
26161 newline_config: &mut NewlineConfig,
26162) -> Option<Arc<str>> {
26163 let BlockCommentConfig {
26164 start: start_tag,
26165 end: end_tag,
26166 prefix: delimiter,
26167 tab_size: len,
26168 } = language.documentation_comment()?;
26169 let is_within_block_comment = buffer
26170 .language_scope_at(*start_point)
26171 .is_some_and(|scope| scope.override_name() == Some("comment"));
26172 if !is_within_block_comment {
26173 return None;
26174 }
26175
26176 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26177
26178 let num_of_whitespaces = snapshot
26179 .chars_for_range(range.clone())
26180 .take_while(|c| c.is_whitespace())
26181 .count();
26182
26183 // 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.
26184 let column = start_point.column;
26185 let cursor_is_after_start_tag = {
26186 let start_tag_len = start_tag.len();
26187 let start_tag_line = snapshot
26188 .chars_for_range(range.clone())
26189 .skip(num_of_whitespaces)
26190 .take(start_tag_len)
26191 .collect::<String>();
26192 if start_tag_line.starts_with(start_tag.as_ref()) {
26193 num_of_whitespaces + start_tag_len <= column as usize
26194 } else {
26195 false
26196 }
26197 };
26198
26199 let cursor_is_after_delimiter = {
26200 let delimiter_trim = delimiter.trim_end();
26201 let delimiter_line = snapshot
26202 .chars_for_range(range.clone())
26203 .skip(num_of_whitespaces)
26204 .take(delimiter_trim.len())
26205 .collect::<String>();
26206 if delimiter_line.starts_with(delimiter_trim) {
26207 num_of_whitespaces + delimiter_trim.len() <= column as usize
26208 } else {
26209 false
26210 }
26211 };
26212
26213 let mut needs_extra_line = false;
26214 let mut extra_line_additional_indent = IndentSize::spaces(0);
26215
26216 let cursor_is_before_end_tag_if_exists = {
26217 let mut char_position = 0u32;
26218 let mut end_tag_offset = None;
26219
26220 'outer: for chunk in snapshot.text_for_range(range) {
26221 if let Some(byte_pos) = chunk.find(&**end_tag) {
26222 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26223 end_tag_offset = Some(char_position + chars_before_match);
26224 break 'outer;
26225 }
26226 char_position += chunk.chars().count() as u32;
26227 }
26228
26229 if let Some(end_tag_offset) = end_tag_offset {
26230 let cursor_is_before_end_tag = column <= end_tag_offset;
26231 if cursor_is_after_start_tag {
26232 if cursor_is_before_end_tag {
26233 needs_extra_line = true;
26234 }
26235 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26236 if cursor_is_at_start_of_end_tag {
26237 extra_line_additional_indent.len = *len;
26238 }
26239 }
26240 cursor_is_before_end_tag
26241 } else {
26242 true
26243 }
26244 };
26245
26246 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26247 && cursor_is_before_end_tag_if_exists
26248 {
26249 let additional_indent = if cursor_is_after_start_tag {
26250 IndentSize::spaces(*len)
26251 } else {
26252 IndentSize::spaces(0)
26253 };
26254
26255 *newline_config = NewlineConfig::Newline {
26256 additional_indent,
26257 extra_line_additional_indent: if needs_extra_line {
26258 Some(extra_line_additional_indent)
26259 } else {
26260 None
26261 },
26262 prevent_auto_indent: true,
26263 };
26264 Some(delimiter.clone())
26265 } else {
26266 None
26267 }
26268}
26269
26270const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26271
26272fn list_delimiter_for_newline(
26273 start_point: &Point,
26274 buffer: &MultiBufferSnapshot,
26275 language: &LanguageScope,
26276 newline_config: &mut NewlineConfig,
26277) -> Option<Arc<str>> {
26278 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26279
26280 let num_of_whitespaces = snapshot
26281 .chars_for_range(range.clone())
26282 .take_while(|c| c.is_whitespace())
26283 .count();
26284
26285 let task_list_entries: Vec<_> = language
26286 .task_list()
26287 .into_iter()
26288 .flat_map(|config| {
26289 config
26290 .prefixes
26291 .iter()
26292 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26293 })
26294 .collect();
26295 let unordered_list_entries: Vec<_> = language
26296 .unordered_list()
26297 .iter()
26298 .map(|marker| (marker.as_ref(), marker.as_ref()))
26299 .collect();
26300
26301 let all_entries: Vec<_> = task_list_entries
26302 .into_iter()
26303 .chain(unordered_list_entries)
26304 .collect();
26305
26306 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26307 let candidate: String = snapshot
26308 .chars_for_range(range.clone())
26309 .skip(num_of_whitespaces)
26310 .take(max_prefix_len)
26311 .collect();
26312
26313 if let Some((prefix, continuation)) = all_entries
26314 .iter()
26315 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26316 .max_by_key(|(prefix, _)| prefix.len())
26317 {
26318 let end_of_prefix = num_of_whitespaces + prefix.len();
26319 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26320 let has_content_after_marker = snapshot
26321 .chars_for_range(range)
26322 .skip(end_of_prefix)
26323 .any(|c| !c.is_whitespace());
26324
26325 if has_content_after_marker && cursor_is_after_prefix {
26326 return Some((*continuation).into());
26327 }
26328
26329 if start_point.column as usize == end_of_prefix {
26330 if num_of_whitespaces == 0 {
26331 *newline_config = NewlineConfig::ClearCurrentLine;
26332 } else {
26333 *newline_config = NewlineConfig::UnindentCurrentLine {
26334 continuation: (*continuation).into(),
26335 };
26336 }
26337 }
26338
26339 return None;
26340 }
26341 }
26342
26343 let candidate: String = snapshot
26344 .chars_for_range(range.clone())
26345 .skip(num_of_whitespaces)
26346 .take(ORDERED_LIST_MAX_MARKER_LEN)
26347 .collect();
26348
26349 for ordered_config in language.ordered_list() {
26350 let regex = match Regex::new(&ordered_config.pattern) {
26351 Ok(r) => r,
26352 Err(_) => continue,
26353 };
26354
26355 if let Some(captures) = regex.captures(&candidate) {
26356 let full_match = captures.get(0)?;
26357 let marker_len = full_match.len();
26358 let end_of_prefix = num_of_whitespaces + marker_len;
26359 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26360
26361 let has_content_after_marker = snapshot
26362 .chars_for_range(range)
26363 .skip(end_of_prefix)
26364 .any(|c| !c.is_whitespace());
26365
26366 if has_content_after_marker && cursor_is_after_prefix {
26367 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26368 let continuation = ordered_config
26369 .format
26370 .replace("{1}", &(number + 1).to_string());
26371 return Some(continuation.into());
26372 }
26373
26374 if start_point.column as usize == end_of_prefix {
26375 let continuation = ordered_config.format.replace("{1}", "1");
26376 if num_of_whitespaces == 0 {
26377 *newline_config = NewlineConfig::ClearCurrentLine;
26378 } else {
26379 *newline_config = NewlineConfig::UnindentCurrentLine {
26380 continuation: continuation.into(),
26381 };
26382 }
26383 }
26384
26385 return None;
26386 }
26387 }
26388
26389 None
26390}
26391
26392fn is_list_prefix_row(
26393 row: MultiBufferRow,
26394 buffer: &MultiBufferSnapshot,
26395 language: &LanguageScope,
26396) -> bool {
26397 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26398 return false;
26399 };
26400
26401 let num_of_whitespaces = snapshot
26402 .chars_for_range(range.clone())
26403 .take_while(|c| c.is_whitespace())
26404 .count();
26405
26406 let task_list_prefixes: Vec<_> = language
26407 .task_list()
26408 .into_iter()
26409 .flat_map(|config| {
26410 config
26411 .prefixes
26412 .iter()
26413 .map(|p| p.as_ref())
26414 .collect::<Vec<_>>()
26415 })
26416 .collect();
26417 let unordered_list_markers: Vec<_> = language
26418 .unordered_list()
26419 .iter()
26420 .map(|marker| marker.as_ref())
26421 .collect();
26422 let all_prefixes: Vec<_> = task_list_prefixes
26423 .into_iter()
26424 .chain(unordered_list_markers)
26425 .collect();
26426 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26427 let candidate: String = snapshot
26428 .chars_for_range(range.clone())
26429 .skip(num_of_whitespaces)
26430 .take(max_prefix_len)
26431 .collect();
26432 if all_prefixes
26433 .iter()
26434 .any(|prefix| candidate.starts_with(*prefix))
26435 {
26436 return true;
26437 }
26438 }
26439
26440 let ordered_list_candidate: String = snapshot
26441 .chars_for_range(range)
26442 .skip(num_of_whitespaces)
26443 .take(ORDERED_LIST_MAX_MARKER_LEN)
26444 .collect();
26445 for ordered_config in language.ordered_list() {
26446 let regex = match Regex::new(&ordered_config.pattern) {
26447 Ok(r) => r,
26448 Err(_) => continue,
26449 };
26450 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26451 return captures.get(0).is_some();
26452 }
26453 }
26454
26455 false
26456}
26457
26458#[derive(Debug)]
26459enum NewlineConfig {
26460 /// Insert newline with optional additional indent and optional extra blank line
26461 Newline {
26462 additional_indent: IndentSize,
26463 extra_line_additional_indent: Option<IndentSize>,
26464 prevent_auto_indent: bool,
26465 },
26466 /// Clear the current line
26467 ClearCurrentLine,
26468 /// Unindent the current line and add continuation
26469 UnindentCurrentLine { continuation: Arc<str> },
26470}
26471
26472impl NewlineConfig {
26473 fn has_extra_line(&self) -> bool {
26474 matches!(
26475 self,
26476 Self::Newline {
26477 extra_line_additional_indent: Some(_),
26478 ..
26479 }
26480 )
26481 }
26482
26483 fn insert_extra_newline_brackets(
26484 buffer: &MultiBufferSnapshot,
26485 range: Range<MultiBufferOffset>,
26486 language: &language::LanguageScope,
26487 ) -> bool {
26488 let leading_whitespace_len = buffer
26489 .reversed_chars_at(range.start)
26490 .take_while(|c| c.is_whitespace() && *c != '\n')
26491 .map(|c| c.len_utf8())
26492 .sum::<usize>();
26493 let trailing_whitespace_len = buffer
26494 .chars_at(range.end)
26495 .take_while(|c| c.is_whitespace() && *c != '\n')
26496 .map(|c| c.len_utf8())
26497 .sum::<usize>();
26498 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26499
26500 language.brackets().any(|(pair, enabled)| {
26501 let pair_start = pair.start.trim_end();
26502 let pair_end = pair.end.trim_start();
26503
26504 enabled
26505 && pair.newline
26506 && buffer.contains_str_at(range.end, pair_end)
26507 && buffer.contains_str_at(
26508 range.start.saturating_sub_usize(pair_start.len()),
26509 pair_start,
26510 )
26511 })
26512 }
26513
26514 fn insert_extra_newline_tree_sitter(
26515 buffer: &MultiBufferSnapshot,
26516 range: Range<MultiBufferOffset>,
26517 ) -> bool {
26518 let (buffer, range) = match buffer
26519 .range_to_buffer_ranges(range.start..=range.end)
26520 .as_slice()
26521 {
26522 [(buffer, range, _)] => (*buffer, range.clone()),
26523 _ => return false,
26524 };
26525 let pair = {
26526 let mut result: Option<BracketMatch<usize>> = None;
26527
26528 for pair in buffer
26529 .all_bracket_ranges(range.start.0..range.end.0)
26530 .filter(move |pair| {
26531 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26532 })
26533 {
26534 let len = pair.close_range.end - pair.open_range.start;
26535
26536 if let Some(existing) = &result {
26537 let existing_len = existing.close_range.end - existing.open_range.start;
26538 if len > existing_len {
26539 continue;
26540 }
26541 }
26542
26543 result = Some(pair);
26544 }
26545
26546 result
26547 };
26548 let Some(pair) = pair else {
26549 return false;
26550 };
26551 pair.newline_only
26552 && buffer
26553 .chars_for_range(pair.open_range.end..range.start.0)
26554 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26555 .all(|c| c.is_whitespace() && c != '\n')
26556 }
26557}
26558
26559fn update_uncommitted_diff_for_buffer(
26560 editor: Entity<Editor>,
26561 project: &Entity<Project>,
26562 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26563 buffer: Entity<MultiBuffer>,
26564 cx: &mut App,
26565) -> Task<()> {
26566 let mut tasks = Vec::new();
26567 project.update(cx, |project, cx| {
26568 for buffer in buffers {
26569 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26570 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26571 }
26572 }
26573 });
26574 cx.spawn(async move |cx| {
26575 let diffs = future::join_all(tasks).await;
26576 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26577 return;
26578 }
26579
26580 buffer.update(cx, |buffer, cx| {
26581 for diff in diffs.into_iter().flatten() {
26582 buffer.add_diff(diff, cx);
26583 }
26584 });
26585 })
26586}
26587
26588fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26589 let tab_size = tab_size.get() as usize;
26590 let mut width = offset;
26591
26592 for ch in text.chars() {
26593 width += if ch == '\t' {
26594 tab_size - (width % tab_size)
26595 } else {
26596 1
26597 };
26598 }
26599
26600 width - offset
26601}
26602
26603#[cfg(test)]
26604mod tests {
26605 use super::*;
26606
26607 #[test]
26608 fn test_string_size_with_expanded_tabs() {
26609 let nz = |val| NonZeroU32::new(val).unwrap();
26610 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26611 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26612 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26613 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26614 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26615 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26616 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26617 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26618 }
26619}
26620
26621/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26622struct WordBreakingTokenizer<'a> {
26623 input: &'a str,
26624}
26625
26626impl<'a> WordBreakingTokenizer<'a> {
26627 fn new(input: &'a str) -> Self {
26628 Self { input }
26629 }
26630}
26631
26632fn is_char_ideographic(ch: char) -> bool {
26633 use unicode_script::Script::*;
26634 use unicode_script::UnicodeScript;
26635 matches!(ch.script(), Han | Tangut | Yi)
26636}
26637
26638fn is_grapheme_ideographic(text: &str) -> bool {
26639 text.chars().any(is_char_ideographic)
26640}
26641
26642fn is_grapheme_whitespace(text: &str) -> bool {
26643 text.chars().any(|x| x.is_whitespace())
26644}
26645
26646fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26647 text.chars()
26648 .next()
26649 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26650}
26651
26652#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26653enum WordBreakToken<'a> {
26654 Word { token: &'a str, grapheme_len: usize },
26655 InlineWhitespace { token: &'a str, grapheme_len: usize },
26656 Newline,
26657}
26658
26659impl<'a> Iterator for WordBreakingTokenizer<'a> {
26660 /// Yields a span, the count of graphemes in the token, and whether it was
26661 /// whitespace. Note that it also breaks at word boundaries.
26662 type Item = WordBreakToken<'a>;
26663
26664 fn next(&mut self) -> Option<Self::Item> {
26665 use unicode_segmentation::UnicodeSegmentation;
26666 if self.input.is_empty() {
26667 return None;
26668 }
26669
26670 let mut iter = self.input.graphemes(true).peekable();
26671 let mut offset = 0;
26672 let mut grapheme_len = 0;
26673 if let Some(first_grapheme) = iter.next() {
26674 let is_newline = first_grapheme == "\n";
26675 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26676 offset += first_grapheme.len();
26677 grapheme_len += 1;
26678 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26679 if let Some(grapheme) = iter.peek().copied()
26680 && should_stay_with_preceding_ideograph(grapheme)
26681 {
26682 offset += grapheme.len();
26683 grapheme_len += 1;
26684 }
26685 } else {
26686 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26687 let mut next_word_bound = words.peek().copied();
26688 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26689 next_word_bound = words.next();
26690 }
26691 while let Some(grapheme) = iter.peek().copied() {
26692 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26693 break;
26694 };
26695 if is_grapheme_whitespace(grapheme) != is_whitespace
26696 || (grapheme == "\n") != is_newline
26697 {
26698 break;
26699 };
26700 offset += grapheme.len();
26701 grapheme_len += 1;
26702 iter.next();
26703 }
26704 }
26705 let token = &self.input[..offset];
26706 self.input = &self.input[offset..];
26707 if token == "\n" {
26708 Some(WordBreakToken::Newline)
26709 } else if is_whitespace {
26710 Some(WordBreakToken::InlineWhitespace {
26711 token,
26712 grapheme_len,
26713 })
26714 } else {
26715 Some(WordBreakToken::Word {
26716 token,
26717 grapheme_len,
26718 })
26719 }
26720 } else {
26721 None
26722 }
26723 }
26724}
26725
26726#[test]
26727fn test_word_breaking_tokenizer() {
26728 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26729 ("", &[]),
26730 (" ", &[whitespace(" ", 2)]),
26731 ("Ʒ", &[word("Ʒ", 1)]),
26732 ("Ǽ", &[word("Ǽ", 1)]),
26733 ("⋑", &[word("⋑", 1)]),
26734 ("⋑⋑", &[word("⋑⋑", 2)]),
26735 (
26736 "原理,进而",
26737 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26738 ),
26739 (
26740 "hello world",
26741 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26742 ),
26743 (
26744 "hello, world",
26745 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26746 ),
26747 (
26748 " hello world",
26749 &[
26750 whitespace(" ", 2),
26751 word("hello", 5),
26752 whitespace(" ", 1),
26753 word("world", 5),
26754 ],
26755 ),
26756 (
26757 "这是什么 \n 钢笔",
26758 &[
26759 word("这", 1),
26760 word("是", 1),
26761 word("什", 1),
26762 word("么", 1),
26763 whitespace(" ", 1),
26764 newline(),
26765 whitespace(" ", 1),
26766 word("钢", 1),
26767 word("笔", 1),
26768 ],
26769 ),
26770 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26771 ];
26772
26773 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26774 WordBreakToken::Word {
26775 token,
26776 grapheme_len,
26777 }
26778 }
26779
26780 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26781 WordBreakToken::InlineWhitespace {
26782 token,
26783 grapheme_len,
26784 }
26785 }
26786
26787 fn newline() -> WordBreakToken<'static> {
26788 WordBreakToken::Newline
26789 }
26790
26791 for (input, result) in tests {
26792 assert_eq!(
26793 WordBreakingTokenizer::new(input)
26794 .collect::<Vec<_>>()
26795 .as_slice(),
26796 *result,
26797 );
26798 }
26799}
26800
26801fn wrap_with_prefix(
26802 first_line_prefix: String,
26803 subsequent_lines_prefix: String,
26804 unwrapped_text: String,
26805 wrap_column: usize,
26806 tab_size: NonZeroU32,
26807 preserve_existing_whitespace: bool,
26808) -> String {
26809 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26810 let subsequent_lines_prefix_len =
26811 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26812 let mut wrapped_text = String::new();
26813 let mut current_line = first_line_prefix;
26814 let mut is_first_line = true;
26815
26816 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26817 let mut current_line_len = first_line_prefix_len;
26818 let mut in_whitespace = false;
26819 for token in tokenizer {
26820 let have_preceding_whitespace = in_whitespace;
26821 match token {
26822 WordBreakToken::Word {
26823 token,
26824 grapheme_len,
26825 } => {
26826 in_whitespace = false;
26827 let current_prefix_len = if is_first_line {
26828 first_line_prefix_len
26829 } else {
26830 subsequent_lines_prefix_len
26831 };
26832 if current_line_len + grapheme_len > wrap_column
26833 && current_line_len != current_prefix_len
26834 {
26835 wrapped_text.push_str(current_line.trim_end());
26836 wrapped_text.push('\n');
26837 is_first_line = false;
26838 current_line = subsequent_lines_prefix.clone();
26839 current_line_len = subsequent_lines_prefix_len;
26840 }
26841 current_line.push_str(token);
26842 current_line_len += grapheme_len;
26843 }
26844 WordBreakToken::InlineWhitespace {
26845 mut token,
26846 mut grapheme_len,
26847 } => {
26848 in_whitespace = true;
26849 if have_preceding_whitespace && !preserve_existing_whitespace {
26850 continue;
26851 }
26852 if !preserve_existing_whitespace {
26853 // Keep a single whitespace grapheme as-is
26854 if let Some(first) =
26855 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26856 {
26857 token = first;
26858 } else {
26859 token = " ";
26860 }
26861 grapheme_len = 1;
26862 }
26863 let current_prefix_len = if is_first_line {
26864 first_line_prefix_len
26865 } else {
26866 subsequent_lines_prefix_len
26867 };
26868 if current_line_len + grapheme_len > wrap_column {
26869 wrapped_text.push_str(current_line.trim_end());
26870 wrapped_text.push('\n');
26871 is_first_line = false;
26872 current_line = subsequent_lines_prefix.clone();
26873 current_line_len = subsequent_lines_prefix_len;
26874 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26875 current_line.push_str(token);
26876 current_line_len += grapheme_len;
26877 }
26878 }
26879 WordBreakToken::Newline => {
26880 in_whitespace = true;
26881 let current_prefix_len = if is_first_line {
26882 first_line_prefix_len
26883 } else {
26884 subsequent_lines_prefix_len
26885 };
26886 if preserve_existing_whitespace {
26887 wrapped_text.push_str(current_line.trim_end());
26888 wrapped_text.push('\n');
26889 is_first_line = false;
26890 current_line = subsequent_lines_prefix.clone();
26891 current_line_len = subsequent_lines_prefix_len;
26892 } else if have_preceding_whitespace {
26893 continue;
26894 } else if current_line_len + 1 > wrap_column
26895 && current_line_len != current_prefix_len
26896 {
26897 wrapped_text.push_str(current_line.trim_end());
26898 wrapped_text.push('\n');
26899 is_first_line = false;
26900 current_line = subsequent_lines_prefix.clone();
26901 current_line_len = subsequent_lines_prefix_len;
26902 } else if current_line_len != current_prefix_len {
26903 current_line.push(' ');
26904 current_line_len += 1;
26905 }
26906 }
26907 }
26908 }
26909
26910 if !current_line.is_empty() {
26911 wrapped_text.push_str(¤t_line);
26912 }
26913 wrapped_text
26914}
26915
26916#[test]
26917fn test_wrap_with_prefix() {
26918 assert_eq!(
26919 wrap_with_prefix(
26920 "# ".to_string(),
26921 "# ".to_string(),
26922 "abcdefg".to_string(),
26923 4,
26924 NonZeroU32::new(4).unwrap(),
26925 false,
26926 ),
26927 "# abcdefg"
26928 );
26929 assert_eq!(
26930 wrap_with_prefix(
26931 "".to_string(),
26932 "".to_string(),
26933 "\thello world".to_string(),
26934 8,
26935 NonZeroU32::new(4).unwrap(),
26936 false,
26937 ),
26938 "hello\nworld"
26939 );
26940 assert_eq!(
26941 wrap_with_prefix(
26942 "// ".to_string(),
26943 "// ".to_string(),
26944 "xx \nyy zz aa bb cc".to_string(),
26945 12,
26946 NonZeroU32::new(4).unwrap(),
26947 false,
26948 ),
26949 "// xx yy zz\n// aa bb cc"
26950 );
26951 assert_eq!(
26952 wrap_with_prefix(
26953 String::new(),
26954 String::new(),
26955 "这是什么 \n 钢笔".to_string(),
26956 3,
26957 NonZeroU32::new(4).unwrap(),
26958 false,
26959 ),
26960 "这是什\n么 钢\n笔"
26961 );
26962 assert_eq!(
26963 wrap_with_prefix(
26964 String::new(),
26965 String::new(),
26966 format!("foo{}bar", '\u{2009}'), // thin space
26967 80,
26968 NonZeroU32::new(4).unwrap(),
26969 false,
26970 ),
26971 format!("foo{}bar", '\u{2009}')
26972 );
26973}
26974
26975pub trait CollaborationHub {
26976 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26977 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26978 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26979}
26980
26981impl CollaborationHub for Entity<Project> {
26982 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26983 self.read(cx).collaborators()
26984 }
26985
26986 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26987 self.read(cx).user_store().read(cx).participant_indices()
26988 }
26989
26990 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26991 let this = self.read(cx);
26992 let user_ids = this.collaborators().values().map(|c| c.user_id);
26993 this.user_store().read(cx).participant_names(user_ids, cx)
26994 }
26995}
26996
26997pub trait SemanticsProvider {
26998 fn hover(
26999 &self,
27000 buffer: &Entity<Buffer>,
27001 position: text::Anchor,
27002 cx: &mut App,
27003 ) -> Option<Task<Option<Vec<project::Hover>>>>;
27004
27005 fn inline_values(
27006 &self,
27007 buffer_handle: Entity<Buffer>,
27008 range: Range<text::Anchor>,
27009 cx: &mut App,
27010 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
27011
27012 fn applicable_inlay_chunks(
27013 &self,
27014 buffer: &Entity<Buffer>,
27015 ranges: &[Range<text::Anchor>],
27016 cx: &mut App,
27017 ) -> Vec<Range<BufferRow>>;
27018
27019 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
27020
27021 fn inlay_hints(
27022 &self,
27023 invalidate: InvalidationStrategy,
27024 buffer: Entity<Buffer>,
27025 ranges: Vec<Range<text::Anchor>>,
27026 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27027 cx: &mut App,
27028 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
27029
27030 fn semantic_tokens(
27031 &self,
27032 buffer: Entity<Buffer>,
27033 refresh: Option<RefreshForServer>,
27034 cx: &mut App,
27035 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
27036
27037 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27038
27039 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27040
27041 fn document_highlights(
27042 &self,
27043 buffer: &Entity<Buffer>,
27044 position: text::Anchor,
27045 cx: &mut App,
27046 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
27047
27048 fn definitions(
27049 &self,
27050 buffer: &Entity<Buffer>,
27051 position: text::Anchor,
27052 kind: GotoDefinitionKind,
27053 cx: &mut App,
27054 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
27055
27056 fn range_for_rename(
27057 &self,
27058 buffer: &Entity<Buffer>,
27059 position: text::Anchor,
27060 cx: &mut App,
27061 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
27062
27063 fn perform_rename(
27064 &self,
27065 buffer: &Entity<Buffer>,
27066 position: text::Anchor,
27067 new_name: String,
27068 cx: &mut App,
27069 ) -> Option<Task<Result<ProjectTransaction>>>;
27070}
27071
27072pub trait CompletionProvider {
27073 fn completions(
27074 &self,
27075 excerpt_id: ExcerptId,
27076 buffer: &Entity<Buffer>,
27077 buffer_position: text::Anchor,
27078 trigger: CompletionContext,
27079 window: &mut Window,
27080 cx: &mut Context<Editor>,
27081 ) -> Task<Result<Vec<CompletionResponse>>>;
27082
27083 fn resolve_completions(
27084 &self,
27085 _buffer: Entity<Buffer>,
27086 _completion_indices: Vec<usize>,
27087 _completions: Rc<RefCell<Box<[Completion]>>>,
27088 _cx: &mut Context<Editor>,
27089 ) -> Task<Result<bool>> {
27090 Task::ready(Ok(false))
27091 }
27092
27093 fn apply_additional_edits_for_completion(
27094 &self,
27095 _buffer: Entity<Buffer>,
27096 _completions: Rc<RefCell<Box<[Completion]>>>,
27097 _completion_index: usize,
27098 _push_to_history: bool,
27099 _all_commit_ranges: Vec<Range<language::Anchor>>,
27100 _cx: &mut Context<Editor>,
27101 ) -> Task<Result<Option<language::Transaction>>> {
27102 Task::ready(Ok(None))
27103 }
27104
27105 fn is_completion_trigger(
27106 &self,
27107 buffer: &Entity<Buffer>,
27108 position: language::Anchor,
27109 text: &str,
27110 trigger_in_words: bool,
27111 cx: &mut Context<Editor>,
27112 ) -> bool;
27113
27114 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
27115
27116 fn sort_completions(&self) -> bool {
27117 true
27118 }
27119
27120 fn filter_completions(&self) -> bool {
27121 true
27122 }
27123
27124 fn show_snippets(&self) -> bool {
27125 false
27126 }
27127}
27128
27129pub trait CodeActionProvider {
27130 fn id(&self) -> Arc<str>;
27131
27132 fn code_actions(
27133 &self,
27134 buffer: &Entity<Buffer>,
27135 range: Range<text::Anchor>,
27136 window: &mut Window,
27137 cx: &mut App,
27138 ) -> Task<Result<Vec<CodeAction>>>;
27139
27140 fn apply_code_action(
27141 &self,
27142 buffer_handle: Entity<Buffer>,
27143 action: CodeAction,
27144 excerpt_id: ExcerptId,
27145 push_to_history: bool,
27146 window: &mut Window,
27147 cx: &mut App,
27148 ) -> Task<Result<ProjectTransaction>>;
27149}
27150
27151impl CodeActionProvider for Entity<Project> {
27152 fn id(&self) -> Arc<str> {
27153 "project".into()
27154 }
27155
27156 fn code_actions(
27157 &self,
27158 buffer: &Entity<Buffer>,
27159 range: Range<text::Anchor>,
27160 _window: &mut Window,
27161 cx: &mut App,
27162 ) -> Task<Result<Vec<CodeAction>>> {
27163 self.update(cx, |project, cx| {
27164 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27165 let code_actions = project.code_actions(buffer, range, None, cx);
27166 cx.background_spawn(async move {
27167 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27168 Ok(code_lens_actions
27169 .context("code lens fetch")?
27170 .into_iter()
27171 .flatten()
27172 .chain(
27173 code_actions
27174 .context("code action fetch")?
27175 .into_iter()
27176 .flatten(),
27177 )
27178 .collect())
27179 })
27180 })
27181 }
27182
27183 fn apply_code_action(
27184 &self,
27185 buffer_handle: Entity<Buffer>,
27186 action: CodeAction,
27187 _excerpt_id: ExcerptId,
27188 push_to_history: bool,
27189 _window: &mut Window,
27190 cx: &mut App,
27191 ) -> Task<Result<ProjectTransaction>> {
27192 self.update(cx, |project, cx| {
27193 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27194 })
27195 }
27196}
27197
27198fn snippet_completions(
27199 project: &Project,
27200 buffer: &Entity<Buffer>,
27201 buffer_anchor: text::Anchor,
27202 classifier: CharClassifier,
27203 cx: &mut App,
27204) -> Task<Result<CompletionResponse>> {
27205 let languages = buffer.read(cx).languages_at(buffer_anchor);
27206 let snippet_store = project.snippets().read(cx);
27207
27208 let scopes: Vec<_> = languages
27209 .iter()
27210 .filter_map(|language| {
27211 let language_name = language.lsp_id();
27212 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27213
27214 if snippets.is_empty() {
27215 None
27216 } else {
27217 Some((language.default_scope(), snippets))
27218 }
27219 })
27220 .collect();
27221
27222 if scopes.is_empty() {
27223 return Task::ready(Ok(CompletionResponse {
27224 completions: vec![],
27225 display_options: CompletionDisplayOptions::default(),
27226 is_incomplete: false,
27227 }));
27228 }
27229
27230 let snapshot = buffer.read(cx).text_snapshot();
27231 let executor = cx.background_executor().clone();
27232
27233 cx.background_spawn(async move {
27234 let is_word_char = |c| classifier.is_word(c);
27235
27236 let mut is_incomplete = false;
27237 let mut completions: Vec<Completion> = Vec::new();
27238
27239 const MAX_PREFIX_LEN: usize = 128;
27240 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27241 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27242 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27243
27244 let max_buffer_window: String = snapshot
27245 .text_for_range(window_start..buffer_offset)
27246 .collect();
27247
27248 if max_buffer_window.is_empty() {
27249 return Ok(CompletionResponse {
27250 completions: vec![],
27251 display_options: CompletionDisplayOptions::default(),
27252 is_incomplete: true,
27253 });
27254 }
27255
27256 for (_scope, snippets) in scopes.into_iter() {
27257 // Sort snippets by word count to match longer snippet prefixes first.
27258 let mut sorted_snippet_candidates = snippets
27259 .iter()
27260 .enumerate()
27261 .flat_map(|(snippet_ix, snippet)| {
27262 snippet
27263 .prefix
27264 .iter()
27265 .enumerate()
27266 .map(move |(prefix_ix, prefix)| {
27267 let word_count =
27268 snippet_candidate_suffixes(prefix, &is_word_char).count();
27269 ((snippet_ix, prefix_ix), prefix, word_count)
27270 })
27271 })
27272 .collect_vec();
27273 sorted_snippet_candidates
27274 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27275
27276 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27277
27278 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27279 .take(
27280 sorted_snippet_candidates
27281 .first()
27282 .map(|(_, _, word_count)| *word_count)
27283 .unwrap_or_default(),
27284 )
27285 .collect_vec();
27286
27287 const MAX_RESULTS: usize = 100;
27288 // Each match also remembers how many characters from the buffer it consumed
27289 let mut matches: Vec<(StringMatch, usize)> = vec![];
27290
27291 let mut snippet_list_cutoff_index = 0;
27292 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27293 let word_count = buffer_index + 1;
27294 // Increase `snippet_list_cutoff_index` until we have all of the
27295 // snippets with sufficiently many words.
27296 while sorted_snippet_candidates
27297 .get(snippet_list_cutoff_index)
27298 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27299 *snippet_word_count >= word_count
27300 })
27301 {
27302 snippet_list_cutoff_index += 1;
27303 }
27304
27305 // Take only the candidates with at least `word_count` many words
27306 let snippet_candidates_at_word_len =
27307 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27308
27309 let candidates = snippet_candidates_at_word_len
27310 .iter()
27311 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27312 .enumerate() // index in `sorted_snippet_candidates`
27313 // First char must match
27314 .filter(|(_ix, prefix)| {
27315 itertools::equal(
27316 prefix
27317 .chars()
27318 .next()
27319 .into_iter()
27320 .flat_map(|c| c.to_lowercase()),
27321 buffer_window
27322 .chars()
27323 .next()
27324 .into_iter()
27325 .flat_map(|c| c.to_lowercase()),
27326 )
27327 })
27328 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27329 .collect::<Vec<StringMatchCandidate>>();
27330
27331 matches.extend(
27332 fuzzy::match_strings(
27333 &candidates,
27334 &buffer_window,
27335 buffer_window.chars().any(|c| c.is_uppercase()),
27336 true,
27337 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27338 &Default::default(),
27339 executor.clone(),
27340 )
27341 .await
27342 .into_iter()
27343 .map(|string_match| (string_match, buffer_window.len())),
27344 );
27345
27346 if matches.len() >= MAX_RESULTS {
27347 break;
27348 }
27349 }
27350
27351 let to_lsp = |point: &text::Anchor| {
27352 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27353 point_to_lsp(end)
27354 };
27355 let lsp_end = to_lsp(&buffer_anchor);
27356
27357 if matches.len() >= MAX_RESULTS {
27358 is_incomplete = true;
27359 }
27360
27361 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27362 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27363 sorted_snippet_candidates[string_match.candidate_id];
27364 let snippet = &snippets[snippet_index];
27365 let start = buffer_offset - buffer_window_len;
27366 let start = snapshot.anchor_before(start);
27367 let range = start..buffer_anchor;
27368 let lsp_start = to_lsp(&start);
27369 let lsp_range = lsp::Range {
27370 start: lsp_start,
27371 end: lsp_end,
27372 };
27373 Completion {
27374 replace_range: range,
27375 new_text: snippet.body.clone(),
27376 source: CompletionSource::Lsp {
27377 insert_range: None,
27378 server_id: LanguageServerId(usize::MAX),
27379 resolved: true,
27380 lsp_completion: Box::new(lsp::CompletionItem {
27381 label: snippet.prefix.first().unwrap().clone(),
27382 kind: Some(CompletionItemKind::SNIPPET),
27383 label_details: snippet.description.as_ref().map(|description| {
27384 lsp::CompletionItemLabelDetails {
27385 detail: Some(description.clone()),
27386 description: None,
27387 }
27388 }),
27389 insert_text_format: Some(InsertTextFormat::SNIPPET),
27390 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27391 lsp::InsertReplaceEdit {
27392 new_text: snippet.body.clone(),
27393 insert: lsp_range,
27394 replace: lsp_range,
27395 },
27396 )),
27397 filter_text: Some(snippet.body.clone()),
27398 sort_text: Some(char::MAX.to_string()),
27399 ..lsp::CompletionItem::default()
27400 }),
27401 lsp_defaults: None,
27402 },
27403 label: CodeLabel {
27404 text: matching_prefix.clone(),
27405 runs: Vec::new(),
27406 filter_range: 0..matching_prefix.len(),
27407 },
27408 icon_path: None,
27409 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27410 single_line: snippet.name.clone().into(),
27411 plain_text: snippet
27412 .description
27413 .clone()
27414 .map(|description| description.into()),
27415 }),
27416 insert_text_mode: None,
27417 confirm: None,
27418 match_start: Some(start),
27419 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27420 }
27421 }));
27422 }
27423
27424 Ok(CompletionResponse {
27425 completions,
27426 display_options: CompletionDisplayOptions::default(),
27427 is_incomplete,
27428 })
27429 })
27430}
27431
27432impl CompletionProvider for Entity<Project> {
27433 fn completions(
27434 &self,
27435 _excerpt_id: ExcerptId,
27436 buffer: &Entity<Buffer>,
27437 buffer_position: text::Anchor,
27438 options: CompletionContext,
27439 _window: &mut Window,
27440 cx: &mut Context<Editor>,
27441 ) -> Task<Result<Vec<CompletionResponse>>> {
27442 self.update(cx, |project, cx| {
27443 let task = project.completions(buffer, buffer_position, options, cx);
27444 cx.background_spawn(task)
27445 })
27446 }
27447
27448 fn resolve_completions(
27449 &self,
27450 buffer: Entity<Buffer>,
27451 completion_indices: Vec<usize>,
27452 completions: Rc<RefCell<Box<[Completion]>>>,
27453 cx: &mut Context<Editor>,
27454 ) -> Task<Result<bool>> {
27455 self.update(cx, |project, cx| {
27456 project.lsp_store().update(cx, |lsp_store, cx| {
27457 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27458 })
27459 })
27460 }
27461
27462 fn apply_additional_edits_for_completion(
27463 &self,
27464 buffer: Entity<Buffer>,
27465 completions: Rc<RefCell<Box<[Completion]>>>,
27466 completion_index: usize,
27467 push_to_history: bool,
27468 all_commit_ranges: Vec<Range<language::Anchor>>,
27469 cx: &mut Context<Editor>,
27470 ) -> Task<Result<Option<language::Transaction>>> {
27471 self.update(cx, |project, cx| {
27472 project.lsp_store().update(cx, |lsp_store, cx| {
27473 lsp_store.apply_additional_edits_for_completion(
27474 buffer,
27475 completions,
27476 completion_index,
27477 push_to_history,
27478 all_commit_ranges,
27479 cx,
27480 )
27481 })
27482 })
27483 }
27484
27485 fn is_completion_trigger(
27486 &self,
27487 buffer: &Entity<Buffer>,
27488 position: language::Anchor,
27489 text: &str,
27490 trigger_in_words: bool,
27491 cx: &mut Context<Editor>,
27492 ) -> bool {
27493 let mut chars = text.chars();
27494 let char = if let Some(char) = chars.next() {
27495 char
27496 } else {
27497 return false;
27498 };
27499 if chars.next().is_some() {
27500 return false;
27501 }
27502
27503 let buffer = buffer.read(cx);
27504 let snapshot = buffer.snapshot();
27505 let classifier = snapshot
27506 .char_classifier_at(position)
27507 .scope_context(Some(CharScopeContext::Completion));
27508 if trigger_in_words && classifier.is_word(char) {
27509 return true;
27510 }
27511
27512 buffer.completion_triggers().contains(text)
27513 }
27514
27515 fn show_snippets(&self) -> bool {
27516 true
27517 }
27518}
27519
27520impl SemanticsProvider for WeakEntity<Project> {
27521 fn hover(
27522 &self,
27523 buffer: &Entity<Buffer>,
27524 position: text::Anchor,
27525 cx: &mut App,
27526 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27527 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27528 .ok()
27529 }
27530
27531 fn document_highlights(
27532 &self,
27533 buffer: &Entity<Buffer>,
27534 position: text::Anchor,
27535 cx: &mut App,
27536 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27537 self.update(cx, |project, cx| {
27538 project.document_highlights(buffer, position, cx)
27539 })
27540 .ok()
27541 }
27542
27543 fn definitions(
27544 &self,
27545 buffer: &Entity<Buffer>,
27546 position: text::Anchor,
27547 kind: GotoDefinitionKind,
27548 cx: &mut App,
27549 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27550 self.update(cx, |project, cx| match kind {
27551 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27552 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27553 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27554 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27555 })
27556 .ok()
27557 }
27558
27559 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27560 self.update(cx, |project, cx| {
27561 if project
27562 .active_debug_session(cx)
27563 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27564 {
27565 return true;
27566 }
27567
27568 buffer.update(cx, |buffer, cx| {
27569 project.any_language_server_supports_inlay_hints(buffer, cx)
27570 })
27571 })
27572 .unwrap_or(false)
27573 }
27574
27575 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27576 self.update(cx, |project, cx| {
27577 buffer.update(cx, |buffer, cx| {
27578 project.any_language_server_supports_semantic_tokens(buffer, cx)
27579 })
27580 })
27581 .unwrap_or(false)
27582 }
27583
27584 fn inline_values(
27585 &self,
27586 buffer_handle: Entity<Buffer>,
27587 range: Range<text::Anchor>,
27588 cx: &mut App,
27589 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27590 self.update(cx, |project, cx| {
27591 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27592
27593 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27594 })
27595 .ok()
27596 .flatten()
27597 }
27598
27599 fn applicable_inlay_chunks(
27600 &self,
27601 buffer: &Entity<Buffer>,
27602 ranges: &[Range<text::Anchor>],
27603 cx: &mut App,
27604 ) -> Vec<Range<BufferRow>> {
27605 self.update(cx, |project, cx| {
27606 project.lsp_store().update(cx, |lsp_store, cx| {
27607 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27608 })
27609 })
27610 .unwrap_or_default()
27611 }
27612
27613 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27614 self.update(cx, |project, cx| {
27615 project.lsp_store().update(cx, |lsp_store, _| {
27616 lsp_store.invalidate_inlay_hints(for_buffers)
27617 })
27618 })
27619 .ok();
27620 }
27621
27622 fn inlay_hints(
27623 &self,
27624 invalidate: InvalidationStrategy,
27625 buffer: Entity<Buffer>,
27626 ranges: Vec<Range<text::Anchor>>,
27627 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27628 cx: &mut App,
27629 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27630 self.update(cx, |project, cx| {
27631 project.lsp_store().update(cx, |lsp_store, cx| {
27632 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27633 })
27634 })
27635 .ok()
27636 }
27637
27638 fn semantic_tokens(
27639 &self,
27640 buffer: Entity<Buffer>,
27641 refresh: Option<RefreshForServer>,
27642 cx: &mut App,
27643 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27644 self.update(cx, |this, cx| {
27645 this.lsp_store().update(cx, |lsp_store, cx| {
27646 lsp_store.semantic_tokens(buffer, refresh, cx)
27647 })
27648 })
27649 .ok()
27650 }
27651
27652 fn range_for_rename(
27653 &self,
27654 buffer: &Entity<Buffer>,
27655 position: text::Anchor,
27656 cx: &mut App,
27657 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27658 self.update(cx, |project, cx| {
27659 let buffer = buffer.clone();
27660 let task = project.prepare_rename(buffer.clone(), position, cx);
27661 cx.spawn(async move |_, cx| {
27662 Ok(match task.await? {
27663 PrepareRenameResponse::Success(range) => Some(range),
27664 PrepareRenameResponse::InvalidPosition => None,
27665 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27666 // Fallback on using TreeSitter info to determine identifier range
27667 buffer.read_with(cx, |buffer, _| {
27668 let snapshot = buffer.snapshot();
27669 let (range, kind) = snapshot.surrounding_word(position, None);
27670 if kind != Some(CharKind::Word) {
27671 return None;
27672 }
27673 Some(
27674 snapshot.anchor_before(range.start)
27675 ..snapshot.anchor_after(range.end),
27676 )
27677 })
27678 }
27679 })
27680 })
27681 })
27682 .ok()
27683 }
27684
27685 fn perform_rename(
27686 &self,
27687 buffer: &Entity<Buffer>,
27688 position: text::Anchor,
27689 new_name: String,
27690 cx: &mut App,
27691 ) -> Option<Task<Result<ProjectTransaction>>> {
27692 self.update(cx, |project, cx| {
27693 project.perform_rename(buffer.clone(), position, new_name, cx)
27694 })
27695 .ok()
27696 }
27697}
27698
27699fn consume_contiguous_rows(
27700 contiguous_row_selections: &mut Vec<Selection<Point>>,
27701 selection: &Selection<Point>,
27702 display_map: &DisplaySnapshot,
27703 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27704) -> (MultiBufferRow, MultiBufferRow) {
27705 contiguous_row_selections.push(selection.clone());
27706 let start_row = starting_row(selection, display_map);
27707 let mut end_row = ending_row(selection, display_map);
27708
27709 while let Some(next_selection) = selections.peek() {
27710 if next_selection.start.row <= end_row.0 {
27711 end_row = ending_row(next_selection, display_map);
27712 contiguous_row_selections.push(selections.next().unwrap().clone());
27713 } else {
27714 break;
27715 }
27716 }
27717 (start_row, end_row)
27718}
27719
27720fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27721 if selection.start.column > 0 {
27722 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27723 } else {
27724 MultiBufferRow(selection.start.row)
27725 }
27726}
27727
27728fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27729 if next_selection.end.column > 0 || next_selection.is_empty() {
27730 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27731 } else {
27732 MultiBufferRow(next_selection.end.row)
27733 }
27734}
27735
27736impl EditorSnapshot {
27737 pub fn remote_selections_in_range<'a>(
27738 &'a self,
27739 range: &'a Range<Anchor>,
27740 collaboration_hub: &dyn CollaborationHub,
27741 cx: &'a App,
27742 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27743 let participant_names = collaboration_hub.user_names(cx);
27744 let participant_indices = collaboration_hub.user_participant_indices(cx);
27745 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27746 let collaborators_by_replica_id = collaborators_by_peer_id
27747 .values()
27748 .map(|collaborator| (collaborator.replica_id, collaborator))
27749 .collect::<HashMap<_, _>>();
27750 self.buffer_snapshot()
27751 .selections_in_range(range, false)
27752 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27753 if replica_id == ReplicaId::AGENT {
27754 Some(RemoteSelection {
27755 replica_id,
27756 selection,
27757 cursor_shape,
27758 line_mode,
27759 collaborator_id: CollaboratorId::Agent,
27760 user_name: Some("Agent".into()),
27761 color: cx.theme().players().agent(),
27762 })
27763 } else {
27764 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27765 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27766 let user_name = participant_names.get(&collaborator.user_id).cloned();
27767 Some(RemoteSelection {
27768 replica_id,
27769 selection,
27770 cursor_shape,
27771 line_mode,
27772 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27773 user_name,
27774 color: if let Some(index) = participant_index {
27775 cx.theme().players().color_for_participant(index.0)
27776 } else {
27777 cx.theme().players().absent()
27778 },
27779 })
27780 }
27781 })
27782 }
27783
27784 pub fn hunks_for_ranges(
27785 &self,
27786 ranges: impl IntoIterator<Item = Range<Point>>,
27787 ) -> Vec<MultiBufferDiffHunk> {
27788 let mut hunks = Vec::new();
27789 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27790 HashMap::default();
27791 for query_range in ranges {
27792 let query_rows =
27793 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27794 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27795 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27796 ) {
27797 // Include deleted hunks that are adjacent to the query range, because
27798 // otherwise they would be missed.
27799 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27800 if hunk.status().is_deleted() {
27801 intersects_range |= hunk.row_range.start == query_rows.end;
27802 intersects_range |= hunk.row_range.end == query_rows.start;
27803 }
27804 if intersects_range {
27805 if !processed_buffer_rows
27806 .entry(hunk.buffer_id)
27807 .or_default()
27808 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27809 {
27810 continue;
27811 }
27812 hunks.push(hunk);
27813 }
27814 }
27815 }
27816
27817 hunks
27818 }
27819
27820 fn display_diff_hunks_for_rows<'a>(
27821 &'a self,
27822 display_rows: Range<DisplayRow>,
27823 folded_buffers: &'a HashSet<BufferId>,
27824 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27825 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27826 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27827
27828 self.buffer_snapshot()
27829 .diff_hunks_in_range(buffer_start..buffer_end)
27830 .filter_map(|hunk| {
27831 if folded_buffers.contains(&hunk.buffer_id)
27832 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27833 {
27834 return None;
27835 }
27836
27837 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27838 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27839 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27840 let line_len = self.buffer_snapshot().line_len(last_row);
27841 Point::new(last_row.0, line_len)
27842 } else {
27843 Point::new(hunk.row_range.end.0, 0)
27844 };
27845
27846 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27847 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27848
27849 let display_hunk = if hunk_display_start.column() != 0 {
27850 DisplayDiffHunk::Folded {
27851 display_row: hunk_display_start.row(),
27852 }
27853 } else {
27854 let mut end_row = hunk_display_end.row();
27855 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27856 end_row.0 += 1;
27857 }
27858 let is_created_file = hunk.is_created_file();
27859
27860 DisplayDiffHunk::Unfolded {
27861 status: hunk.status(),
27862 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27863 ..hunk.diff_base_byte_range.end.0,
27864 word_diffs: hunk.word_diffs,
27865 display_row_range: hunk_display_start.row()..end_row,
27866 multi_buffer_range: Anchor::range_in_buffer(
27867 hunk.excerpt_id,
27868 hunk.buffer_range,
27869 ),
27870 is_created_file,
27871 }
27872 };
27873
27874 Some(display_hunk)
27875 })
27876 }
27877
27878 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27879 self.display_snapshot
27880 .buffer_snapshot()
27881 .language_at(position)
27882 }
27883
27884 pub fn is_focused(&self) -> bool {
27885 self.is_focused
27886 }
27887
27888 pub fn placeholder_text(&self) -> Option<String> {
27889 self.placeholder_display_snapshot
27890 .as_ref()
27891 .map(|display_map| display_map.text())
27892 }
27893
27894 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27895 self.scroll_anchor.scroll_position(&self.display_snapshot)
27896 }
27897
27898 pub fn gutter_dimensions(
27899 &self,
27900 font_id: FontId,
27901 font_size: Pixels,
27902 style: &EditorStyle,
27903 window: &mut Window,
27904 cx: &App,
27905 ) -> GutterDimensions {
27906 if self.show_gutter
27907 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27908 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27909 {
27910 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27911 matches!(
27912 ProjectSettings::get_global(cx).git.git_gutter,
27913 GitGutterSetting::TrackedFiles
27914 )
27915 });
27916 let gutter_settings = EditorSettings::get_global(cx).gutter;
27917 let show_line_numbers = self
27918 .show_line_numbers
27919 .unwrap_or(gutter_settings.line_numbers);
27920 let line_gutter_width = if show_line_numbers {
27921 // Avoid flicker-like gutter resizes when the line number gains another digit by
27922 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27923 let min_width_for_number_on_gutter =
27924 ch_advance * gutter_settings.min_line_number_digits as f32;
27925 self.max_line_number_width(style, window)
27926 .max(min_width_for_number_on_gutter)
27927 } else {
27928 0.0.into()
27929 };
27930
27931 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27932 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27933
27934 let git_blame_entries_width =
27935 self.git_blame_gutter_max_author_length
27936 .map(|max_author_length| {
27937 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27938 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27939
27940 /// The number of characters to dedicate to gaps and margins.
27941 const SPACING_WIDTH: usize = 4;
27942
27943 let max_char_count = max_author_length.min(renderer.max_author_length())
27944 + ::git::SHORT_SHA_LENGTH
27945 + MAX_RELATIVE_TIMESTAMP.len()
27946 + SPACING_WIDTH;
27947
27948 ch_advance * max_char_count
27949 });
27950
27951 let is_singleton = self.buffer_snapshot().is_singleton();
27952
27953 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27954 left_padding += if !is_singleton {
27955 ch_width * 4.0
27956 } else if show_runnables || show_breakpoints {
27957 ch_width * 3.0
27958 } else if show_git_gutter && show_line_numbers {
27959 ch_width * 2.0
27960 } else if show_git_gutter || show_line_numbers {
27961 ch_width
27962 } else {
27963 px(0.)
27964 };
27965
27966 let shows_folds = is_singleton && gutter_settings.folds;
27967
27968 let right_padding = if shows_folds && show_line_numbers {
27969 ch_width * 4.0
27970 } else if shows_folds || (!is_singleton && show_line_numbers) {
27971 ch_width * 3.0
27972 } else if show_line_numbers {
27973 ch_width
27974 } else {
27975 px(0.)
27976 };
27977
27978 GutterDimensions {
27979 left_padding,
27980 right_padding,
27981 width: line_gutter_width + left_padding + right_padding,
27982 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27983 git_blame_entries_width,
27984 }
27985 } else if self.offset_content {
27986 GutterDimensions::default_with_margin(font_id, font_size, cx)
27987 } else {
27988 GutterDimensions::default()
27989 }
27990 }
27991
27992 pub fn render_crease_toggle(
27993 &self,
27994 buffer_row: MultiBufferRow,
27995 row_contains_cursor: bool,
27996 editor: Entity<Editor>,
27997 window: &mut Window,
27998 cx: &mut App,
27999 ) -> Option<AnyElement> {
28000 let folded = self.is_line_folded(buffer_row);
28001 let mut is_foldable = false;
28002
28003 if let Some(crease) = self
28004 .crease_snapshot
28005 .query_row(buffer_row, self.buffer_snapshot())
28006 {
28007 is_foldable = true;
28008 match crease {
28009 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
28010 if let Some(render_toggle) = render_toggle {
28011 let toggle_callback =
28012 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
28013 if folded {
28014 editor.update(cx, |editor, cx| {
28015 editor.fold_at(buffer_row, window, cx)
28016 });
28017 } else {
28018 editor.update(cx, |editor, cx| {
28019 editor.unfold_at(buffer_row, window, cx)
28020 });
28021 }
28022 });
28023 return Some((render_toggle)(
28024 buffer_row,
28025 folded,
28026 toggle_callback,
28027 window,
28028 cx,
28029 ));
28030 }
28031 }
28032 }
28033 }
28034
28035 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
28036
28037 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
28038 Some(
28039 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
28040 .toggle_state(folded)
28041 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
28042 if folded {
28043 this.unfold_at(buffer_row, window, cx);
28044 } else {
28045 this.fold_at(buffer_row, window, cx);
28046 }
28047 }))
28048 .into_any_element(),
28049 )
28050 } else {
28051 None
28052 }
28053 }
28054
28055 pub fn render_crease_trailer(
28056 &self,
28057 buffer_row: MultiBufferRow,
28058 window: &mut Window,
28059 cx: &mut App,
28060 ) -> Option<AnyElement> {
28061 let folded = self.is_line_folded(buffer_row);
28062 if let Crease::Inline { render_trailer, .. } = self
28063 .crease_snapshot
28064 .query_row(buffer_row, self.buffer_snapshot())?
28065 {
28066 let render_trailer = render_trailer.as_ref()?;
28067 Some(render_trailer(buffer_row, folded, window, cx))
28068 } else {
28069 None
28070 }
28071 }
28072
28073 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
28074 let digit_count = self.widest_line_number().ilog10() + 1;
28075 column_pixels(style, digit_count as usize, window)
28076 }
28077
28078 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
28079 ///
28080 /// This is positive if `base` is before `line`.
28081 fn relative_line_delta(
28082 &self,
28083 current_selection_head: DisplayRow,
28084 first_visible_row: DisplayRow,
28085 consider_wrapped_lines: bool,
28086 ) -> i64 {
28087 let current_selection_head = current_selection_head.as_display_point().to_point(self);
28088 let first_visible_row = first_visible_row.as_display_point().to_point(self);
28089
28090 if consider_wrapped_lines {
28091 let wrap_snapshot = self.wrap_snapshot();
28092 let base_wrap_row = wrap_snapshot
28093 .make_wrap_point(current_selection_head, Bias::Left)
28094 .row();
28095 let wrap_row = wrap_snapshot
28096 .make_wrap_point(first_visible_row, Bias::Left)
28097 .row();
28098
28099 wrap_row.0 as i64 - base_wrap_row.0 as i64
28100 } else {
28101 let fold_snapshot = self.fold_snapshot();
28102 let base_fold_row = fold_snapshot
28103 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
28104 .row();
28105 let fold_row = fold_snapshot
28106 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
28107 .row();
28108
28109 fold_row as i64 - base_fold_row as i64
28110 }
28111 }
28112
28113 /// Returns the unsigned relative line number to display for each row in `rows`.
28114 ///
28115 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
28116 pub fn calculate_relative_line_numbers(
28117 &self,
28118 rows: &Range<DisplayRow>,
28119 current_selection_head: DisplayRow,
28120 count_wrapped_lines: bool,
28121 ) -> HashMap<DisplayRow, u32> {
28122 let initial_offset =
28123 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
28124
28125 self.row_infos(rows.start)
28126 .take(rows.len())
28127 .enumerate()
28128 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
28129 .filter(|(_row, row_info)| {
28130 row_info.buffer_row.is_some()
28131 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
28132 })
28133 .enumerate()
28134 .filter_map(|(i, (row, row_info))| {
28135 // We want to ensure here that the current line has absolute
28136 // numbering, even if we are in a soft-wrapped line. With the
28137 // exception that if we are in a deleted line, we should number this
28138 // relative with 0, as otherwise it would have no line number at all
28139 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
28140
28141 (relative_line_number != 0
28142 || row_info
28143 .diff_status
28144 .is_some_and(|status| status.is_deleted()))
28145 .then_some((row, relative_line_number))
28146 })
28147 .collect()
28148 }
28149}
28150
28151pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28152 let font_size = style.text.font_size.to_pixels(window.rem_size());
28153 let layout = window.text_system().shape_line(
28154 SharedString::from(" ".repeat(column)),
28155 font_size,
28156 &[TextRun {
28157 len: column,
28158 font: style.text.font(),
28159 color: Hsla::default(),
28160 ..Default::default()
28161 }],
28162 None,
28163 );
28164
28165 layout.width
28166}
28167
28168impl Deref for EditorSnapshot {
28169 type Target = DisplaySnapshot;
28170
28171 fn deref(&self) -> &Self::Target {
28172 &self.display_snapshot
28173 }
28174}
28175
28176#[derive(Clone, Debug, PartialEq, Eq)]
28177pub enum EditorEvent {
28178 /// Emitted when the stored review comments change (added, removed, or updated).
28179 ReviewCommentsChanged {
28180 /// The new total count of review comments.
28181 total_count: usize,
28182 },
28183 InputIgnored {
28184 text: Arc<str>,
28185 },
28186 InputHandled {
28187 utf16_range_to_replace: Option<Range<isize>>,
28188 text: Arc<str>,
28189 },
28190 ExcerptsAdded {
28191 buffer: Entity<Buffer>,
28192 predecessor: ExcerptId,
28193 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
28194 },
28195 ExcerptsRemoved {
28196 ids: Vec<ExcerptId>,
28197 removed_buffer_ids: Vec<BufferId>,
28198 },
28199 BufferFoldToggled {
28200 ids: Vec<ExcerptId>,
28201 folded: bool,
28202 },
28203 ExcerptsEdited {
28204 ids: Vec<ExcerptId>,
28205 },
28206 ExcerptsExpanded {
28207 ids: Vec<ExcerptId>,
28208 },
28209 ExpandExcerptsRequested {
28210 excerpt_ids: Vec<ExcerptId>,
28211 lines: u32,
28212 direction: ExpandExcerptDirection,
28213 },
28214 StageOrUnstageRequested {
28215 stage: bool,
28216 hunks: Vec<MultiBufferDiffHunk>,
28217 },
28218 OpenExcerptsRequested {
28219 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28220 split: bool,
28221 },
28222 RestoreRequested {
28223 hunks: Vec<MultiBufferDiffHunk>,
28224 },
28225 BufferEdited,
28226 Edited {
28227 transaction_id: clock::Lamport,
28228 },
28229 Reparsed(BufferId),
28230 Focused,
28231 FocusedIn,
28232 Blurred,
28233 DirtyChanged,
28234 Saved,
28235 TitleChanged,
28236 SelectionsChanged {
28237 local: bool,
28238 },
28239 ScrollPositionChanged {
28240 local: bool,
28241 autoscroll: bool,
28242 },
28243 TransactionUndone {
28244 transaction_id: clock::Lamport,
28245 },
28246 TransactionBegun {
28247 transaction_id: clock::Lamport,
28248 },
28249 CursorShapeChanged,
28250 BreadcrumbsChanged,
28251 OutlineSymbolsChanged,
28252 PushedToNavHistory {
28253 anchor: Anchor,
28254 is_deactivate: bool,
28255 },
28256}
28257
28258impl EventEmitter<EditorEvent> for Editor {}
28259
28260impl Focusable for Editor {
28261 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28262 self.focus_handle.clone()
28263 }
28264}
28265
28266impl Render for Editor {
28267 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28268 EditorElement::new(&cx.entity(), self.create_style(cx))
28269 }
28270}
28271
28272impl EntityInputHandler for Editor {
28273 fn text_for_range(
28274 &mut self,
28275 range_utf16: Range<usize>,
28276 adjusted_range: &mut Option<Range<usize>>,
28277 _: &mut Window,
28278 cx: &mut Context<Self>,
28279 ) -> Option<String> {
28280 let snapshot = self.buffer.read(cx).read(cx);
28281 let start = snapshot.clip_offset_utf16(
28282 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28283 Bias::Left,
28284 );
28285 let end = snapshot.clip_offset_utf16(
28286 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28287 Bias::Right,
28288 );
28289 if (start.0.0..end.0.0) != range_utf16 {
28290 adjusted_range.replace(start.0.0..end.0.0);
28291 }
28292 Some(snapshot.text_for_range(start..end).collect())
28293 }
28294
28295 fn selected_text_range(
28296 &mut self,
28297 ignore_disabled_input: bool,
28298 _: &mut Window,
28299 cx: &mut Context<Self>,
28300 ) -> Option<UTF16Selection> {
28301 // Prevent the IME menu from appearing when holding down an alphabetic key
28302 // while input is disabled.
28303 if !ignore_disabled_input && !self.input_enabled {
28304 return None;
28305 }
28306
28307 let selection = self
28308 .selections
28309 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28310 let range = selection.range();
28311
28312 Some(UTF16Selection {
28313 range: range.start.0.0..range.end.0.0,
28314 reversed: selection.reversed,
28315 })
28316 }
28317
28318 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28319 let snapshot = self.buffer.read(cx).read(cx);
28320 let range = self
28321 .text_highlights(HighlightKey::InputComposition, cx)?
28322 .1
28323 .first()?;
28324 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28325 }
28326
28327 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28328 self.clear_highlights(HighlightKey::InputComposition, cx);
28329 self.ime_transaction.take();
28330 }
28331
28332 fn replace_text_in_range(
28333 &mut self,
28334 range_utf16: Option<Range<usize>>,
28335 text: &str,
28336 window: &mut Window,
28337 cx: &mut Context<Self>,
28338 ) {
28339 if !self.input_enabled {
28340 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28341 return;
28342 }
28343
28344 self.transact(window, cx, |this, window, cx| {
28345 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28346 if let Some(marked_ranges) = this.marked_text_ranges(cx) {
28347 // During IME composition, macOS reports the replacement range
28348 // relative to the first marked region (the only one visible via
28349 // marked_text_range). The correct targets for replacement are the
28350 // marked ranges themselves — one per cursor — so use them directly.
28351 Some(marked_ranges)
28352 } else if range_utf16.start == range_utf16.end {
28353 // An empty replacement range means "insert at cursor" with no text
28354 // to replace. macOS reports the cursor position from its own
28355 // (single-cursor) view of the buffer, which diverges from our actual
28356 // cursor positions after multi-cursor edits have shifted offsets.
28357 // Treating this as range_utf16=None lets each cursor insert in place.
28358 None
28359 } else {
28360 // Outside of IME composition (e.g. Accessibility Keyboard word
28361 // completion), the range is an absolute document offset for the
28362 // newest cursor. Fan it out to all cursors via
28363 // selection_replacement_ranges, which applies the delta relative
28364 // to the newest selection to every cursor.
28365 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28366 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28367 Some(this.selection_replacement_ranges(range_utf16, cx))
28368 }
28369 } else {
28370 this.marked_text_ranges(cx)
28371 };
28372
28373 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28374 let newest_selection_id = this.selections.newest_anchor().id;
28375 this.selections
28376 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28377 .iter()
28378 .zip(ranges_to_replace.iter())
28379 .find_map(|(selection, range)| {
28380 if selection.id == newest_selection_id {
28381 Some(
28382 (range.start.0.0 as isize - selection.head().0.0 as isize)
28383 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28384 )
28385 } else {
28386 None
28387 }
28388 })
28389 });
28390
28391 cx.emit(EditorEvent::InputHandled {
28392 utf16_range_to_replace: range_to_replace,
28393 text: text.into(),
28394 });
28395
28396 if let Some(new_selected_ranges) = new_selected_ranges {
28397 // Only backspace if at least one range covers actual text. When all
28398 // ranges are empty (e.g. a trailing-space insertion from Accessibility
28399 // Keyboard sends replacementRange=cursor..cursor), backspace would
28400 // incorrectly delete the character just before the cursor.
28401 let should_backspace = new_selected_ranges.iter().any(|r| r.start != r.end);
28402 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28403 selections.select_ranges(new_selected_ranges)
28404 });
28405 if should_backspace {
28406 this.backspace(&Default::default(), window, cx);
28407 }
28408 }
28409
28410 this.handle_input(text, window, cx);
28411 });
28412
28413 if let Some(transaction) = self.ime_transaction {
28414 self.buffer.update(cx, |buffer, cx| {
28415 buffer.group_until_transaction(transaction, cx);
28416 });
28417 }
28418
28419 self.unmark_text(window, cx);
28420 }
28421
28422 fn replace_and_mark_text_in_range(
28423 &mut self,
28424 range_utf16: Option<Range<usize>>,
28425 text: &str,
28426 new_selected_range_utf16: Option<Range<usize>>,
28427 window: &mut Window,
28428 cx: &mut Context<Self>,
28429 ) {
28430 if !self.input_enabled {
28431 return;
28432 }
28433
28434 let transaction = self.transact(window, cx, |this, window, cx| {
28435 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28436 let snapshot = this.buffer.read(cx).read(cx);
28437 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28438 for marked_range in &mut marked_ranges {
28439 marked_range.end = marked_range.start + relative_range_utf16.end;
28440 marked_range.start += relative_range_utf16.start;
28441 marked_range.start =
28442 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28443 marked_range.end =
28444 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28445 }
28446 }
28447 Some(marked_ranges)
28448 } else if let Some(range_utf16) = range_utf16 {
28449 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28450 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28451 Some(this.selection_replacement_ranges(range_utf16, cx))
28452 } else {
28453 None
28454 };
28455
28456 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28457 let newest_selection_id = this.selections.newest_anchor().id;
28458 this.selections
28459 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28460 .iter()
28461 .zip(ranges_to_replace.iter())
28462 .find_map(|(selection, range)| {
28463 if selection.id == newest_selection_id {
28464 Some(
28465 (range.start.0.0 as isize - selection.head().0.0 as isize)
28466 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28467 )
28468 } else {
28469 None
28470 }
28471 })
28472 });
28473
28474 cx.emit(EditorEvent::InputHandled {
28475 utf16_range_to_replace: range_to_replace,
28476 text: text.into(),
28477 });
28478
28479 if let Some(ranges) = ranges_to_replace {
28480 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28481 s.select_ranges(ranges)
28482 });
28483 }
28484
28485 let marked_ranges = {
28486 let snapshot = this.buffer.read(cx).read(cx);
28487 this.selections
28488 .disjoint_anchors_arc()
28489 .iter()
28490 .map(|selection| {
28491 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28492 })
28493 .collect::<Vec<_>>()
28494 };
28495
28496 if text.is_empty() {
28497 this.unmark_text(window, cx);
28498 } else {
28499 this.highlight_text(
28500 HighlightKey::InputComposition,
28501 marked_ranges.clone(),
28502 HighlightStyle {
28503 underline: Some(UnderlineStyle {
28504 thickness: px(1.),
28505 color: None,
28506 wavy: false,
28507 }),
28508 ..Default::default()
28509 },
28510 cx,
28511 );
28512 }
28513
28514 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28515 let use_autoclose = this.use_autoclose;
28516 let use_auto_surround = this.use_auto_surround;
28517 this.set_use_autoclose(false);
28518 this.set_use_auto_surround(false);
28519 this.handle_input(text, window, cx);
28520 this.set_use_autoclose(use_autoclose);
28521 this.set_use_auto_surround(use_auto_surround);
28522
28523 if let Some(new_selected_range) = new_selected_range_utf16 {
28524 let snapshot = this.buffer.read(cx).read(cx);
28525 let new_selected_ranges = marked_ranges
28526 .into_iter()
28527 .map(|marked_range| {
28528 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28529 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28530 insertion_start.0 + new_selected_range.start,
28531 ));
28532 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28533 insertion_start.0 + new_selected_range.end,
28534 ));
28535 snapshot.clip_offset_utf16(new_start, Bias::Left)
28536 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28537 })
28538 .collect::<Vec<_>>();
28539
28540 drop(snapshot);
28541 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28542 selections.select_ranges(new_selected_ranges)
28543 });
28544 }
28545 });
28546
28547 self.ime_transaction = self.ime_transaction.or(transaction);
28548 if let Some(transaction) = self.ime_transaction {
28549 self.buffer.update(cx, |buffer, cx| {
28550 buffer.group_until_transaction(transaction, cx);
28551 });
28552 }
28553
28554 if self
28555 .text_highlights(HighlightKey::InputComposition, cx)
28556 .is_none()
28557 {
28558 self.ime_transaction.take();
28559 }
28560 }
28561
28562 fn bounds_for_range(
28563 &mut self,
28564 range_utf16: Range<usize>,
28565 element_bounds: gpui::Bounds<Pixels>,
28566 window: &mut Window,
28567 cx: &mut Context<Self>,
28568 ) -> Option<gpui::Bounds<Pixels>> {
28569 let text_layout_details = self.text_layout_details(window, cx);
28570 let CharacterDimensions {
28571 em_width,
28572 em_advance,
28573 line_height,
28574 } = self.character_dimensions(window, cx);
28575
28576 let snapshot = self.snapshot(window, cx);
28577 let scroll_position = snapshot.scroll_position();
28578 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28579
28580 let start =
28581 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28582 let x = Pixels::from(
28583 ScrollOffset::from(
28584 snapshot.x_for_display_point(start, &text_layout_details)
28585 + self.gutter_dimensions.full_width(),
28586 ) - scroll_left,
28587 );
28588 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28589
28590 Some(Bounds {
28591 origin: element_bounds.origin + point(x, y),
28592 size: size(em_width, line_height),
28593 })
28594 }
28595
28596 fn character_index_for_point(
28597 &mut self,
28598 point: gpui::Point<Pixels>,
28599 _window: &mut Window,
28600 _cx: &mut Context<Self>,
28601 ) -> Option<usize> {
28602 let position_map = self.last_position_map.as_ref()?;
28603 if !position_map.text_hitbox.contains(&point) {
28604 return None;
28605 }
28606 let display_point = position_map.point_for_position(point).previous_valid;
28607 let anchor = position_map
28608 .snapshot
28609 .display_point_to_anchor(display_point, Bias::Left);
28610 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28611 Some(utf16_offset.0.0)
28612 }
28613
28614 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28615 self.expects_character_input
28616 }
28617}
28618
28619trait SelectionExt {
28620 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28621 fn spanned_rows(
28622 &self,
28623 include_end_if_at_line_start: bool,
28624 map: &DisplaySnapshot,
28625 ) -> Range<MultiBufferRow>;
28626}
28627
28628impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28629 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28630 let start = self
28631 .start
28632 .to_point(map.buffer_snapshot())
28633 .to_display_point(map);
28634 let end = self
28635 .end
28636 .to_point(map.buffer_snapshot())
28637 .to_display_point(map);
28638 if self.reversed {
28639 end..start
28640 } else {
28641 start..end
28642 }
28643 }
28644
28645 fn spanned_rows(
28646 &self,
28647 include_end_if_at_line_start: bool,
28648 map: &DisplaySnapshot,
28649 ) -> Range<MultiBufferRow> {
28650 let start = self.start.to_point(map.buffer_snapshot());
28651 let mut end = self.end.to_point(map.buffer_snapshot());
28652 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28653 end.row -= 1;
28654 }
28655
28656 let buffer_start = map.prev_line_boundary(start).0;
28657 let buffer_end = map.next_line_boundary(end).0;
28658 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28659 }
28660}
28661
28662impl<T: InvalidationRegion> InvalidationStack<T> {
28663 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28664 where
28665 S: Clone + ToOffset,
28666 {
28667 while let Some(region) = self.last() {
28668 let all_selections_inside_invalidation_ranges =
28669 if selections.len() == region.ranges().len() {
28670 selections
28671 .iter()
28672 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28673 .all(|(selection, invalidation_range)| {
28674 let head = selection.head().to_offset(buffer);
28675 invalidation_range.start <= head && invalidation_range.end >= head
28676 })
28677 } else {
28678 false
28679 };
28680
28681 if all_selections_inside_invalidation_ranges {
28682 break;
28683 } else {
28684 self.pop();
28685 }
28686 }
28687 }
28688}
28689
28690#[derive(Clone)]
28691struct ErasedEditorImpl(Entity<Editor>);
28692
28693impl ui_input::ErasedEditor for ErasedEditorImpl {
28694 fn text(&self, cx: &App) -> String {
28695 self.0.read(cx).text(cx)
28696 }
28697
28698 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28699 self.0.update(cx, |this, cx| {
28700 this.set_text(text, window, cx);
28701 })
28702 }
28703
28704 fn clear(&self, window: &mut Window, cx: &mut App) {
28705 self.0.update(cx, |this, cx| this.clear(window, cx));
28706 }
28707
28708 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28709 self.0.update(cx, |this, cx| {
28710 this.set_placeholder_text(text, window, cx);
28711 });
28712 }
28713
28714 fn focus_handle(&self, cx: &App) -> FocusHandle {
28715 self.0.read(cx).focus_handle(cx)
28716 }
28717
28718 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28719 let settings = ThemeSettings::get_global(cx);
28720 let theme_color = cx.theme().colors();
28721
28722 let text_style = TextStyle {
28723 font_family: settings.ui_font.family.clone(),
28724 font_features: settings.ui_font.features.clone(),
28725 font_size: rems(0.875).into(),
28726 font_weight: settings.ui_font.weight,
28727 font_style: FontStyle::Normal,
28728 line_height: relative(1.2),
28729 color: theme_color.text,
28730 ..Default::default()
28731 };
28732 let editor_style = EditorStyle {
28733 background: theme_color.ghost_element_background,
28734 local_player: cx.theme().players().local(),
28735 syntax: cx.theme().syntax().clone(),
28736 text: text_style,
28737 ..Default::default()
28738 };
28739 EditorElement::new(&self.0, editor_style).into_any()
28740 }
28741
28742 fn as_any(&self) -> &dyn Any {
28743 &self.0
28744 }
28745
28746 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28747 self.0.update(cx, |editor, cx| {
28748 let editor_offset = editor.buffer().read(cx).len(cx);
28749 editor.change_selections(
28750 SelectionEffects::scroll(Autoscroll::Next),
28751 window,
28752 cx,
28753 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28754 );
28755 });
28756 }
28757
28758 fn subscribe(
28759 &self,
28760 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28761 window: &mut Window,
28762 cx: &mut App,
28763 ) -> Subscription {
28764 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28765 let event = match event {
28766 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28767 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28768 _ => return,
28769 };
28770 (callback)(event, window, cx);
28771 })
28772 }
28773
28774 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28775 self.0.update(cx, |editor, cx| {
28776 editor.set_masked(masked, cx);
28777 });
28778 }
28779}
28780impl<T> Default for InvalidationStack<T> {
28781 fn default() -> Self {
28782 Self(Default::default())
28783 }
28784}
28785
28786impl<T> Deref for InvalidationStack<T> {
28787 type Target = Vec<T>;
28788
28789 fn deref(&self) -> &Self::Target {
28790 &self.0
28791 }
28792}
28793
28794impl<T> DerefMut for InvalidationStack<T> {
28795 fn deref_mut(&mut self) -> &mut Self::Target {
28796 &mut self.0
28797 }
28798}
28799
28800impl InvalidationRegion for SnippetState {
28801 fn ranges(&self) -> &[Range<Anchor>] {
28802 &self.ranges[self.active_index]
28803 }
28804}
28805
28806fn edit_prediction_edit_text(
28807 current_snapshot: &BufferSnapshot,
28808 edits: &[(Range<Anchor>, impl AsRef<str>)],
28809 edit_preview: &EditPreview,
28810 include_deletions: bool,
28811 cx: &App,
28812) -> HighlightedText {
28813 let edits = edits
28814 .iter()
28815 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28816 .collect::<Vec<_>>();
28817
28818 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28819}
28820
28821fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28822 // Fallback for providers that don't provide edit_preview (like Copilot)
28823 // Just show the raw edit text with basic styling
28824 let mut text = String::new();
28825 let mut highlights = Vec::new();
28826
28827 let insertion_highlight_style = HighlightStyle {
28828 color: Some(cx.theme().colors().text),
28829 ..Default::default()
28830 };
28831
28832 for (_, edit_text) in edits {
28833 let start_offset = text.len();
28834 text.push_str(edit_text);
28835 let end_offset = text.len();
28836
28837 if start_offset < end_offset {
28838 highlights.push((start_offset..end_offset, insertion_highlight_style));
28839 }
28840 }
28841
28842 HighlightedText {
28843 text: text.into(),
28844 highlights,
28845 }
28846}
28847
28848pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28849 match severity {
28850 lsp::DiagnosticSeverity::ERROR => colors.error,
28851 lsp::DiagnosticSeverity::WARNING => colors.warning,
28852 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28853 lsp::DiagnosticSeverity::HINT => colors.info,
28854 _ => colors.ignored,
28855 }
28856}
28857
28858pub fn styled_runs_for_code_label<'a>(
28859 label: &'a CodeLabel,
28860 syntax_theme: &'a theme::SyntaxTheme,
28861 local_player: &'a theme::PlayerColor,
28862) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28863 let fade_out = HighlightStyle {
28864 fade_out: Some(0.35),
28865 ..Default::default()
28866 };
28867
28868 if label.runs.is_empty() {
28869 let desc_start = label.filter_range.end;
28870 let fade_run =
28871 (desc_start < label.text.len()).then(|| (desc_start..label.text.len(), fade_out));
28872 return Either::Left(fade_run.into_iter());
28873 }
28874
28875 let mut prev_end = label.filter_range.end;
28876 Either::Right(
28877 label
28878 .runs
28879 .iter()
28880 .enumerate()
28881 .flat_map(move |(ix, (range, highlight_id))| {
28882 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28883 HighlightStyle {
28884 color: Some(local_player.cursor),
28885 ..Default::default()
28886 }
28887 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28888 HighlightStyle {
28889 background_color: Some(local_player.selection),
28890 ..Default::default()
28891 }
28892 } else if let Some(style) = syntax_theme.get(*highlight_id).cloned() {
28893 style
28894 } else {
28895 return Default::default();
28896 };
28897
28898 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28899 let muted_style = style.highlight(fade_out);
28900 if range.start >= label.filter_range.end {
28901 if range.start > prev_end {
28902 runs.push((prev_end..range.start, fade_out));
28903 }
28904 runs.push((range.clone(), muted_style));
28905 } else if range.end <= label.filter_range.end {
28906 runs.push((range.clone(), style));
28907 } else {
28908 runs.push((range.start..label.filter_range.end, style));
28909 runs.push((label.filter_range.end..range.end, muted_style));
28910 }
28911 prev_end = cmp::max(prev_end, range.end);
28912
28913 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28914 runs.push((prev_end..label.text.len(), fade_out));
28915 }
28916
28917 runs
28918 }),
28919 )
28920}
28921
28922pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28923 let mut prev_index = 0;
28924 let mut prev_codepoint: Option<char> = None;
28925 text.char_indices()
28926 .chain([(text.len(), '\0')])
28927 .filter_map(move |(index, codepoint)| {
28928 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28929 let is_boundary = index == text.len()
28930 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28931 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28932 if is_boundary {
28933 let chunk = &text[prev_index..index];
28934 prev_index = index;
28935 Some(chunk)
28936 } else {
28937 None
28938 }
28939 })
28940}
28941
28942/// Given a string of text immediately before the cursor, iterates over possible
28943/// strings a snippet could match to. More precisely: returns an iterator over
28944/// suffixes of `text` created by splitting at word boundaries (before & after
28945/// every non-word character).
28946///
28947/// Shorter suffixes are returned first.
28948pub(crate) fn snippet_candidate_suffixes<'a>(
28949 text: &'a str,
28950 is_word_char: &'a dyn Fn(char) -> bool,
28951) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28952 let mut prev_index = text.len();
28953 let mut prev_codepoint = None;
28954 text.char_indices()
28955 .rev()
28956 .chain([(0, '\0')])
28957 .filter_map(move |(index, codepoint)| {
28958 let prev_index = std::mem::replace(&mut prev_index, index);
28959 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28960 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28961 None
28962 } else {
28963 let chunk = &text[prev_index..]; // go to end of string
28964 Some(chunk)
28965 }
28966 })
28967}
28968
28969pub trait RangeToAnchorExt: Sized {
28970 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28971
28972 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28973 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28974 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28975 }
28976}
28977
28978impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28979 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28980 let start_offset = self.start.to_offset(snapshot);
28981 let end_offset = self.end.to_offset(snapshot);
28982 if start_offset == end_offset {
28983 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28984 } else {
28985 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28986 }
28987 }
28988}
28989
28990pub trait RowExt {
28991 fn as_f64(&self) -> f64;
28992
28993 fn next_row(&self) -> Self;
28994
28995 fn previous_row(&self) -> Self;
28996
28997 fn minus(&self, other: Self) -> u32;
28998}
28999
29000impl RowExt for DisplayRow {
29001 fn as_f64(&self) -> f64 {
29002 self.0 as _
29003 }
29004
29005 fn next_row(&self) -> Self {
29006 Self(self.0 + 1)
29007 }
29008
29009 fn previous_row(&self) -> Self {
29010 Self(self.0.saturating_sub(1))
29011 }
29012
29013 fn minus(&self, other: Self) -> u32 {
29014 self.0 - other.0
29015 }
29016}
29017
29018impl RowExt for MultiBufferRow {
29019 fn as_f64(&self) -> f64 {
29020 self.0 as _
29021 }
29022
29023 fn next_row(&self) -> Self {
29024 Self(self.0 + 1)
29025 }
29026
29027 fn previous_row(&self) -> Self {
29028 Self(self.0.saturating_sub(1))
29029 }
29030
29031 fn minus(&self, other: Self) -> u32 {
29032 self.0 - other.0
29033 }
29034}
29035
29036trait RowRangeExt {
29037 type Row;
29038
29039 fn len(&self) -> usize;
29040
29041 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
29042}
29043
29044impl RowRangeExt for Range<MultiBufferRow> {
29045 type Row = MultiBufferRow;
29046
29047 fn len(&self) -> usize {
29048 (self.end.0 - self.start.0) as usize
29049 }
29050
29051 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
29052 (self.start.0..self.end.0).map(MultiBufferRow)
29053 }
29054}
29055
29056impl RowRangeExt for Range<DisplayRow> {
29057 type Row = DisplayRow;
29058
29059 fn len(&self) -> usize {
29060 (self.end.0 - self.start.0) as usize
29061 }
29062
29063 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
29064 (self.start.0..self.end.0).map(DisplayRow)
29065 }
29066}
29067
29068/// If select range has more than one line, we
29069/// just point the cursor to range.start.
29070fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
29071 if range.start.row == range.end.row {
29072 range
29073 } else {
29074 range.start..range.start
29075 }
29076}
29077pub struct KillRing(ClipboardItem);
29078impl Global for KillRing {}
29079
29080const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
29081
29082enum BreakpointPromptEditAction {
29083 Log,
29084 Condition,
29085 HitCondition,
29086}
29087
29088struct BreakpointPromptEditor {
29089 pub(crate) prompt: Entity<Editor>,
29090 editor: WeakEntity<Editor>,
29091 breakpoint_anchor: Anchor,
29092 breakpoint: Breakpoint,
29093 edit_action: BreakpointPromptEditAction,
29094 block_ids: HashSet<CustomBlockId>,
29095 editor_margins: Arc<Mutex<EditorMargins>>,
29096 _subscriptions: Vec<Subscription>,
29097}
29098
29099impl BreakpointPromptEditor {
29100 const MAX_LINES: u8 = 4;
29101
29102 fn new(
29103 editor: WeakEntity<Editor>,
29104 breakpoint_anchor: Anchor,
29105 breakpoint: Breakpoint,
29106 edit_action: BreakpointPromptEditAction,
29107 window: &mut Window,
29108 cx: &mut Context<Self>,
29109 ) -> Self {
29110 let base_text = match edit_action {
29111 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
29112 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
29113 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
29114 }
29115 .map(|msg| msg.to_string())
29116 .unwrap_or_default();
29117
29118 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
29119 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29120
29121 let prompt = cx.new(|cx| {
29122 let mut prompt = Editor::new(
29123 EditorMode::AutoHeight {
29124 min_lines: 1,
29125 max_lines: Some(Self::MAX_LINES as usize),
29126 },
29127 buffer,
29128 None,
29129 window,
29130 cx,
29131 );
29132 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
29133 prompt.set_show_cursor_when_unfocused(false, cx);
29134 prompt.set_placeholder_text(
29135 match edit_action {
29136 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
29137 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
29138 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
29139 },
29140 window,
29141 cx,
29142 );
29143
29144 prompt
29145 });
29146
29147 Self {
29148 prompt,
29149 editor,
29150 breakpoint_anchor,
29151 breakpoint,
29152 edit_action,
29153 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
29154 block_ids: Default::default(),
29155 _subscriptions: vec![],
29156 }
29157 }
29158
29159 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
29160 self.block_ids.extend(block_ids)
29161 }
29162
29163 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
29164 if let Some(editor) = self.editor.upgrade() {
29165 let message = self
29166 .prompt
29167 .read(cx)
29168 .buffer
29169 .read(cx)
29170 .as_singleton()
29171 .expect("A multi buffer in breakpoint prompt isn't possible")
29172 .read(cx)
29173 .as_rope()
29174 .to_string();
29175
29176 editor.update(cx, |editor, cx| {
29177 editor.edit_breakpoint_at_anchor(
29178 self.breakpoint_anchor,
29179 self.breakpoint.clone(),
29180 match self.edit_action {
29181 BreakpointPromptEditAction::Log => {
29182 BreakpointEditAction::EditLogMessage(message.into())
29183 }
29184 BreakpointPromptEditAction::Condition => {
29185 BreakpointEditAction::EditCondition(message.into())
29186 }
29187 BreakpointPromptEditAction::HitCondition => {
29188 BreakpointEditAction::EditHitCondition(message.into())
29189 }
29190 },
29191 cx,
29192 );
29193
29194 editor.remove_blocks(self.block_ids.clone(), None, cx);
29195 cx.focus_self(window);
29196 });
29197 }
29198 }
29199
29200 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29201 self.editor
29202 .update(cx, |editor, cx| {
29203 editor.remove_blocks(self.block_ids.clone(), None, cx);
29204 window.focus(&editor.focus_handle, cx);
29205 })
29206 .log_err();
29207 }
29208
29209 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29210 let settings = ThemeSettings::get_global(cx);
29211 let text_style = TextStyle {
29212 color: if self.prompt.read(cx).read_only(cx) {
29213 cx.theme().colors().text_disabled
29214 } else {
29215 cx.theme().colors().text
29216 },
29217 font_family: settings.buffer_font.family.clone(),
29218 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29219 font_size: settings.buffer_font_size(cx).into(),
29220 font_weight: settings.buffer_font.weight,
29221 line_height: relative(settings.buffer_line_height.value()),
29222 ..Default::default()
29223 };
29224 EditorElement::new(
29225 &self.prompt,
29226 EditorStyle {
29227 background: cx.theme().colors().editor_background,
29228 local_player: cx.theme().players().local(),
29229 text: text_style,
29230 ..Default::default()
29231 },
29232 )
29233 }
29234
29235 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29236 let focus_handle = self.prompt.focus_handle(cx);
29237 IconButton::new("cancel", IconName::Close)
29238 .icon_color(Color::Muted)
29239 .shape(IconButtonShape::Square)
29240 .tooltip(move |_window, cx| {
29241 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29242 })
29243 .on_click(cx.listener(|this, _, window, cx| {
29244 this.cancel(&menu::Cancel, window, cx);
29245 }))
29246 }
29247
29248 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29249 let focus_handle = self.prompt.focus_handle(cx);
29250 IconButton::new("confirm", IconName::Return)
29251 .icon_color(Color::Muted)
29252 .shape(IconButtonShape::Square)
29253 .tooltip(move |_window, cx| {
29254 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29255 })
29256 .on_click(cx.listener(|this, _, window, cx| {
29257 this.confirm(&menu::Confirm, window, cx);
29258 }))
29259 }
29260}
29261
29262impl Render for BreakpointPromptEditor {
29263 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29264 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29265 let editor_margins = *self.editor_margins.lock();
29266 let gutter_dimensions = editor_margins.gutter;
29267 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29268 let right_padding = editor_margins.right + px(9.);
29269 h_flex()
29270 .key_context("Editor")
29271 .bg(cx.theme().colors().editor_background)
29272 .border_y_1()
29273 .border_color(cx.theme().status().info_border)
29274 .size_full()
29275 .py(window.line_height() / 2.5)
29276 .pr(right_padding)
29277 .on_action(cx.listener(Self::confirm))
29278 .on_action(cx.listener(Self::cancel))
29279 .child(
29280 WithRemSize::new(ui_font_size)
29281 .h_full()
29282 .w(left_gutter_width)
29283 .flex()
29284 .flex_row()
29285 .flex_shrink_0()
29286 .items_center()
29287 .justify_center()
29288 .gap_1()
29289 .child(self.render_close_button(cx)),
29290 )
29291 .child(
29292 h_flex()
29293 .w_full()
29294 .justify_between()
29295 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29296 .child(
29297 WithRemSize::new(ui_font_size)
29298 .flex()
29299 .flex_row()
29300 .items_center()
29301 .child(self.render_confirm_button(cx)),
29302 ),
29303 )
29304 }
29305}
29306
29307impl Focusable for BreakpointPromptEditor {
29308 fn focus_handle(&self, cx: &App) -> FocusHandle {
29309 self.prompt.focus_handle(cx)
29310 }
29311}
29312
29313fn all_edits_insertions_or_deletions(
29314 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29315 snapshot: &MultiBufferSnapshot,
29316) -> bool {
29317 let mut all_insertions = true;
29318 let mut all_deletions = true;
29319
29320 for (range, new_text) in edits.iter() {
29321 let range_is_empty = range.to_offset(snapshot).is_empty();
29322 let text_is_empty = new_text.is_empty();
29323
29324 if range_is_empty != text_is_empty {
29325 if range_is_empty {
29326 all_deletions = false;
29327 } else {
29328 all_insertions = false;
29329 }
29330 } else {
29331 return false;
29332 }
29333
29334 if !all_insertions && !all_deletions {
29335 return false;
29336 }
29337 }
29338 all_insertions || all_deletions
29339}
29340
29341struct MissingEditPredictionKeybindingTooltip;
29342
29343impl Render for MissingEditPredictionKeybindingTooltip {
29344 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29345 ui::tooltip_container(cx, |container, cx| {
29346 container
29347 .flex_shrink_0()
29348 .max_w_80()
29349 .min_h(rems_from_px(124.))
29350 .justify_between()
29351 .child(
29352 v_flex()
29353 .flex_1()
29354 .text_ui_sm(cx)
29355 .child(Label::new("Conflict with Accept Keybinding"))
29356 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29357 )
29358 .child(
29359 h_flex()
29360 .pb_1()
29361 .gap_1()
29362 .items_end()
29363 .w_full()
29364 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29365 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29366 }))
29367 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29368 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29369 })),
29370 )
29371 })
29372 }
29373}
29374
29375#[derive(Debug, Clone, Copy, PartialEq)]
29376pub struct LineHighlight {
29377 pub background: Background,
29378 pub border: Option<gpui::Hsla>,
29379 pub include_gutter: bool,
29380 pub type_id: Option<TypeId>,
29381}
29382
29383struct LineManipulationResult {
29384 pub new_text: String,
29385 pub line_count_before: usize,
29386 pub line_count_after: usize,
29387}
29388
29389fn render_diff_hunk_controls(
29390 row: u32,
29391 status: &DiffHunkStatus,
29392 hunk_range: Range<Anchor>,
29393 is_created_file: bool,
29394 line_height: Pixels,
29395 editor: &Entity<Editor>,
29396 _window: &mut Window,
29397 cx: &mut App,
29398) -> AnyElement {
29399 h_flex()
29400 .h(line_height)
29401 .mr_1()
29402 .gap_1()
29403 .px_0p5()
29404 .pb_1()
29405 .border_x_1()
29406 .border_b_1()
29407 .border_color(cx.theme().colors().border_variant)
29408 .rounded_b_lg()
29409 .bg(cx.theme().colors().editor_background)
29410 .gap_1()
29411 .block_mouse_except_scroll()
29412 .shadow_md()
29413 .child(if status.has_secondary_hunk() {
29414 Button::new(("stage", row as u64), "Stage")
29415 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29416 .tooltip({
29417 let focus_handle = editor.focus_handle(cx);
29418 move |_window, cx| {
29419 Tooltip::for_action_in(
29420 "Stage Hunk",
29421 &::git::ToggleStaged,
29422 &focus_handle,
29423 cx,
29424 )
29425 }
29426 })
29427 .on_click({
29428 let editor = editor.clone();
29429 move |_event, _window, cx| {
29430 editor.update(cx, |editor, cx| {
29431 editor.stage_or_unstage_diff_hunks(
29432 true,
29433 vec![hunk_range.start..hunk_range.start],
29434 cx,
29435 );
29436 });
29437 }
29438 })
29439 } else {
29440 Button::new(("unstage", row as u64), "Unstage")
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 "Unstage 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 false,
29459 vec![hunk_range.start..hunk_range.start],
29460 cx,
29461 );
29462 });
29463 }
29464 })
29465 })
29466 .child(
29467 Button::new(("restore", row as u64), "Restore")
29468 .tooltip({
29469 let focus_handle = editor.focus_handle(cx);
29470 move |_window, cx| {
29471 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29472 }
29473 })
29474 .on_click({
29475 let editor = editor.clone();
29476 move |_event, window, cx| {
29477 editor.update(cx, |editor, cx| {
29478 let snapshot = editor.snapshot(window, cx);
29479 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29480 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29481 });
29482 }
29483 })
29484 .disabled(is_created_file),
29485 )
29486 .when(
29487 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29488 |el| {
29489 el.child(
29490 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29491 .shape(IconButtonShape::Square)
29492 .icon_size(IconSize::Small)
29493 // .disabled(!has_multiple_hunks)
29494 .tooltip({
29495 let focus_handle = editor.focus_handle(cx);
29496 move |_window, cx| {
29497 Tooltip::for_action_in("Next Hunk", &GoToHunk, &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 position =
29506 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29507 editor.go_to_hunk_before_or_after_position(
29508 &snapshot,
29509 position,
29510 Direction::Next,
29511 true,
29512 window,
29513 cx,
29514 );
29515 editor.expand_selected_diff_hunks(cx);
29516 });
29517 }
29518 }),
29519 )
29520 .child(
29521 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29522 .shape(IconButtonShape::Square)
29523 .icon_size(IconSize::Small)
29524 // .disabled(!has_multiple_hunks)
29525 .tooltip({
29526 let focus_handle = editor.focus_handle(cx);
29527 move |_window, cx| {
29528 Tooltip::for_action_in(
29529 "Previous Hunk",
29530 &GoToPreviousHunk,
29531 &focus_handle,
29532 cx,
29533 )
29534 }
29535 })
29536 .on_click({
29537 let editor = editor.clone();
29538 move |_event, window, cx| {
29539 editor.update(cx, |editor, cx| {
29540 let snapshot = editor.snapshot(window, cx);
29541 let point =
29542 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29543 editor.go_to_hunk_before_or_after_position(
29544 &snapshot,
29545 point,
29546 Direction::Prev,
29547 true,
29548 window,
29549 cx,
29550 );
29551 editor.expand_selected_diff_hunks(cx);
29552 });
29553 }
29554 }),
29555 )
29556 },
29557 )
29558 .into_any_element()
29559}
29560
29561pub fn multibuffer_context_lines(cx: &App) -> u32 {
29562 EditorSettings::try_get(cx)
29563 .map(|settings| settings.excerpt_context_lines)
29564 .unwrap_or(2)
29565 .min(32)
29566}