1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod document_colors;
21mod document_symbols;
22mod editor_settings;
23mod element;
24mod folding_ranges;
25mod git;
26mod highlight_matching_bracket;
27mod hover_links;
28pub mod hover_popover;
29mod indent_guides;
30mod inlays;
31pub mod items;
32mod jsx_tag_auto_close;
33mod linked_editing_ranges;
34mod lsp_ext;
35mod mouse_context_menu;
36pub mod movement;
37mod persistence;
38mod runnables;
39mod rust_analyzer_ext;
40pub mod scroll;
41mod selections_collection;
42pub mod semantic_tokens;
43mod split;
44pub mod split_editor_view;
45
46#[cfg(test)]
47mod code_completion_tests;
48#[cfg(test)]
49mod edit_prediction_tests;
50#[cfg(test)]
51mod editor_tests;
52mod signature_help;
53#[cfg(any(test, feature = "test-support"))]
54pub mod test;
55
56pub(crate) use actions::*;
57pub use display_map::{
58 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
59 SemanticTokenHighlight,
60};
61pub use edit_prediction_types::Direction;
62pub use editor_settings::{
63 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
64 EditorSettings, EditorSettingsScrollbarProxy, HideMouseMode, ScrollBeyondLastLine,
65 ScrollbarAxes, SearchSettings, ShowMinimap, ui_scrollbar_settings_from_raw,
66};
67pub use element::{
68 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
69 render_breadcrumb_text,
70};
71pub use git::blame::BlameRenderer;
72pub use hover_popover::hover_markdown_style;
73pub use inlays::Inlay;
74pub use items::MAX_TAB_TITLE_LEN;
75pub use linked_editing_ranges::LinkedEdits;
76pub use lsp::CompletionContext;
77pub use lsp_ext::lsp_tasks;
78pub use multi_buffer::{
79 Anchor, AnchorRangeExt, BufferOffset, 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 ExcerptBoundaryInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint,
154 MultiBufferRow,
155};
156use parking_lot::Mutex;
157use persistence::EditorDb;
158use project::{
159 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
160 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
161 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
162 ProjectItem, ProjectPath, ProjectTransaction,
163 debugger::{
164 breakpoint_store::{
165 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
166 BreakpointStore, BreakpointStoreEvent,
167 },
168 session::{Session, SessionEvent},
169 },
170 git_store::GitStoreEvent,
171 lsp_store::{
172 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
173 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
174 },
175 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
176};
177use rand::seq::SliceRandom;
178use regex::Regex;
179use rpc::{ErrorCode, ErrorExt, proto::PeerId};
180use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
181use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
182use serde::{Deserialize, Serialize};
183use settings::{
184 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
185 update_settings_file,
186};
187use smallvec::{SmallVec, smallvec};
188use snippet::Snippet;
189use std::{
190 any::{Any, TypeId},
191 borrow::Cow,
192 cell::{OnceCell, RefCell},
193 cmp::{self, Ordering, Reverse},
194 collections::hash_map,
195 iter::{self, Peekable},
196 mem,
197 num::NonZeroU32,
198 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
199 path::{Path, PathBuf},
200 rc::Rc,
201 sync::Arc,
202 time::{Duration, Instant},
203};
204use task::TaskVariables;
205use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
206use theme::{
207 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
208};
209use theme_settings::{ThemeSettings, observe_buffer_font_size_adjustment};
210use ui::{
211 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
212 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
213 utils::WithRemSize,
214};
215use ui_input::ErasedEditor;
216use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
217use workspace::{
218 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
219 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
220 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
221 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
222 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
223 searchable::SearchEvent,
224};
225pub use zed_actions::editor::RevealInFileManager;
226use zed_actions::editor::{MoveDown, MoveUp};
227
228use crate::{
229 code_context_menus::CompletionsMenuSource,
230 editor_settings::MultiCursorModifier,
231 hover_links::{find_url, find_url_from_range},
232 inlays::{
233 InlineValueCache,
234 inlay_hints::{LspInlayHintData, inlay_hint_settings},
235 },
236 runnables::{ResolvedTasks, RunnableData, RunnableTasks},
237 scroll::{ScrollOffset, ScrollPixelOffset},
238 selections_collection::resolve_selections_wrapping_blocks,
239 semantic_tokens::SemanticTokenState,
240 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
241};
242
243pub const FILE_HEADER_HEIGHT: u32 = 2;
244pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
245pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
246const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
247const MAX_LINE_LEN: usize = 1024;
248const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
249const MAX_SELECTION_HISTORY_LEN: usize = 1024;
250pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
251#[doc(hidden)]
252pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
253pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
254
255pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
256pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
257pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
258pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
259
260pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
261pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
262
263pub type RenderDiffHunkControlsFn = Arc<
264 dyn Fn(
265 u32,
266 &DiffHunkStatus,
267 Range<Anchor>,
268 bool,
269 Pixels,
270 &Entity<Editor>,
271 &mut Window,
272 &mut App,
273 ) -> AnyElement,
274>;
275
276enum ReportEditorEvent {
277 Saved { auto_saved: bool },
278 EditorOpened,
279 Closed,
280}
281
282impl ReportEditorEvent {
283 pub fn event_type(&self) -> &'static str {
284 match self {
285 Self::Saved { .. } => "Editor Saved",
286 Self::EditorOpened => "Editor Opened",
287 Self::Closed => "Editor Closed",
288 }
289 }
290}
291
292pub enum ActiveDebugLine {}
293pub enum DebugStackFrameLine {}
294
295pub enum ConflictsOuter {}
296pub enum ConflictsOurs {}
297pub enum ConflictsTheirs {}
298pub enum ConflictsOursMarker {}
299pub enum ConflictsTheirsMarker {}
300
301pub struct HunkAddedColor;
302pub struct HunkRemovedColor;
303
304#[derive(Debug, Copy, Clone, PartialEq, Eq)]
305pub enum Navigated {
306 Yes,
307 No,
308}
309
310impl Navigated {
311 pub fn from_bool(yes: bool) -> Navigated {
312 if yes { Navigated::Yes } else { Navigated::No }
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, Eq)]
317enum DisplayDiffHunk {
318 Folded {
319 display_row: DisplayRow,
320 },
321 Unfolded {
322 is_created_file: bool,
323 diff_base_byte_range: Range<usize>,
324 display_row_range: Range<DisplayRow>,
325 multi_buffer_range: Range<Anchor>,
326 status: DiffHunkStatus,
327 word_diffs: Vec<Range<MultiBufferOffset>>,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init(cx: &mut App) {
337 cx.set_global(GlobalBlameRenderer(Arc::new(())));
338 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
339
340 workspace::register_project_item::<Editor>(cx);
341 workspace::FollowableViewRegistry::register::<Editor>(cx);
342 workspace::register_serializable_item::<Editor>(cx);
343
344 cx.observe_new(
345 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
346 workspace.register_action(Editor::new_file);
347 workspace.register_action(Editor::new_file_split);
348 workspace.register_action(Editor::new_file_vertical);
349 workspace.register_action(Editor::new_file_horizontal);
350 workspace.register_action(Editor::cancel_language_server_work);
351 workspace.register_action(Editor::toggle_focus);
352 },
353 )
354 .detach();
355
356 cx.on_action(move |_: &workspace::NewFile, cx| {
357 let app_state = workspace::AppState::global(cx);
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| Editor::new_file(workspace, &Default::default(), window, cx),
363 )
364 .detach_and_log_err(cx);
365 })
366 .on_action(move |_: &workspace::NewWindow, cx| {
367 let app_state = workspace::AppState::global(cx);
368 workspace::open_new(
369 Default::default(),
370 app_state,
371 cx,
372 |workspace, window, cx| {
373 cx.activate(true);
374 Editor::new_file(workspace, &Default::default(), window, cx)
375 },
376 )
377 .detach_and_log_err(cx);
378 });
379 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
380 Arc::new(ErasedEditorImpl(
381 cx.new(|cx| Editor::single_line(window, cx)),
382 )) as Arc<dyn ErasedEditor>
383 });
384 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
385}
386
387pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
388 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
389}
390
391pub trait DiagnosticRenderer {
392 fn render_group(
393 &self,
394 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
395 buffer_id: BufferId,
396 snapshot: EditorSnapshot,
397 editor: WeakEntity<Editor>,
398 language_registry: Option<Arc<LanguageRegistry>>,
399 cx: &mut App,
400 ) -> Vec<BlockProperties<Anchor>>;
401
402 fn render_hover(
403 &self,
404 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
405 range: Range<Point>,
406 buffer_id: BufferId,
407 language_registry: Option<Arc<LanguageRegistry>>,
408 cx: &mut App,
409 ) -> Option<Entity<markdown::Markdown>>;
410
411 fn open_link(
412 &self,
413 editor: &mut Editor,
414 link: SharedString,
415 window: &mut Window,
416 cx: &mut Context<Editor>,
417 );
418}
419
420pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
421
422impl GlobalDiagnosticRenderer {
423 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
424 cx.try_global::<Self>().map(|g| g.0.clone())
425 }
426}
427
428impl gpui::Global for GlobalDiagnosticRenderer {}
429pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
430 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
431}
432
433pub struct SearchWithinRange;
434
435trait InvalidationRegion {
436 fn ranges(&self) -> &[Range<Anchor>];
437}
438
439#[derive(Clone, Debug, PartialEq)]
440pub enum SelectPhase {
441 Begin {
442 position: DisplayPoint,
443 add: bool,
444 click_count: usize,
445 },
446 BeginColumnar {
447 position: DisplayPoint,
448 reset: bool,
449 mode: ColumnarMode,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug, PartialEq)]
465pub enum ColumnarMode {
466 FromMouse,
467 FromSelection,
468}
469
470#[derive(Clone, Debug)]
471pub enum SelectMode {
472 Character,
473 Word(Range<Anchor>),
474 Line(Range<Anchor>),
475 All,
476}
477
478#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
479pub enum SizingBehavior {
480 /// The editor will layout itself using `size_full` and will include the vertical
481 /// scroll margin as requested by user settings.
482 #[default]
483 Default,
484 /// The editor will layout itself using `size_full`, but will not have any
485 /// vertical overscroll.
486 ExcludeOverscrollMargin,
487 /// The editor will request a vertical size according to its content and will be
488 /// layouted without a vertical scroll margin.
489 SizeByContent,
490}
491
492#[derive(Clone, PartialEq, Eq, Debug)]
493pub enum EditorMode {
494 SingleLine,
495 AutoHeight {
496 min_lines: usize,
497 max_lines: Option<usize>,
498 },
499 Full {
500 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
501 scale_ui_elements_with_buffer_font_size: bool,
502 /// When set to `true`, the editor will render a background for the active line.
503 show_active_line_background: bool,
504 /// Determines the sizing behavior for this editor
505 sizing_behavior: SizingBehavior,
506 },
507 Minimap {
508 parent: WeakEntity<Editor>,
509 },
510}
511
512impl EditorMode {
513 pub fn full() -> Self {
514 Self::Full {
515 scale_ui_elements_with_buffer_font_size: true,
516 show_active_line_background: true,
517 sizing_behavior: SizingBehavior::Default,
518 }
519 }
520
521 #[inline]
522 pub fn is_full(&self) -> bool {
523 matches!(self, Self::Full { .. })
524 }
525
526 #[inline]
527 pub fn is_single_line(&self) -> bool {
528 matches!(self, Self::SingleLine { .. })
529 }
530
531 #[inline]
532 fn is_minimap(&self) -> bool {
533 matches!(self, Self::Minimap { .. })
534 }
535}
536
537#[derive(Copy, Clone, Debug)]
538pub enum SoftWrap {
539 /// Prefer not to wrap at all.
540 ///
541 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
542 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
543 GitDiff,
544 /// Prefer a single line generally, unless an overly long line is encountered.
545 None,
546 /// Soft wrap lines that exceed the editor width.
547 EditorWidth,
548 /// Soft wrap lines at the preferred line length.
549 Column(u32),
550 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
551 Bounded(u32),
552}
553
554#[derive(Clone)]
555pub struct EditorStyle {
556 pub background: Hsla,
557 pub border: Hsla,
558 pub local_player: PlayerColor,
559 pub text: TextStyle,
560 pub scrollbar_width: Pixels,
561 pub syntax: Arc<SyntaxTheme>,
562 pub status: StatusColors,
563 pub inlay_hints_style: HighlightStyle,
564 pub edit_prediction_styles: EditPredictionStyles,
565 pub unnecessary_code_fade: f32,
566 pub show_underlines: bool,
567}
568
569impl Default for EditorStyle {
570 fn default() -> Self {
571 Self {
572 background: Hsla::default(),
573 border: Hsla::default(),
574 local_player: PlayerColor::default(),
575 text: TextStyle::default(),
576 scrollbar_width: Pixels::default(),
577 syntax: Default::default(),
578 // HACK: Status colors don't have a real default.
579 // We should look into removing the status colors from the editor
580 // style and retrieve them directly from the theme.
581 status: StatusColors::dark(),
582 inlay_hints_style: HighlightStyle::default(),
583 edit_prediction_styles: EditPredictionStyles {
584 insertion: HighlightStyle::default(),
585 whitespace: HighlightStyle::default(),
586 },
587 unnecessary_code_fade: Default::default(),
588 show_underlines: true,
589 }
590 }
591}
592
593pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
594 let show_background = AllLanguageSettings::get_global(cx)
595 .defaults
596 .inlay_hints
597 .show_background;
598
599 let mut style = cx
600 .theme()
601 .syntax()
602 .style_for_name("hint")
603 .unwrap_or_default();
604
605 if style.color.is_none() {
606 style.color = Some(cx.theme().status().hint);
607 }
608
609 if !show_background {
610 style.background_color = None;
611 return style;
612 }
613
614 if style.background_color.is_none() {
615 style.background_color = Some(cx.theme().status().hint_background);
616 }
617
618 style
619}
620
621pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
622 EditPredictionStyles {
623 insertion: HighlightStyle {
624 color: Some(cx.theme().status().predictive),
625 ..HighlightStyle::default()
626 },
627 whitespace: HighlightStyle {
628 background_color: Some(cx.theme().status().created_background),
629 ..HighlightStyle::default()
630 },
631 }
632}
633
634type CompletionId = usize;
635
636pub(crate) enum EditDisplayMode {
637 TabAccept,
638 DiffPopover,
639 Inline,
640}
641
642enum EditPrediction {
643 Edit {
644 // TODO could be a language::Anchor?
645 edits: Vec<(Range<Anchor>, Arc<str>)>,
646 /// Predicted cursor position as (anchor, offset_from_anchor).
647 /// The anchor is in multibuffer coordinates; after applying edits,
648 /// resolve the anchor and add the offset to get the final cursor position.
649 cursor_position: Option<(Anchor, usize)>,
650 edit_preview: Option<EditPreview>,
651 display_mode: EditDisplayMode,
652 snapshot: BufferSnapshot,
653 },
654 /// Move to a specific location in the active editor
655 MoveWithin {
656 target: Anchor,
657 snapshot: BufferSnapshot,
658 },
659 /// Move to a specific location in a different editor (not the active one)
660 MoveOutside {
661 target: language::Anchor,
662 snapshot: BufferSnapshot,
663 },
664}
665
666struct EditPredictionState {
667 inlay_ids: Vec<InlayId>,
668 completion: EditPrediction,
669 completion_id: Option<SharedString>,
670 invalidation_range: Option<Range<Anchor>>,
671}
672
673enum EditPredictionSettings {
674 Disabled,
675 Enabled {
676 show_in_menu: bool,
677 preview_requires_modifier: bool,
678 },
679}
680
681#[derive(Debug, Clone)]
682struct InlineDiagnostic {
683 message: SharedString,
684 group_id: usize,
685 is_primary: bool,
686 start: Point,
687 severity: lsp::DiagnosticSeverity,
688}
689
690pub enum MenuEditPredictionsPolicy {
691 Never,
692 ByProvider,
693}
694
695pub enum EditPredictionPreview {
696 /// Modifier is not pressed
697 Inactive { released_too_fast: bool },
698 /// Modifier pressed
699 Active {
700 since: Instant,
701 previous_scroll_position: Option<SharedScrollAnchor>,
702 },
703}
704
705#[derive(Copy, Clone, Eq, PartialEq)]
706enum EditPredictionKeybindSurface {
707 Inline,
708 CursorPopoverCompact,
709 CursorPopoverExpanded,
710}
711
712#[derive(Copy, Clone, Eq, PartialEq, Debug)]
713enum EditPredictionKeybindAction {
714 Accept,
715 Preview,
716}
717
718struct EditPredictionKeybindDisplay {
719 #[cfg(test)]
720 accept_keystroke: Option<gpui::KeybindingKeystroke>,
721 #[cfg(test)]
722 preview_keystroke: Option<gpui::KeybindingKeystroke>,
723 displayed_keystroke: Option<gpui::KeybindingKeystroke>,
724 action: EditPredictionKeybindAction,
725 missing_accept_keystroke: bool,
726 show_hold_label: bool,
727}
728
729impl EditPredictionPreview {
730 pub fn released_too_fast(&self) -> bool {
731 match self {
732 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
733 EditPredictionPreview::Active { .. } => false,
734 }
735 }
736
737 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
738 if let EditPredictionPreview::Active {
739 previous_scroll_position,
740 ..
741 } = self
742 {
743 *previous_scroll_position = scroll_position;
744 }
745 }
746}
747
748pub struct ContextMenuOptions {
749 pub min_entries_visible: usize,
750 pub max_entries_visible: usize,
751 pub placement: Option<ContextMenuPlacement>,
752}
753
754#[derive(Debug, Clone, PartialEq, Eq)]
755pub enum ContextMenuPlacement {
756 Above,
757 Below,
758}
759
760#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
761struct EditorActionId(usize);
762
763impl EditorActionId {
764 pub fn post_inc(&mut self) -> Self {
765 let answer = self.0;
766
767 *self = Self(answer + 1);
768
769 Self(answer)
770 }
771}
772
773// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
774// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
775
776type BackgroundHighlight = (
777 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
778 Arc<[Range<Anchor>]>,
779);
780type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
781
782#[derive(Default)]
783struct ScrollbarMarkerState {
784 scrollbar_size: Size<Pixels>,
785 dirty: bool,
786 markers: Arc<[PaintQuad]>,
787 pending_refresh: Option<Task<Result<()>>>,
788}
789
790impl ScrollbarMarkerState {
791 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
792 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
793 }
794}
795
796#[derive(Clone, Copy, PartialEq, Eq)]
797pub enum MinimapVisibility {
798 Disabled,
799 Enabled {
800 /// The configuration currently present in the users settings.
801 setting_configuration: bool,
802 /// Whether to override the currently set visibility from the users setting.
803 toggle_override: bool,
804 },
805}
806
807impl MinimapVisibility {
808 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
809 if mode.is_full() {
810 Self::Enabled {
811 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
812 toggle_override: false,
813 }
814 } else {
815 Self::Disabled
816 }
817 }
818
819 fn hidden(&self) -> Self {
820 match *self {
821 Self::Enabled {
822 setting_configuration,
823 ..
824 } => Self::Enabled {
825 setting_configuration,
826 toggle_override: setting_configuration,
827 },
828 Self::Disabled => Self::Disabled,
829 }
830 }
831
832 fn disabled(&self) -> bool {
833 matches!(*self, Self::Disabled)
834 }
835
836 fn settings_visibility(&self) -> bool {
837 match *self {
838 Self::Enabled {
839 setting_configuration,
840 ..
841 } => setting_configuration,
842 _ => false,
843 }
844 }
845
846 fn visible(&self) -> bool {
847 match *self {
848 Self::Enabled {
849 setting_configuration,
850 toggle_override,
851 } => setting_configuration ^ toggle_override,
852 _ => false,
853 }
854 }
855
856 fn toggle_visibility(&self) -> Self {
857 match *self {
858 Self::Enabled {
859 toggle_override,
860 setting_configuration,
861 } => Self::Enabled {
862 setting_configuration,
863 toggle_override: !toggle_override,
864 },
865 Self::Disabled => Self::Disabled,
866 }
867 }
868}
869
870#[derive(Debug, Clone, Copy, PartialEq, Eq)]
871pub enum BufferSerialization {
872 All,
873 NonDirtyBuffers,
874}
875
876impl BufferSerialization {
877 fn new(restore_unsaved_buffers: bool) -> Self {
878 if restore_unsaved_buffers {
879 Self::All
880 } else {
881 Self::NonDirtyBuffers
882 }
883 }
884}
885
886/// Addons allow storing per-editor state in other crates (e.g. Vim)
887pub trait Addon: 'static {
888 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
889
890 fn render_buffer_header_controls(
891 &self,
892 _: &ExcerptBoundaryInfo,
893 _: &language::BufferSnapshot,
894 _: &Window,
895 _: &App,
896 ) -> Option<AnyElement> {
897 None
898 }
899
900 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
901 None
902 }
903
904 fn to_any(&self) -> &dyn std::any::Any;
905
906 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
907 None
908 }
909}
910
911struct ChangeLocation {
912 current: Option<Vec<Anchor>>,
913 original: Vec<Anchor>,
914}
915impl ChangeLocation {
916 fn locations(&self) -> &[Anchor] {
917 self.current.as_ref().unwrap_or(&self.original)
918 }
919}
920
921/// A set of caret positions, registered when the editor was edited.
922pub struct ChangeList {
923 changes: Vec<ChangeLocation>,
924 /// Currently "selected" change.
925 position: Option<usize>,
926}
927
928impl ChangeList {
929 pub fn new() -> Self {
930 Self {
931 changes: Vec::new(),
932 position: None,
933 }
934 }
935
936 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
937 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
938 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
939 if self.changes.is_empty() {
940 return None;
941 }
942
943 let prev = self.position.unwrap_or(self.changes.len());
944 let next = if direction == Direction::Prev {
945 prev.saturating_sub(count)
946 } else {
947 (prev + count).min(self.changes.len() - 1)
948 };
949 self.position = Some(next);
950 self.changes.get(next).map(|change| change.locations())
951 }
952
953 /// Adds a new change to the list, resetting the change list position.
954 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
955 self.position.take();
956 if let Some(last) = self.changes.last_mut()
957 && group
958 {
959 last.current = Some(new_positions)
960 } else {
961 self.changes.push(ChangeLocation {
962 original: new_positions,
963 current: None,
964 });
965 }
966 }
967
968 pub fn last(&self) -> Option<&[Anchor]> {
969 self.changes.last().map(|change| change.locations())
970 }
971
972 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
973 self.changes.last().map(|change| change.original.as_slice())
974 }
975
976 pub fn invert_last_group(&mut self) {
977 if let Some(last) = self.changes.last_mut()
978 && let Some(current) = last.current.as_mut()
979 {
980 mem::swap(&mut last.original, current);
981 }
982 }
983}
984
985#[derive(Clone)]
986struct InlineBlamePopoverState {
987 scroll_handle: ScrollHandle,
988 commit_message: Option<ParsedCommitMessage>,
989 markdown: Entity<Markdown>,
990}
991
992struct InlineBlamePopover {
993 position: gpui::Point<Pixels>,
994 hide_task: Option<Task<()>>,
995 popover_bounds: Option<Bounds<Pixels>>,
996 popover_state: InlineBlamePopoverState,
997 keyboard_grace: bool,
998}
999
1000enum SelectionDragState {
1001 /// State when no drag related activity is detected.
1002 None,
1003 /// State when the mouse is down on a selection that is about to be dragged.
1004 ReadyToDrag {
1005 selection: Selection<Anchor>,
1006 click_position: gpui::Point<Pixels>,
1007 mouse_down_time: Instant,
1008 },
1009 /// State when the mouse is dragging the selection in the editor.
1010 Dragging {
1011 selection: Selection<Anchor>,
1012 drop_cursor: Selection<Anchor>,
1013 hide_drop_cursor: bool,
1014 },
1015}
1016
1017enum ColumnarSelectionState {
1018 FromMouse {
1019 selection_tail: Anchor,
1020 display_point: Option<DisplayPoint>,
1021 },
1022 FromSelection {
1023 selection_tail: Anchor,
1024 },
1025}
1026
1027/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1028/// a breakpoint on them.
1029#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1030struct PhantomBreakpointIndicator {
1031 display_row: DisplayRow,
1032 /// There's a small debounce between hovering over the line and showing the indicator.
1033 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1034 is_active: bool,
1035 collides_with_existing_breakpoint: bool,
1036}
1037
1038/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1039/// in diff view mode.
1040#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1041pub(crate) struct PhantomDiffReviewIndicator {
1042 /// The starting anchor of the selection (or the only row if not dragging).
1043 pub start: Anchor,
1044 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1045 pub end: Anchor,
1046 /// There's a small debounce between hovering over the line and showing the indicator.
1047 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1048 pub is_active: bool,
1049}
1050
1051#[derive(Clone, Debug)]
1052pub(crate) struct DiffReviewDragState {
1053 pub start_anchor: Anchor,
1054 pub current_anchor: Anchor,
1055}
1056
1057impl DiffReviewDragState {
1058 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1059 let start = self.start_anchor.to_display_point(snapshot).row();
1060 let current = self.current_anchor.to_display_point(snapshot).row();
1061
1062 (start..=current).sorted()
1063 }
1064}
1065
1066/// Identifies a specific hunk in the diff buffer.
1067/// Used as a key to group comments by their location.
1068#[derive(Clone, Debug)]
1069pub struct DiffHunkKey {
1070 /// The file path (relative to worktree) this hunk belongs to.
1071 pub file_path: Arc<util::rel_path::RelPath>,
1072 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1073 pub hunk_start_anchor: Anchor,
1074}
1075
1076/// A review comment stored locally before being sent to the Agent panel.
1077#[derive(Clone)]
1078pub struct StoredReviewComment {
1079 /// Unique identifier for this comment (for edit/delete operations).
1080 pub id: usize,
1081 /// The comment text entered by the user.
1082 pub comment: String,
1083 /// Anchors for the code range being reviewed.
1084 pub range: Range<Anchor>,
1085 /// Timestamp when the comment was created (for chronological ordering).
1086 pub created_at: Instant,
1087 /// Whether this comment is currently being edited inline.
1088 pub is_editing: bool,
1089}
1090
1091impl StoredReviewComment {
1092 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1093 Self {
1094 id,
1095 comment,
1096 range: anchor_range,
1097 created_at: Instant::now(),
1098 is_editing: false,
1099 }
1100 }
1101}
1102
1103/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1104pub(crate) struct DiffReviewOverlay {
1105 pub anchor_range: Range<Anchor>,
1106 /// The block ID for the overlay.
1107 pub block_id: CustomBlockId,
1108 /// The editor entity for the review input.
1109 pub prompt_editor: Entity<Editor>,
1110 /// The hunk key this overlay belongs to.
1111 pub hunk_key: DiffHunkKey,
1112 /// Whether the comments section is expanded.
1113 pub comments_expanded: bool,
1114 /// Editors for comments currently being edited inline.
1115 /// Key: comment ID, Value: Editor entity for inline editing.
1116 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1117 /// Subscriptions for inline edit editors' action handlers.
1118 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1119 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1120 /// The current user's avatar URI for display in comment rows.
1121 pub user_avatar_uri: Option<SharedUri>,
1122 /// Subscription to keep the action handler alive.
1123 _subscription: Subscription,
1124}
1125
1126/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1127///
1128/// See the [module level documentation](self) for more information.
1129pub struct Editor {
1130 focus_handle: FocusHandle,
1131 last_focused_descendant: Option<WeakFocusHandle>,
1132 /// The text buffer being edited
1133 buffer: Entity<MultiBuffer>,
1134 /// Map of how text in the buffer should be displayed.
1135 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1136 pub display_map: Entity<DisplayMap>,
1137 placeholder_display_map: Option<Entity<DisplayMap>>,
1138 pub selections: SelectionsCollection,
1139 pub scroll_manager: ScrollManager,
1140 /// When inline assist editors are linked, they all render cursors because
1141 /// typing enters text into each of them, even the ones that aren't focused.
1142 pub(crate) show_cursor_when_unfocused: bool,
1143 columnar_selection_state: Option<ColumnarSelectionState>,
1144 add_selections_state: Option<AddSelectionsState>,
1145 select_next_state: Option<SelectNextState>,
1146 select_prev_state: Option<SelectNextState>,
1147 selection_history: SelectionHistory,
1148 defer_selection_effects: bool,
1149 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1150 autoclose_regions: Vec<AutocloseRegion>,
1151 snippet_stack: InvalidationStack<SnippetState>,
1152 select_syntax_node_history: SelectSyntaxNodeHistory,
1153 ime_transaction: Option<TransactionId>,
1154 pub diagnostics_max_severity: DiagnosticSeverity,
1155 active_diagnostics: ActiveDiagnostic,
1156 show_inline_diagnostics: bool,
1157 inline_diagnostics_update: Task<()>,
1158 inline_diagnostics_enabled: bool,
1159 diagnostics_enabled: bool,
1160 word_completions_enabled: bool,
1161 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1162 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1163 hard_wrap: Option<usize>,
1164 project: Option<Entity<Project>>,
1165 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1166 completion_provider: Option<Rc<dyn CompletionProvider>>,
1167 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1168 blink_manager: Entity<BlinkManager>,
1169 show_cursor_names: bool,
1170 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1171 pub show_local_selections: bool,
1172 mode: EditorMode,
1173 show_breadcrumbs: bool,
1174 show_gutter: bool,
1175 show_scrollbars: ScrollbarAxes,
1176 minimap_visibility: MinimapVisibility,
1177 offset_content: bool,
1178 disable_expand_excerpt_buttons: bool,
1179 delegate_expand_excerpts: bool,
1180 delegate_stage_and_restore: bool,
1181 delegate_open_excerpts: bool,
1182 enable_lsp_data: bool,
1183 enable_runnables: bool,
1184 show_line_numbers: Option<bool>,
1185 use_relative_line_numbers: Option<bool>,
1186 show_git_diff_gutter: Option<bool>,
1187 show_code_actions: Option<bool>,
1188 show_runnables: Option<bool>,
1189 show_breakpoints: Option<bool>,
1190 show_diff_review_button: bool,
1191 show_wrap_guides: Option<bool>,
1192 show_indent_guides: Option<bool>,
1193 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1194 highlight_order: usize,
1195 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1196 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1197 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1198 scrollbar_marker_state: ScrollbarMarkerState,
1199 active_indent_guides_state: ActiveIndentGuidesState,
1200 nav_history: Option<ItemNavHistory>,
1201 context_menu: RefCell<Option<CodeContextMenu>>,
1202 context_menu_options: Option<ContextMenuOptions>,
1203 mouse_context_menu: Option<MouseContextMenu>,
1204 completion_tasks: Vec<(CompletionId, Task<()>)>,
1205 inline_blame_popover: Option<InlineBlamePopover>,
1206 inline_blame_popover_show_task: Option<Task<()>>,
1207 signature_help_state: SignatureHelpState,
1208 auto_signature_help: Option<bool>,
1209 find_all_references_task_sources: Vec<Anchor>,
1210 next_completion_id: CompletionId,
1211 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1212 code_actions_task: Option<Task<Result<()>>>,
1213 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1214 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1215 debounced_selection_highlight_complete: bool,
1216 document_highlights_task: Option<Task<()>>,
1217 linked_editing_range_task: Option<Task<Option<()>>>,
1218 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1219 pending_rename: Option<RenameState>,
1220 searchable: bool,
1221 cursor_shape: CursorShape,
1222 /// Whether the cursor is offset one character to the left when something is
1223 /// selected (needed for vim visual mode)
1224 cursor_offset_on_selection: bool,
1225 current_line_highlight: Option<CurrentLineHighlight>,
1226 /// Whether to collapse search match ranges to just their start position.
1227 /// When true, navigating to a match positions the cursor at the match
1228 /// without selecting the matched text.
1229 collapse_matches: bool,
1230 autoindent_mode: Option<AutoindentMode>,
1231 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1232 input_enabled: bool,
1233 expects_character_input: bool,
1234 use_modal_editing: bool,
1235 read_only: bool,
1236 leader_id: Option<CollaboratorId>,
1237 remote_id: Option<ViewId>,
1238 pub hover_state: HoverState,
1239 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1240 prev_pressure_stage: Option<PressureStage>,
1241 gutter_hovered: bool,
1242 hovered_link_state: Option<HoveredLinkState>,
1243 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1244 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1245 active_edit_prediction: Option<EditPredictionState>,
1246 /// Used to prevent flickering as the user types while the menu is open
1247 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1248 edit_prediction_settings: EditPredictionSettings,
1249 edit_predictions_hidden_for_vim_mode: bool,
1250 show_edit_predictions_override: Option<bool>,
1251 show_completions_on_input_override: Option<bool>,
1252 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1253 edit_prediction_preview: EditPredictionPreview,
1254 in_leading_whitespace: bool,
1255 next_inlay_id: usize,
1256 next_color_inlay_id: usize,
1257 _subscriptions: Vec<Subscription>,
1258 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1259 gutter_dimensions: GutterDimensions,
1260 style: Option<EditorStyle>,
1261 text_style_refinement: Option<TextStyleRefinement>,
1262 next_editor_action_id: EditorActionId,
1263 editor_actions: Rc<
1264 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1265 >,
1266 use_autoclose: bool,
1267 use_auto_surround: bool,
1268 auto_replace_emoji_shortcode: bool,
1269 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1270 show_git_blame_gutter: bool,
1271 show_git_blame_inline: bool,
1272 show_git_blame_inline_delay_task: Option<Task<()>>,
1273 git_blame_inline_enabled: bool,
1274 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1275 buffer_serialization: Option<BufferSerialization>,
1276 show_selection_menu: Option<bool>,
1277 blame: Option<Entity<GitBlame>>,
1278 blame_subscription: Option<Subscription>,
1279 custom_context_menu: Option<
1280 Box<
1281 dyn 'static
1282 + Fn(
1283 &mut Self,
1284 DisplayPoint,
1285 &mut Window,
1286 &mut Context<Self>,
1287 ) -> Option<Entity<ui::ContextMenu>>,
1288 >,
1289 >,
1290 last_bounds: Option<Bounds<Pixels>>,
1291 last_position_map: Option<Rc<PositionMap>>,
1292 expect_bounds_change: Option<Bounds<Pixels>>,
1293 runnables: RunnableData,
1294 breakpoint_store: Option<Entity<BreakpointStore>>,
1295 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1296 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1297 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1298 /// Active diff review overlays. Multiple overlays can be open simultaneously
1299 /// when hunks have comments stored.
1300 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1301 /// Stored review comments grouped by hunk.
1302 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1303 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1304 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1305 /// Counter for generating unique comment IDs.
1306 next_review_comment_id: usize,
1307 hovered_diff_hunk_row: Option<DisplayRow>,
1308 pull_diagnostics_task: Task<()>,
1309 in_project_search: bool,
1310 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1311 breadcrumb_header: Option<String>,
1312 focused_block: Option<FocusedBlock>,
1313 next_scroll_position: NextScrollCursorCenterTopBottom,
1314 addons: HashMap<TypeId, Box<dyn Addon>>,
1315 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1316 load_diff_task: Option<Shared<Task<()>>>,
1317 /// Whether we are temporarily displaying a diff other than git's
1318 temporary_diff_override: bool,
1319 selection_mark_mode: bool,
1320 toggle_fold_multiple_buffers: Task<()>,
1321 _scroll_cursor_center_top_bottom_task: Task<()>,
1322 serialize_selections: Task<()>,
1323 serialize_folds: Task<()>,
1324 mouse_cursor_hidden: bool,
1325 minimap: Option<Entity<Self>>,
1326 hide_mouse_mode: HideMouseMode,
1327 pub change_list: ChangeList,
1328 inline_value_cache: InlineValueCache,
1329 number_deleted_lines: bool,
1330
1331 selection_drag_state: SelectionDragState,
1332 colors: Option<LspColorData>,
1333 post_scroll_update: Task<()>,
1334 refresh_colors_task: Task<()>,
1335 use_document_folding_ranges: bool,
1336 refresh_folding_ranges_task: Task<()>,
1337 inlay_hints: Option<LspInlayHintData>,
1338 folding_newlines: Task<()>,
1339 select_next_is_case_sensitive: Option<bool>,
1340 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1341 on_local_selections_changed:
1342 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1343 suppress_selection_callback: bool,
1344 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1345 accent_data: Option<AccentData>,
1346 bracket_fetched_tree_sitter_chunks: HashMap<Range<text::Anchor>, HashSet<Range<BufferRow>>>,
1347 semantic_token_state: SemanticTokenState,
1348 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1349 refresh_document_symbols_task: Shared<Task<()>>,
1350 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1351 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1352 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1353 sticky_headers_task: Task<()>,
1354 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1355 pub(crate) colorize_brackets_task: Task<()>,
1356}
1357
1358#[derive(Debug, PartialEq)]
1359struct AccentData {
1360 colors: AccentColors,
1361 overrides: Vec<SharedString>,
1362}
1363
1364fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1365 if debounce_ms > 0 {
1366 Some(Duration::from_millis(debounce_ms))
1367 } else {
1368 None
1369 }
1370}
1371
1372#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1373enum NextScrollCursorCenterTopBottom {
1374 #[default]
1375 Center,
1376 Top,
1377 Bottom,
1378}
1379
1380impl NextScrollCursorCenterTopBottom {
1381 fn next(&self) -> Self {
1382 match self {
1383 Self::Center => Self::Top,
1384 Self::Top => Self::Bottom,
1385 Self::Bottom => Self::Center,
1386 }
1387 }
1388}
1389
1390#[derive(Clone)]
1391pub struct EditorSnapshot {
1392 pub mode: EditorMode,
1393 show_gutter: bool,
1394 offset_content: bool,
1395 show_line_numbers: Option<bool>,
1396 number_deleted_lines: bool,
1397 show_git_diff_gutter: Option<bool>,
1398 show_code_actions: Option<bool>,
1399 show_runnables: Option<bool>,
1400 show_breakpoints: Option<bool>,
1401 git_blame_gutter_max_author_length: Option<usize>,
1402 pub display_snapshot: DisplaySnapshot,
1403 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1404 is_focused: bool,
1405 scroll_anchor: SharedScrollAnchor,
1406 ongoing_scroll: OngoingScroll,
1407 current_line_highlight: CurrentLineHighlight,
1408 gutter_hovered: bool,
1409 semantic_tokens_enabled: bool,
1410}
1411
1412#[derive(Default, Debug, Clone, Copy)]
1413pub struct GutterDimensions {
1414 pub left_padding: Pixels,
1415 pub right_padding: Pixels,
1416 pub width: Pixels,
1417 pub margin: Pixels,
1418 pub git_blame_entries_width: Option<Pixels>,
1419}
1420
1421impl GutterDimensions {
1422 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1423 Self {
1424 margin: Self::default_gutter_margin(font_id, font_size, cx),
1425 ..Default::default()
1426 }
1427 }
1428
1429 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1430 -cx.text_system().descent(font_id, font_size)
1431 }
1432 /// The full width of the space taken up by the gutter.
1433 pub fn full_width(&self) -> Pixels {
1434 self.margin + self.width
1435 }
1436
1437 /// The width of the space reserved for the fold indicators,
1438 /// use alongside 'justify_end' and `gutter_width` to
1439 /// right align content with the line numbers
1440 pub fn fold_area_width(&self) -> Pixels {
1441 self.margin + self.right_padding
1442 }
1443}
1444
1445struct CharacterDimensions {
1446 em_width: Pixels,
1447 em_advance: Pixels,
1448 line_height: Pixels,
1449}
1450
1451#[derive(Debug)]
1452pub struct RemoteSelection {
1453 pub replica_id: ReplicaId,
1454 pub selection: Selection<Anchor>,
1455 pub cursor_shape: CursorShape,
1456 pub collaborator_id: CollaboratorId,
1457 pub line_mode: bool,
1458 pub user_name: Option<SharedString>,
1459 pub color: PlayerColor,
1460}
1461
1462#[derive(Clone, Debug)]
1463struct SelectionHistoryEntry {
1464 selections: Arc<[Selection<Anchor>]>,
1465 select_next_state: Option<SelectNextState>,
1466 select_prev_state: Option<SelectNextState>,
1467 add_selections_state: Option<AddSelectionsState>,
1468}
1469
1470#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1471enum SelectionHistoryMode {
1472 #[default]
1473 Normal,
1474 Undoing,
1475 Redoing,
1476 Skipping,
1477}
1478
1479#[derive(Clone, PartialEq, Eq, Hash)]
1480struct HoveredCursor {
1481 replica_id: ReplicaId,
1482 selection_id: usize,
1483}
1484
1485#[derive(Debug)]
1486/// SelectionEffects controls the side-effects of updating the selection.
1487///
1488/// The default behaviour does "what you mostly want":
1489/// - it pushes to the nav history if the cursor moved by >10 lines
1490/// - it re-triggers completion requests
1491/// - it scrolls to fit
1492///
1493/// You might want to modify these behaviours. For example when doing a "jump"
1494/// like go to definition, we always want to add to nav history; but when scrolling
1495/// in vim mode we never do.
1496///
1497/// Similarly, you might want to disable scrolling if you don't want the viewport to
1498/// move.
1499#[derive(Clone)]
1500pub struct SelectionEffects {
1501 nav_history: Option<bool>,
1502 completions: bool,
1503 scroll: Option<Autoscroll>,
1504}
1505
1506impl Default for SelectionEffects {
1507 fn default() -> Self {
1508 Self {
1509 nav_history: None,
1510 completions: true,
1511 scroll: Some(Autoscroll::fit()),
1512 }
1513 }
1514}
1515impl SelectionEffects {
1516 pub fn scroll(scroll: Autoscroll) -> Self {
1517 Self {
1518 scroll: Some(scroll),
1519 ..Default::default()
1520 }
1521 }
1522
1523 pub fn no_scroll() -> Self {
1524 Self {
1525 scroll: None,
1526 ..Default::default()
1527 }
1528 }
1529
1530 pub fn completions(self, completions: bool) -> Self {
1531 Self {
1532 completions,
1533 ..self
1534 }
1535 }
1536
1537 pub fn nav_history(self, nav_history: bool) -> Self {
1538 Self {
1539 nav_history: Some(nav_history),
1540 ..self
1541 }
1542 }
1543}
1544
1545struct DeferredSelectionEffectsState {
1546 changed: bool,
1547 effects: SelectionEffects,
1548 old_cursor_position: Anchor,
1549 history_entry: SelectionHistoryEntry,
1550}
1551
1552#[derive(Default)]
1553struct SelectionHistory {
1554 #[allow(clippy::type_complexity)]
1555 selections_by_transaction:
1556 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1557 mode: SelectionHistoryMode,
1558 undo_stack: VecDeque<SelectionHistoryEntry>,
1559 redo_stack: VecDeque<SelectionHistoryEntry>,
1560}
1561
1562impl SelectionHistory {
1563 #[track_caller]
1564 fn insert_transaction(
1565 &mut self,
1566 transaction_id: TransactionId,
1567 selections: Arc<[Selection<Anchor>]>,
1568 ) {
1569 if selections.is_empty() {
1570 log::error!(
1571 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1572 std::panic::Location::caller()
1573 );
1574 return;
1575 }
1576 self.selections_by_transaction
1577 .insert(transaction_id, (selections, None));
1578 }
1579
1580 #[allow(clippy::type_complexity)]
1581 fn transaction(
1582 &self,
1583 transaction_id: TransactionId,
1584 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1585 self.selections_by_transaction.get(&transaction_id)
1586 }
1587
1588 #[allow(clippy::type_complexity)]
1589 fn transaction_mut(
1590 &mut self,
1591 transaction_id: TransactionId,
1592 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1593 self.selections_by_transaction.get_mut(&transaction_id)
1594 }
1595
1596 fn push(&mut self, entry: SelectionHistoryEntry) {
1597 if !entry.selections.is_empty() {
1598 match self.mode {
1599 SelectionHistoryMode::Normal => {
1600 self.push_undo(entry);
1601 self.redo_stack.clear();
1602 }
1603 SelectionHistoryMode::Undoing => self.push_redo(entry),
1604 SelectionHistoryMode::Redoing => self.push_undo(entry),
1605 SelectionHistoryMode::Skipping => {}
1606 }
1607 }
1608 }
1609
1610 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1611 if self
1612 .undo_stack
1613 .back()
1614 .is_none_or(|e| e.selections != entry.selections)
1615 {
1616 self.undo_stack.push_back(entry);
1617 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1618 self.undo_stack.pop_front();
1619 }
1620 }
1621 }
1622
1623 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1624 if self
1625 .redo_stack
1626 .back()
1627 .is_none_or(|e| e.selections != entry.selections)
1628 {
1629 self.redo_stack.push_back(entry);
1630 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1631 self.redo_stack.pop_front();
1632 }
1633 }
1634 }
1635}
1636
1637#[derive(Clone, Copy)]
1638pub struct RowHighlightOptions {
1639 pub autoscroll: bool,
1640 pub include_gutter: bool,
1641}
1642
1643impl Default for RowHighlightOptions {
1644 fn default() -> Self {
1645 Self {
1646 autoscroll: Default::default(),
1647 include_gutter: true,
1648 }
1649 }
1650}
1651
1652struct RowHighlight {
1653 index: usize,
1654 range: Range<Anchor>,
1655 color: Hsla,
1656 options: RowHighlightOptions,
1657 type_id: TypeId,
1658}
1659
1660#[derive(Clone, Debug)]
1661struct AddSelectionsState {
1662 groups: Vec<AddSelectionsGroup>,
1663}
1664
1665#[derive(Clone, Debug)]
1666struct AddSelectionsGroup {
1667 above: bool,
1668 stack: Vec<usize>,
1669}
1670
1671#[derive(Clone)]
1672struct SelectNextState {
1673 query: AhoCorasick,
1674 wordwise: bool,
1675 done: bool,
1676}
1677
1678impl std::fmt::Debug for SelectNextState {
1679 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1680 f.debug_struct(std::any::type_name::<Self>())
1681 .field("wordwise", &self.wordwise)
1682 .field("done", &self.done)
1683 .finish()
1684 }
1685}
1686
1687#[derive(Debug)]
1688struct AutocloseRegion {
1689 selection_id: usize,
1690 range: Range<Anchor>,
1691 pair: BracketPair,
1692}
1693
1694#[derive(Debug)]
1695struct SnippetState {
1696 ranges: Vec<Vec<Range<Anchor>>>,
1697 active_index: usize,
1698 choices: Vec<Option<Vec<String>>>,
1699}
1700
1701#[doc(hidden)]
1702pub struct RenameState {
1703 pub range: Range<Anchor>,
1704 pub old_name: Arc<str>,
1705 pub editor: Entity<Editor>,
1706 block_id: CustomBlockId,
1707}
1708
1709struct InvalidationStack<T>(Vec<T>);
1710
1711struct RegisteredEditPredictionDelegate {
1712 provider: Arc<dyn EditPredictionDelegateHandle>,
1713 _subscription: Subscription,
1714}
1715
1716#[derive(Debug, PartialEq, Eq)]
1717pub struct ActiveDiagnosticGroup {
1718 pub active_range: Range<Anchor>,
1719 pub active_message: String,
1720 pub group_id: usize,
1721 pub blocks: HashSet<CustomBlockId>,
1722}
1723
1724#[derive(Debug, PartialEq, Eq)]
1725
1726pub(crate) enum ActiveDiagnostic {
1727 None,
1728 All,
1729 Group(ActiveDiagnosticGroup),
1730}
1731
1732#[derive(Serialize, Deserialize, Clone, Debug)]
1733pub struct ClipboardSelection {
1734 /// The number of bytes in this selection.
1735 pub len: usize,
1736 /// Whether this was a full-line selection.
1737 pub is_entire_line: bool,
1738 /// The indentation of the first line when this content was originally copied.
1739 pub first_line_indent: u32,
1740 #[serde(default)]
1741 pub file_path: Option<PathBuf>,
1742 #[serde(default)]
1743 pub line_range: Option<RangeInclusive<u32>>,
1744}
1745
1746impl ClipboardSelection {
1747 pub fn for_buffer(
1748 len: usize,
1749 is_entire_line: bool,
1750 range: Range<Point>,
1751 buffer: &MultiBufferSnapshot,
1752 project: Option<&Entity<Project>>,
1753 cx: &App,
1754 ) -> Self {
1755 let first_line_indent = buffer
1756 .indent_size_for_line(MultiBufferRow(range.start.row))
1757 .len;
1758
1759 let file_path = util::maybe!({
1760 let project = project?.read(cx);
1761 let file = buffer.file_at(range.start)?;
1762 let project_path = ProjectPath {
1763 worktree_id: file.worktree_id(cx),
1764 path: file.path().clone(),
1765 };
1766 project.absolute_path(&project_path, cx)
1767 });
1768
1769 let line_range = if file_path.is_some() {
1770 buffer
1771 .range_to_buffer_range(range)
1772 .map(|(_, buffer_range)| buffer_range.start.row..=buffer_range.end.row)
1773 } else {
1774 None
1775 };
1776
1777 Self {
1778 len,
1779 is_entire_line,
1780 first_line_indent,
1781 file_path,
1782 line_range,
1783 }
1784 }
1785}
1786
1787// selections, scroll behavior, was newest selection reversed
1788type SelectSyntaxNodeHistoryState = (
1789 Box<[Selection<Anchor>]>,
1790 SelectSyntaxNodeScrollBehavior,
1791 bool,
1792);
1793
1794#[derive(Default)]
1795struct SelectSyntaxNodeHistory {
1796 stack: Vec<SelectSyntaxNodeHistoryState>,
1797 // disable temporarily to allow changing selections without losing the stack
1798 pub disable_clearing: bool,
1799}
1800
1801impl SelectSyntaxNodeHistory {
1802 pub fn try_clear(&mut self) {
1803 if !self.disable_clearing {
1804 self.stack.clear();
1805 }
1806 }
1807
1808 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1809 self.stack.push(selection);
1810 }
1811
1812 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1813 self.stack.pop()
1814 }
1815}
1816
1817enum SelectSyntaxNodeScrollBehavior {
1818 CursorTop,
1819 FitSelection,
1820 CursorBottom,
1821}
1822
1823#[derive(Debug, Clone, Copy)]
1824pub(crate) struct NavigationData {
1825 cursor_anchor: Anchor,
1826 cursor_position: Point,
1827 scroll_anchor: ScrollAnchor,
1828 scroll_top_row: u32,
1829}
1830
1831#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1832pub enum GotoDefinitionKind {
1833 Symbol,
1834 Declaration,
1835 Type,
1836 Implementation,
1837}
1838
1839pub enum FormatTarget {
1840 Buffers(HashSet<Entity<Buffer>>),
1841 Ranges(Vec<Range<MultiBufferPoint>>),
1842}
1843
1844pub(crate) struct FocusedBlock {
1845 id: BlockId,
1846 focus_handle: WeakFocusHandle,
1847}
1848
1849#[derive(Clone, Debug)]
1850pub enum JumpData {
1851 MultiBufferRow {
1852 row: MultiBufferRow,
1853 line_offset_from_top: u32,
1854 },
1855 MultiBufferPoint {
1856 anchor: language::Anchor,
1857 position: Point,
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().clone();
1994 let scroll_anchor = self
1995 .scroll_manager
1996 .native_anchor(display_snapshot, cx)
1997 .anchor;
1998 let Some(buffer_snapshot) = multi_buffer.as_singleton() else {
1999 return;
2000 };
2001
2002 let buffer = buffer_snapshot.clone();
2003 let Some((buffer_visible_start, _)) = multi_buffer.anchor_to_buffer_anchor(scroll_anchor)
2004 else {
2005 return;
2006 };
2007 let buffer_visible_start = buffer_visible_start.to_point(&buffer);
2008 let max_row = buffer.max_point().row;
2009 let start_row = buffer_visible_start.row.min(max_row);
2010 let end_row = (buffer_visible_start.row + 10).min(max_row);
2011
2012 let syntax = self.style(cx).syntax.clone();
2013 let background_task = cx.background_spawn(async move {
2014 buffer
2015 .outline_items_containing(
2016 Point::new(start_row, 0)..Point::new(end_row, 0),
2017 true,
2018 Some(syntax.as_ref()),
2019 )
2020 .into_iter()
2021 .filter_map(|outline_item| {
2022 Some(OutlineItem {
2023 depth: outline_item.depth,
2024 range: multi_buffer
2025 .buffer_anchor_range_to_anchor_range(outline_item.range)?,
2026 source_range_for_text: multi_buffer.buffer_anchor_range_to_anchor_range(
2027 outline_item.source_range_for_text,
2028 )?,
2029 text: outline_item.text,
2030 highlight_ranges: outline_item.highlight_ranges,
2031 name_ranges: outline_item.name_ranges,
2032 body_range: outline_item.body_range.and_then(|range| {
2033 multi_buffer.buffer_anchor_range_to_anchor_range(range)
2034 }),
2035 annotation_range: outline_item.annotation_range.and_then(|range| {
2036 multi_buffer.buffer_anchor_range_to_anchor_range(range)
2037 }),
2038 })
2039 })
2040 .collect()
2041 });
2042 self.sticky_headers_task = cx.spawn(async move |this, cx| {
2043 let sticky_headers = background_task.await;
2044 this.update(cx, |this, cx| {
2045 this.sticky_headers = Some(sticky_headers);
2046 cx.notify();
2047 })
2048 .ok();
2049 });
2050 }
2051
2052 fn new_internal(
2053 mode: EditorMode,
2054 multi_buffer: Entity<MultiBuffer>,
2055 project: Option<Entity<Project>>,
2056 display_map: Option<Entity<DisplayMap>>,
2057 window: &mut Window,
2058 cx: &mut Context<Self>,
2059 ) -> Self {
2060 debug_assert!(
2061 display_map.is_none() || mode.is_minimap(),
2062 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
2063 );
2064
2065 let full_mode = mode.is_full();
2066 let is_minimap = mode.is_minimap();
2067 let diagnostics_max_severity = if full_mode {
2068 EditorSettings::get_global(cx)
2069 .diagnostics_max_severity
2070 .unwrap_or(DiagnosticSeverity::Hint)
2071 } else {
2072 DiagnosticSeverity::Off
2073 };
2074 let style = window.text_style();
2075 let font_size = style.font_size.to_pixels(window.rem_size());
2076 let editor = cx.entity().downgrade();
2077 let fold_placeholder = FoldPlaceholder {
2078 constrain_width: false,
2079 render: Arc::new(move |fold_id, fold_range, cx| {
2080 let editor = editor.clone();
2081 FoldPlaceholder::fold_element(fold_id, cx)
2082 .cursor_pointer()
2083 .child("⋯")
2084 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
2085 .on_click(move |_, _window, cx| {
2086 editor
2087 .update(cx, |editor, cx| {
2088 editor.unfold_ranges(
2089 &[fold_range.start..fold_range.end],
2090 true,
2091 false,
2092 cx,
2093 );
2094 cx.stop_propagation();
2095 })
2096 .ok();
2097 })
2098 .into_any()
2099 }),
2100 merge_adjacent: true,
2101 ..FoldPlaceholder::default()
2102 };
2103 let display_map = display_map.unwrap_or_else(|| {
2104 cx.new(|cx| {
2105 DisplayMap::new(
2106 multi_buffer.clone(),
2107 style.font(),
2108 font_size,
2109 None,
2110 FILE_HEADER_HEIGHT,
2111 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2112 fold_placeholder,
2113 diagnostics_max_severity,
2114 cx,
2115 )
2116 })
2117 });
2118
2119 let selections = SelectionsCollection::new();
2120
2121 let blink_manager = cx.new(|cx| {
2122 let mut blink_manager = BlinkManager::new(
2123 CURSOR_BLINK_INTERVAL,
2124 |cx| EditorSettings::get_global(cx).cursor_blink,
2125 cx,
2126 );
2127 if is_minimap {
2128 blink_manager.disable(cx);
2129 }
2130 blink_manager
2131 });
2132
2133 let soft_wrap_mode_override =
2134 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
2135
2136 let mut project_subscriptions = Vec::new();
2137 if full_mode && let Some(project) = project.as_ref() {
2138 project_subscriptions.push(cx.subscribe_in(
2139 project,
2140 window,
2141 |editor, _, event, window, cx| match event {
2142 project::Event::RefreshCodeLens => {
2143 // we always query lens with actions, without storing them, always refreshing them
2144 }
2145 project::Event::RefreshInlayHints {
2146 server_id,
2147 request_id,
2148 } => {
2149 editor.refresh_inlay_hints(
2150 InlayHintRefreshReason::RefreshRequested {
2151 server_id: *server_id,
2152 request_id: *request_id,
2153 },
2154 cx,
2155 );
2156 }
2157 project::Event::RefreshSemanticTokens {
2158 server_id,
2159 request_id,
2160 } => {
2161 editor.refresh_semantic_tokens(
2162 None,
2163 Some(RefreshForServer {
2164 server_id: *server_id,
2165 request_id: *request_id,
2166 }),
2167 cx,
2168 );
2169 }
2170 project::Event::LanguageServerRemoved(_) => {
2171 editor.registered_buffers.clear();
2172 editor.register_visible_buffers(cx);
2173 editor.invalidate_semantic_tokens(None);
2174 editor.refresh_runnables(None, window, cx);
2175 editor.update_lsp_data(None, window, cx);
2176 editor.refresh_inlay_hints(InlayHintRefreshReason::ServerRemoved, cx);
2177 }
2178 project::Event::SnippetEdit(id, snippet_edits) => {
2179 // todo(lw): Non singletons
2180 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2181 let snapshot = buffer.read(cx).snapshot();
2182 let focus_handle = editor.focus_handle(cx);
2183 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2184 for (range, snippet) in snippet_edits {
2185 let buffer_range =
2186 language::range_from_lsp(*range).to_offset(&snapshot);
2187 editor
2188 .insert_snippet(
2189 &[MultiBufferOffset(buffer_range.start)
2190 ..MultiBufferOffset(buffer_range.end)],
2191 snippet.clone(),
2192 window,
2193 cx,
2194 )
2195 .ok();
2196 }
2197 }
2198 }
2199 }
2200 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2201 let buffer_id = *buffer_id;
2202 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2203 editor.register_buffer(buffer_id, cx);
2204 editor.refresh_runnables(Some(buffer_id), window, cx);
2205 editor.update_lsp_data(Some(buffer_id), window, cx);
2206 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2207 refresh_linked_ranges(editor, window, cx);
2208 editor.refresh_code_actions(window, cx);
2209 editor.refresh_document_highlights(cx);
2210 }
2211 }
2212
2213 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2214 let Some(workspace) = editor.workspace() else {
2215 return;
2216 };
2217 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2218 else {
2219 return;
2220 };
2221
2222 if active_editor.entity_id() == cx.entity_id() {
2223 let entity_id = cx.entity_id();
2224 workspace.update(cx, |this, cx| {
2225 this.panes_mut()
2226 .iter_mut()
2227 .filter(|pane| pane.entity_id() != entity_id)
2228 .for_each(|p| {
2229 p.update(cx, |pane, _| {
2230 pane.nav_history_mut().rename_item(
2231 entity_id,
2232 project_path.clone(),
2233 abs_path.clone().into(),
2234 );
2235 })
2236 });
2237 });
2238
2239 Self::open_transaction_for_hidden_buffers(
2240 workspace,
2241 transaction.clone(),
2242 "Rename".to_string(),
2243 window,
2244 cx,
2245 );
2246 }
2247 }
2248
2249 project::Event::WorkspaceEditApplied(transaction) => {
2250 let Some(workspace) = editor.workspace() else {
2251 return;
2252 };
2253 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2254 else {
2255 return;
2256 };
2257
2258 if active_editor.entity_id() == cx.entity_id() {
2259 Self::open_transaction_for_hidden_buffers(
2260 workspace,
2261 transaction.clone(),
2262 "LSP Edit".to_string(),
2263 window,
2264 cx,
2265 );
2266 }
2267 }
2268
2269 _ => {}
2270 },
2271 ));
2272 if let Some(task_inventory) = project
2273 .read(cx)
2274 .task_store()
2275 .read(cx)
2276 .task_inventory()
2277 .cloned()
2278 {
2279 project_subscriptions.push(cx.observe_in(
2280 &task_inventory,
2281 window,
2282 |editor, _, window, cx| {
2283 editor.refresh_runnables(None, window, cx);
2284 },
2285 ));
2286 };
2287
2288 project_subscriptions.push(cx.subscribe_in(
2289 &project.read(cx).breakpoint_store(),
2290 window,
2291 |editor, _, event, window, cx| match event {
2292 BreakpointStoreEvent::ClearDebugLines => {
2293 editor.clear_row_highlights::<ActiveDebugLine>();
2294 editor.refresh_inline_values(cx);
2295 }
2296 BreakpointStoreEvent::SetDebugLine => {
2297 if editor.go_to_active_debug_line(window, cx) {
2298 cx.stop_propagation();
2299 }
2300
2301 editor.refresh_inline_values(cx);
2302 }
2303 _ => {}
2304 },
2305 ));
2306 let git_store = project.read(cx).git_store().clone();
2307 let project = project.clone();
2308 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2309 if let GitStoreEvent::RepositoryAdded = event {
2310 this.load_diff_task = Some(
2311 update_uncommitted_diff_for_buffer(
2312 cx.entity(),
2313 &project,
2314 this.buffer.read(cx).all_buffers(),
2315 this.buffer.clone(),
2316 cx,
2317 )
2318 .shared(),
2319 );
2320 }
2321 }));
2322 }
2323
2324 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2325
2326 let inlay_hint_settings =
2327 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2328 let focus_handle = cx.focus_handle();
2329 if !is_minimap {
2330 cx.on_focus(&focus_handle, window, Self::handle_focus)
2331 .detach();
2332 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2333 .detach();
2334 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2335 .detach();
2336 cx.on_blur(&focus_handle, window, Self::handle_blur)
2337 .detach();
2338 cx.observe_pending_input(window, Self::observe_pending_input)
2339 .detach();
2340 }
2341
2342 let show_indent_guides =
2343 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2344 Some(false)
2345 } else {
2346 None
2347 };
2348
2349 let breakpoint_store = match (&mode, project.as_ref()) {
2350 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2351 _ => None,
2352 };
2353
2354 let mut code_action_providers = Vec::new();
2355 let mut load_uncommitted_diff = None;
2356 if let Some(project) = project.clone() {
2357 load_uncommitted_diff = Some(
2358 update_uncommitted_diff_for_buffer(
2359 cx.entity(),
2360 &project,
2361 multi_buffer.read(cx).all_buffers(),
2362 multi_buffer.clone(),
2363 cx,
2364 )
2365 .shared(),
2366 );
2367 code_action_providers.push(Rc::new(project) as Rc<_>);
2368 }
2369
2370 let mut editor = Self {
2371 focus_handle,
2372 show_cursor_when_unfocused: false,
2373 last_focused_descendant: None,
2374 buffer: multi_buffer.clone(),
2375 display_map: display_map.clone(),
2376 placeholder_display_map: None,
2377 selections,
2378 scroll_manager: ScrollManager::new(cx),
2379 columnar_selection_state: None,
2380 add_selections_state: None,
2381 select_next_state: None,
2382 select_prev_state: None,
2383 selection_history: SelectionHistory::default(),
2384 defer_selection_effects: false,
2385 deferred_selection_effects_state: None,
2386 autoclose_regions: Vec::new(),
2387 snippet_stack: InvalidationStack::default(),
2388 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2389 ime_transaction: None,
2390 active_diagnostics: ActiveDiagnostic::None,
2391 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2392 inline_diagnostics_update: Task::ready(()),
2393 inline_diagnostics: Vec::new(),
2394 soft_wrap_mode_override,
2395 diagnostics_max_severity,
2396 hard_wrap: None,
2397 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2398 semantics_provider: project
2399 .as_ref()
2400 .map(|project| Rc::new(project.downgrade()) as _),
2401 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2402 project,
2403 blink_manager: blink_manager.clone(),
2404 show_local_selections: true,
2405 show_scrollbars: ScrollbarAxes {
2406 horizontal: full_mode,
2407 vertical: full_mode,
2408 },
2409 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2410 offset_content: !matches!(mode, EditorMode::SingleLine),
2411 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2412 show_gutter: full_mode,
2413 show_line_numbers: (!full_mode).then_some(false),
2414 use_relative_line_numbers: None,
2415 disable_expand_excerpt_buttons: !full_mode,
2416 delegate_expand_excerpts: false,
2417 delegate_stage_and_restore: false,
2418 delegate_open_excerpts: false,
2419 enable_lsp_data: true,
2420 enable_runnables: true,
2421 show_git_diff_gutter: None,
2422 show_code_actions: None,
2423 show_runnables: None,
2424 show_breakpoints: None,
2425 show_diff_review_button: false,
2426 show_wrap_guides: None,
2427 show_indent_guides,
2428 buffers_with_disabled_indent_guides: HashSet::default(),
2429 highlight_order: 0,
2430 highlighted_rows: HashMap::default(),
2431 background_highlights: HashMap::default(),
2432 gutter_highlights: HashMap::default(),
2433 scrollbar_marker_state: ScrollbarMarkerState::default(),
2434 active_indent_guides_state: ActiveIndentGuidesState::default(),
2435 nav_history: None,
2436 context_menu: RefCell::new(None),
2437 context_menu_options: None,
2438 mouse_context_menu: None,
2439 completion_tasks: Vec::new(),
2440 inline_blame_popover: None,
2441 inline_blame_popover_show_task: None,
2442 signature_help_state: SignatureHelpState::default(),
2443 auto_signature_help: None,
2444 find_all_references_task_sources: Vec::new(),
2445 next_completion_id: 0,
2446 next_inlay_id: 0,
2447 code_action_providers,
2448 available_code_actions: None,
2449 code_actions_task: None,
2450 quick_selection_highlight_task: None,
2451 debounced_selection_highlight_task: None,
2452 debounced_selection_highlight_complete: false,
2453 document_highlights_task: None,
2454 linked_editing_range_task: None,
2455 pending_rename: None,
2456 searchable: !is_minimap,
2457 cursor_shape: EditorSettings::get_global(cx)
2458 .cursor_shape
2459 .unwrap_or_default(),
2460 cursor_offset_on_selection: false,
2461 current_line_highlight: None,
2462 autoindent_mode: Some(AutoindentMode::EachLine),
2463 collapse_matches: false,
2464 workspace: None,
2465 input_enabled: !is_minimap,
2466 expects_character_input: !is_minimap,
2467 use_modal_editing: full_mode,
2468 read_only: is_minimap,
2469 use_autoclose: true,
2470 use_auto_surround: true,
2471 auto_replace_emoji_shortcode: false,
2472 jsx_tag_auto_close_enabled_in_any_buffer: false,
2473 leader_id: None,
2474 remote_id: None,
2475 hover_state: HoverState::default(),
2476 pending_mouse_down: None,
2477 prev_pressure_stage: None,
2478 hovered_link_state: None,
2479 edit_prediction_provider: None,
2480 active_edit_prediction: None,
2481 stale_edit_prediction_in_menu: None,
2482 edit_prediction_preview: EditPredictionPreview::Inactive {
2483 released_too_fast: false,
2484 },
2485 inline_diagnostics_enabled: full_mode,
2486 diagnostics_enabled: full_mode,
2487 word_completions_enabled: full_mode,
2488 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2489 gutter_hovered: false,
2490 pixel_position_of_newest_cursor: None,
2491 last_bounds: None,
2492 last_position_map: None,
2493 expect_bounds_change: None,
2494 gutter_dimensions: GutterDimensions::default(),
2495 style: None,
2496 show_cursor_names: false,
2497 hovered_cursors: HashMap::default(),
2498 next_editor_action_id: EditorActionId::default(),
2499 editor_actions: Rc::default(),
2500 edit_predictions_hidden_for_vim_mode: false,
2501 show_edit_predictions_override: None,
2502 show_completions_on_input_override: None,
2503 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2504 edit_prediction_settings: EditPredictionSettings::Disabled,
2505 in_leading_whitespace: false,
2506 custom_context_menu: None,
2507 show_git_blame_gutter: false,
2508 show_git_blame_inline: false,
2509 show_selection_menu: None,
2510 show_git_blame_inline_delay_task: None,
2511 git_blame_inline_enabled: full_mode
2512 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2513 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2514 buffer_serialization: is_minimap.not().then(|| {
2515 BufferSerialization::new(
2516 ProjectSettings::get_global(cx)
2517 .session
2518 .restore_unsaved_buffers,
2519 )
2520 }),
2521 blame: None,
2522 blame_subscription: None,
2523
2524 breakpoint_store,
2525 gutter_breakpoint_indicator: (None, None),
2526 gutter_diff_review_indicator: (None, None),
2527 diff_review_drag_state: None,
2528 diff_review_overlays: Vec::new(),
2529 stored_review_comments: Vec::new(),
2530 next_review_comment_id: 0,
2531 hovered_diff_hunk_row: None,
2532 _subscriptions: (!is_minimap)
2533 .then(|| {
2534 vec![
2535 cx.observe(&multi_buffer, Self::on_buffer_changed),
2536 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2537 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2538 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2539 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2540 cx.observe_global_in::<GlobalTheme>(window, Self::theme_changed),
2541 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2542 cx.observe_window_activation(window, |editor, window, cx| {
2543 let active = window.is_window_active();
2544 editor.blink_manager.update(cx, |blink_manager, cx| {
2545 if active {
2546 blink_manager.enable(cx);
2547 } else {
2548 blink_manager.disable(cx);
2549 }
2550 });
2551 if active {
2552 editor.show_mouse_cursor(cx);
2553 }
2554 }),
2555 ]
2556 })
2557 .unwrap_or_default(),
2558 runnables: RunnableData::new(),
2559 pull_diagnostics_task: Task::ready(()),
2560 colors: None,
2561 refresh_colors_task: Task::ready(()),
2562 use_document_folding_ranges: false,
2563 refresh_folding_ranges_task: Task::ready(()),
2564 inlay_hints: None,
2565 next_color_inlay_id: 0,
2566 post_scroll_update: Task::ready(()),
2567 linked_edit_ranges: Default::default(),
2568 in_project_search: false,
2569 previous_search_ranges: None,
2570 breadcrumb_header: None,
2571 focused_block: None,
2572 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2573 addons: HashMap::default(),
2574 registered_buffers: HashMap::default(),
2575 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2576 selection_mark_mode: false,
2577 toggle_fold_multiple_buffers: Task::ready(()),
2578 serialize_selections: Task::ready(()),
2579 serialize_folds: Task::ready(()),
2580 text_style_refinement: None,
2581 load_diff_task: load_uncommitted_diff,
2582 temporary_diff_override: false,
2583 mouse_cursor_hidden: false,
2584 minimap: None,
2585 hide_mouse_mode: EditorSettings::get_global(cx)
2586 .hide_mouse
2587 .unwrap_or_default(),
2588 change_list: ChangeList::new(),
2589 mode,
2590 selection_drag_state: SelectionDragState::None,
2591 folding_newlines: Task::ready(()),
2592 lookup_key: None,
2593 select_next_is_case_sensitive: None,
2594 on_local_selections_changed: None,
2595 suppress_selection_callback: false,
2596 applicable_language_settings: HashMap::default(),
2597 semantic_token_state: SemanticTokenState::new(cx, full_mode),
2598 accent_data: None,
2599 bracket_fetched_tree_sitter_chunks: HashMap::default(),
2600 number_deleted_lines: false,
2601 refresh_matching_bracket_highlights_task: Task::ready(()),
2602 refresh_document_symbols_task: Task::ready(()).shared(),
2603 lsp_document_symbols: HashMap::default(),
2604 refresh_outline_symbols_at_cursor_at_cursor_task: Task::ready(()),
2605 outline_symbols_at_cursor: None,
2606 sticky_headers_task: Task::ready(()),
2607 sticky_headers: None,
2608 colorize_brackets_task: Task::ready(()),
2609 };
2610
2611 if is_minimap {
2612 return editor;
2613 }
2614
2615 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2616 editor.accent_data = editor.fetch_accent_data(cx);
2617
2618 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2619 editor
2620 ._subscriptions
2621 .push(cx.observe(breakpoints, |_, _, cx| {
2622 cx.notify();
2623 }));
2624 }
2625 editor._subscriptions.extend(project_subscriptions);
2626
2627 editor._subscriptions.push(cx.subscribe_in(
2628 &cx.entity(),
2629 window,
2630 |editor, _, e: &EditorEvent, window, cx| match e {
2631 EditorEvent::ScrollPositionChanged { local, .. } => {
2632 if *local {
2633 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2634 editor.inline_blame_popover.take();
2635 let snapshot = editor.snapshot(window, cx);
2636 let new_anchor = editor
2637 .scroll_manager
2638 .native_anchor(&snapshot.display_snapshot, cx);
2639 editor.update_restoration_data(cx, move |data| {
2640 data.scroll_position = (
2641 new_anchor.top_row(snapshot.buffer_snapshot()),
2642 new_anchor.offset,
2643 );
2644 });
2645
2646 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2647 cx.background_executor()
2648 .timer(Duration::from_millis(50))
2649 .await;
2650 editor
2651 .update_in(cx, |editor, window, cx| {
2652 editor.update_data_on_scroll(window, cx)
2653 })
2654 .ok();
2655 });
2656 }
2657 editor.refresh_sticky_headers(&editor.snapshot(window, cx), cx);
2658 }
2659 EditorEvent::Edited { .. } => {
2660 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2661 .map(|vim_mode| vim_mode.0)
2662 .unwrap_or(false);
2663 if !vim_mode {
2664 let display_map = editor.display_snapshot(cx);
2665 let selections = editor.selections.all_adjusted_display(&display_map);
2666 let pop_state = editor
2667 .change_list
2668 .last()
2669 .map(|previous| {
2670 previous.len() == selections.len()
2671 && previous.iter().enumerate().all(|(ix, p)| {
2672 p.to_display_point(&display_map).row()
2673 == selections[ix].head().row()
2674 })
2675 })
2676 .unwrap_or(false);
2677 let new_positions = selections
2678 .into_iter()
2679 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2680 .collect();
2681 editor
2682 .change_list
2683 .push_to_change_list(pop_state, new_positions);
2684 }
2685 }
2686 _ => (),
2687 },
2688 ));
2689
2690 if let Some(dap_store) = editor
2691 .project
2692 .as_ref()
2693 .map(|project| project.read(cx).dap_store())
2694 {
2695 let weak_editor = cx.weak_entity();
2696
2697 editor
2698 ._subscriptions
2699 .push(
2700 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2701 let session_entity = cx.entity();
2702 weak_editor
2703 .update(cx, |editor, cx| {
2704 editor._subscriptions.push(
2705 cx.subscribe(&session_entity, Self::on_debug_session_event),
2706 );
2707 })
2708 .ok();
2709 }),
2710 );
2711
2712 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2713 editor
2714 ._subscriptions
2715 .push(cx.subscribe(&session, Self::on_debug_session_event));
2716 }
2717 }
2718
2719 // skip adding the initial selection to selection history
2720 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2721 editor.end_selection(window, cx);
2722 editor.selection_history.mode = SelectionHistoryMode::Normal;
2723
2724 editor.scroll_manager.show_scrollbars(window, cx);
2725 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2726
2727 if full_mode {
2728 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2729 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2730
2731 if editor.git_blame_inline_enabled {
2732 editor.start_git_blame_inline(false, window, cx);
2733 }
2734
2735 editor.go_to_active_debug_line(window, cx);
2736
2737 editor.minimap =
2738 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2739 editor.colors = Some(LspColorData::new(cx));
2740 editor.use_document_folding_ranges = true;
2741 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2742
2743 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2744 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2745 }
2746 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2747 }
2748
2749 editor
2750 }
2751
2752 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2753 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2754 }
2755
2756 pub fn deploy_mouse_context_menu(
2757 &mut self,
2758 position: gpui::Point<Pixels>,
2759 context_menu: Entity<ContextMenu>,
2760 window: &mut Window,
2761 cx: &mut Context<Self>,
2762 ) {
2763 self.mouse_context_menu = Some(MouseContextMenu::new(
2764 self,
2765 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2766 context_menu,
2767 window,
2768 cx,
2769 ));
2770 }
2771
2772 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2773 self.mouse_context_menu
2774 .as_ref()
2775 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2776 }
2777
2778 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2779 if self
2780 .selections
2781 .pending_anchor()
2782 .is_some_and(|pending_selection| {
2783 let snapshot = self.buffer().read(cx).snapshot(cx);
2784 pending_selection.range().includes(range, &snapshot)
2785 })
2786 {
2787 return true;
2788 }
2789
2790 self.selections
2791 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2792 .into_iter()
2793 .any(|selection| {
2794 // This is needed to cover a corner case, if we just check for an existing
2795 // selection in the fold range, having a cursor at the start of the fold
2796 // marks it as selected. Non-empty selections don't cause this.
2797 let length = selection.end - selection.start;
2798 length > 0
2799 })
2800 }
2801
2802 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2803 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2804 }
2805
2806 fn key_context_internal(
2807 &self,
2808 has_active_edit_prediction: bool,
2809 window: &mut Window,
2810 cx: &mut App,
2811 ) -> KeyContext {
2812 let mut key_context = KeyContext::new_with_defaults();
2813 key_context.add("Editor");
2814 let mode = match self.mode {
2815 EditorMode::SingleLine => "single_line",
2816 EditorMode::AutoHeight { .. } => "auto_height",
2817 EditorMode::Minimap { .. } => "minimap",
2818 EditorMode::Full { .. } => "full",
2819 };
2820
2821 if EditorSettings::jupyter_enabled(cx) {
2822 key_context.add("jupyter");
2823 }
2824
2825 key_context.set("mode", mode);
2826 if self.pending_rename.is_some() {
2827 key_context.add("renaming");
2828 }
2829
2830 if let Some(snippet_stack) = self.snippet_stack.last() {
2831 key_context.add("in_snippet");
2832
2833 if snippet_stack.active_index > 0 {
2834 key_context.add("has_previous_tabstop");
2835 }
2836
2837 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2838 key_context.add("has_next_tabstop");
2839 }
2840 }
2841
2842 match self.context_menu.borrow().as_ref() {
2843 Some(CodeContextMenu::Completions(menu)) => {
2844 if menu.visible() {
2845 key_context.add("menu");
2846 key_context.add("showing_completions");
2847 }
2848 }
2849 Some(CodeContextMenu::CodeActions(menu)) => {
2850 if menu.visible() {
2851 key_context.add("menu");
2852 key_context.add("showing_code_actions")
2853 }
2854 }
2855 None => {}
2856 }
2857
2858 if self.signature_help_state.has_multiple_signatures() {
2859 key_context.add("showing_signature_help");
2860 }
2861
2862 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2863 if !self.focus_handle(cx).contains_focused(window, cx)
2864 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2865 {
2866 for addon in self.addons.values() {
2867 addon.extend_key_context(&mut key_context, cx)
2868 }
2869 }
2870
2871 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2872 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2873 Some(
2874 file.full_path(cx)
2875 .extension()?
2876 .to_string_lossy()
2877 .to_lowercase(),
2878 )
2879 }) {
2880 key_context.set("extension", extension);
2881 }
2882 } else {
2883 key_context.add("multibuffer");
2884 }
2885
2886 if has_active_edit_prediction {
2887 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2888 key_context.add("copilot_suggestion");
2889 }
2890
2891 if self.in_leading_whitespace {
2892 key_context.add("in_leading_whitespace");
2893 }
2894 if self.edit_prediction_requires_modifier() {
2895 key_context.set("edit_prediction_mode", "subtle")
2896 } else {
2897 key_context.set("edit_prediction_mode", "eager");
2898 }
2899
2900 if self.selection_mark_mode {
2901 key_context.add("selection_mode");
2902 }
2903
2904 let disjoint = self.selections.disjoint_anchors();
2905 if matches!(
2906 &self.mode,
2907 EditorMode::SingleLine | EditorMode::AutoHeight { .. }
2908 ) && let [selection] = disjoint
2909 && selection.start == selection.end
2910 {
2911 let snapshot = self.snapshot(window, cx);
2912 let snapshot = snapshot.buffer_snapshot();
2913 let caret_offset = selection.end.to_offset(snapshot);
2914
2915 if caret_offset == MultiBufferOffset(0) {
2916 key_context.add("start_of_input");
2917 }
2918
2919 if caret_offset == snapshot.len() {
2920 key_context.add("end_of_input");
2921 }
2922 }
2923
2924 if self.has_any_expanded_diff_hunks(cx) {
2925 key_context.add("diffs_expanded");
2926 }
2927
2928 key_context
2929 }
2930
2931 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2932 self.last_bounds.as_ref()
2933 }
2934
2935 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2936 if self.mouse_cursor_hidden {
2937 self.mouse_cursor_hidden = false;
2938 cx.notify();
2939 }
2940 }
2941
2942 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2943 let hide_mouse_cursor = match origin {
2944 HideMouseCursorOrigin::TypingAction => {
2945 matches!(
2946 self.hide_mouse_mode,
2947 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2948 )
2949 }
2950 HideMouseCursorOrigin::MovementAction => {
2951 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2952 }
2953 };
2954 if self.mouse_cursor_hidden != hide_mouse_cursor {
2955 self.mouse_cursor_hidden = hide_mouse_cursor;
2956 cx.notify();
2957 }
2958 }
2959
2960 fn accept_edit_prediction_keystroke(
2961 &self,
2962 granularity: EditPredictionGranularity,
2963 window: &mut Window,
2964 cx: &mut App,
2965 ) -> Option<gpui::KeybindingKeystroke> {
2966 let key_context = self.key_context_internal(true, window, cx);
2967
2968 let bindings =
2969 match granularity {
2970 EditPredictionGranularity::Word => window
2971 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2972 EditPredictionGranularity::Line => window
2973 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2974 EditPredictionGranularity::Full => {
2975 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2976 }
2977 };
2978
2979 bindings
2980 .into_iter()
2981 .rev()
2982 .find_map(|binding| match binding.keystrokes() {
2983 [keystroke, ..] => Some(keystroke.clone()),
2984 _ => None,
2985 })
2986 }
2987
2988 fn preview_edit_prediction_keystroke(
2989 &self,
2990 window: &mut Window,
2991 cx: &mut App,
2992 ) -> Option<gpui::KeybindingKeystroke> {
2993 let key_context = self.key_context_internal(true, window, cx);
2994 let bindings = window.bindings_for_action_in_context(&AcceptEditPrediction, key_context);
2995 bindings
2996 .into_iter()
2997 .rev()
2998 .find_map(|binding| match binding.keystrokes() {
2999 [keystroke, ..] if keystroke.modifiers().modified() => Some(keystroke.clone()),
3000 _ => None,
3001 })
3002 }
3003
3004 fn edit_prediction_preview_modifiers_held(
3005 &self,
3006 modifiers: &Modifiers,
3007 window: &mut Window,
3008 cx: &mut App,
3009 ) -> bool {
3010 let key_context = self.key_context_internal(true, window, cx);
3011 let actions: [&dyn Action; 3] = [
3012 &AcceptEditPrediction,
3013 &AcceptNextWordEditPrediction,
3014 &AcceptNextLineEditPrediction,
3015 ];
3016
3017 actions.into_iter().any(|action| {
3018 window
3019 .bindings_for_action_in_context(action, key_context.clone())
3020 .into_iter()
3021 .rev()
3022 .any(|binding| {
3023 binding.keystrokes().first().is_some_and(|keystroke| {
3024 keystroke.modifiers().modified() && keystroke.modifiers() == modifiers
3025 })
3026 })
3027 })
3028 }
3029
3030 fn edit_prediction_cursor_popover_prefers_preview(
3031 &self,
3032 completion: &EditPredictionState,
3033 cx: &App,
3034 ) -> bool {
3035 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
3036
3037 match &completion.completion {
3038 EditPrediction::Edit {
3039 edits, snapshot, ..
3040 } => {
3041 let mut start_row: Option<u32> = None;
3042 let mut end_row: Option<u32> = None;
3043
3044 for (range, text) in edits {
3045 let Some((_, range)) =
3046 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(range.clone())
3047 else {
3048 continue;
3049 };
3050 let edit_start_row = range.start.to_point(snapshot).row;
3051 let old_end_row = range.end.to_point(snapshot).row;
3052 let inserted_newline_count = text
3053 .as_ref()
3054 .chars()
3055 .filter(|character| *character == '\n')
3056 .count() as u32;
3057 let deleted_newline_count = old_end_row - edit_start_row;
3058 let preview_end_row = edit_start_row + inserted_newline_count;
3059
3060 start_row =
3061 Some(start_row.map_or(edit_start_row, |row| row.min(edit_start_row)));
3062 end_row = Some(end_row.map_or(preview_end_row, |row| row.max(preview_end_row)));
3063
3064 if deleted_newline_count > 1 {
3065 end_row = Some(end_row.map_or(old_end_row, |row| row.max(old_end_row)));
3066 }
3067 }
3068
3069 start_row
3070 .zip(end_row)
3071 .is_some_and(|(start_row, end_row)| end_row > start_row)
3072 }
3073 EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => false,
3074 }
3075 }
3076
3077 fn edit_prediction_keybind_display(
3078 &self,
3079 surface: EditPredictionKeybindSurface,
3080 window: &mut Window,
3081 cx: &mut App,
3082 ) -> EditPredictionKeybindDisplay {
3083 let accept_keystroke =
3084 self.accept_edit_prediction_keystroke(EditPredictionGranularity::Full, window, cx);
3085 let preview_keystroke = self.preview_edit_prediction_keystroke(window, cx);
3086
3087 let action = match surface {
3088 EditPredictionKeybindSurface::Inline
3089 | EditPredictionKeybindSurface::CursorPopoverCompact => {
3090 if self.edit_prediction_requires_modifier() {
3091 EditPredictionKeybindAction::Preview
3092 } else {
3093 EditPredictionKeybindAction::Accept
3094 }
3095 }
3096 EditPredictionKeybindSurface::CursorPopoverExpanded => self
3097 .active_edit_prediction
3098 .as_ref()
3099 .filter(|completion| {
3100 self.edit_prediction_cursor_popover_prefers_preview(completion, cx)
3101 })
3102 .map_or(EditPredictionKeybindAction::Accept, |_| {
3103 EditPredictionKeybindAction::Preview
3104 }),
3105 };
3106 #[cfg(test)]
3107 let preview_copy = preview_keystroke.clone();
3108 #[cfg(test)]
3109 let accept_copy = accept_keystroke.clone();
3110
3111 let displayed_keystroke = match surface {
3112 EditPredictionKeybindSurface::Inline => match action {
3113 EditPredictionKeybindAction::Accept => accept_keystroke,
3114 EditPredictionKeybindAction::Preview => preview_keystroke,
3115 },
3116 EditPredictionKeybindSurface::CursorPopoverCompact
3117 | EditPredictionKeybindSurface::CursorPopoverExpanded => match action {
3118 EditPredictionKeybindAction::Accept => accept_keystroke,
3119 EditPredictionKeybindAction::Preview => {
3120 preview_keystroke.or_else(|| accept_keystroke.clone())
3121 }
3122 },
3123 };
3124
3125 let missing_accept_keystroke = displayed_keystroke.is_none();
3126
3127 EditPredictionKeybindDisplay {
3128 #[cfg(test)]
3129 accept_keystroke: accept_copy,
3130 #[cfg(test)]
3131 preview_keystroke: preview_copy,
3132 displayed_keystroke,
3133 action,
3134 missing_accept_keystroke,
3135 show_hold_label: matches!(surface, EditPredictionKeybindSurface::CursorPopoverCompact)
3136 && self.edit_prediction_preview.released_too_fast(),
3137 }
3138 }
3139
3140 pub fn new_file(
3141 workspace: &mut Workspace,
3142 _: &workspace::NewFile,
3143 window: &mut Window,
3144 cx: &mut Context<Workspace>,
3145 ) {
3146 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3147 "Failed to create buffer",
3148 window,
3149 cx,
3150 |e, _, _| match e.error_code() {
3151 ErrorCode::RemoteUpgradeRequired => Some(format!(
3152 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3153 e.error_tag("required").unwrap_or("the latest version")
3154 )),
3155 _ => None,
3156 },
3157 );
3158 }
3159
3160 pub fn new_in_workspace(
3161 workspace: &mut Workspace,
3162 window: &mut Window,
3163 cx: &mut Context<Workspace>,
3164 ) -> Task<Result<Entity<Editor>>> {
3165 let project = workspace.project().clone();
3166 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3167
3168 cx.spawn_in(window, async move |workspace, cx| {
3169 let buffer = create.await?;
3170 workspace.update_in(cx, |workspace, window, cx| {
3171 let editor =
3172 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3173 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3174 editor
3175 })
3176 })
3177 }
3178
3179 fn new_file_vertical(
3180 workspace: &mut Workspace,
3181 _: &workspace::NewFileSplitVertical,
3182 window: &mut Window,
3183 cx: &mut Context<Workspace>,
3184 ) {
3185 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3186 }
3187
3188 fn new_file_horizontal(
3189 workspace: &mut Workspace,
3190 _: &workspace::NewFileSplitHorizontal,
3191 window: &mut Window,
3192 cx: &mut Context<Workspace>,
3193 ) {
3194 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3195 }
3196
3197 fn new_file_split(
3198 workspace: &mut Workspace,
3199 action: &workspace::NewFileSplit,
3200 window: &mut Window,
3201 cx: &mut Context<Workspace>,
3202 ) {
3203 Self::new_file_in_direction(workspace, action.0, window, cx)
3204 }
3205
3206 fn new_file_in_direction(
3207 workspace: &mut Workspace,
3208 direction: SplitDirection,
3209 window: &mut Window,
3210 cx: &mut Context<Workspace>,
3211 ) {
3212 let project = workspace.project().clone();
3213 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3214
3215 cx.spawn_in(window, async move |workspace, cx| {
3216 let buffer = create.await?;
3217 workspace.update_in(cx, move |workspace, window, cx| {
3218 workspace.split_item(
3219 direction,
3220 Box::new(
3221 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3222 ),
3223 window,
3224 cx,
3225 )
3226 })?;
3227 anyhow::Ok(())
3228 })
3229 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3230 match e.error_code() {
3231 ErrorCode::RemoteUpgradeRequired => Some(format!(
3232 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3233 e.error_tag("required").unwrap_or("the latest version")
3234 )),
3235 _ => None,
3236 }
3237 });
3238 }
3239
3240 pub fn leader_id(&self) -> Option<CollaboratorId> {
3241 self.leader_id
3242 }
3243
3244 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3245 &self.buffer
3246 }
3247
3248 pub fn project(&self) -> Option<&Entity<Project>> {
3249 self.project.as_ref()
3250 }
3251
3252 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3253 self.workspace.as_ref()?.0.upgrade()
3254 }
3255
3256 /// Detaches a task and shows an error notification in the workspace if available,
3257 /// otherwise just logs the error.
3258 pub fn detach_and_notify_err<R, E>(
3259 &self,
3260 task: Task<Result<R, E>>,
3261 window: &mut Window,
3262 cx: &mut App,
3263 ) where
3264 E: std::fmt::Debug + std::fmt::Display + 'static,
3265 R: 'static,
3266 {
3267 if let Some(workspace) = self.workspace() {
3268 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3269 } else {
3270 task.detach_and_log_err(cx);
3271 }
3272 }
3273
3274 /// Returns the workspace serialization ID if this editor should be serialized.
3275 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3276 self.workspace
3277 .as_ref()
3278 .filter(|_| self.should_serialize_buffer())
3279 .and_then(|workspace| workspace.1)
3280 }
3281
3282 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3283 self.buffer().read(cx).title(cx)
3284 }
3285
3286 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3287 let git_blame_gutter_max_author_length = self
3288 .render_git_blame_gutter(cx)
3289 .then(|| {
3290 if let Some(blame) = self.blame.as_ref() {
3291 let max_author_length =
3292 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3293 Some(max_author_length)
3294 } else {
3295 None
3296 }
3297 })
3298 .flatten();
3299
3300 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3301
3302 EditorSnapshot {
3303 mode: self.mode.clone(),
3304 show_gutter: self.show_gutter,
3305 offset_content: self.offset_content,
3306 show_line_numbers: self.show_line_numbers,
3307 number_deleted_lines: self.number_deleted_lines,
3308 show_git_diff_gutter: self.show_git_diff_gutter,
3309 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3310 show_code_actions: self.show_code_actions,
3311 show_runnables: self.show_runnables,
3312 show_breakpoints: self.show_breakpoints,
3313 git_blame_gutter_max_author_length,
3314 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3315 display_snapshot,
3316 placeholder_display_snapshot: self
3317 .placeholder_display_map
3318 .as_ref()
3319 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3320 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3321 is_focused: self.focus_handle.is_focused(window),
3322 current_line_highlight: self
3323 .current_line_highlight
3324 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3325 gutter_hovered: self.gutter_hovered,
3326 }
3327 }
3328
3329 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3330 self.buffer.read(cx).language_at(point, cx)
3331 }
3332
3333 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3334 self.buffer.read(cx).read(cx).file_at(point).cloned()
3335 }
3336
3337 pub fn active_buffer(&self, cx: &App) -> Option<Entity<Buffer>> {
3338 let multibuffer = self.buffer.read(cx);
3339 let snapshot = multibuffer.snapshot(cx);
3340 let (anchor, _) =
3341 snapshot.anchor_to_buffer_anchor(self.selections.newest_anchor().head())?;
3342 multibuffer.buffer(anchor.buffer_id)
3343 }
3344
3345 pub fn mode(&self) -> &EditorMode {
3346 &self.mode
3347 }
3348
3349 pub fn set_mode(&mut self, mode: EditorMode) {
3350 self.mode = mode;
3351 }
3352
3353 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3354 self.collaboration_hub.as_deref()
3355 }
3356
3357 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3358 self.collaboration_hub = Some(hub);
3359 }
3360
3361 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3362 self.in_project_search = in_project_search;
3363 }
3364
3365 pub fn set_custom_context_menu(
3366 &mut self,
3367 f: impl 'static
3368 + Fn(
3369 &mut Self,
3370 DisplayPoint,
3371 &mut Window,
3372 &mut Context<Self>,
3373 ) -> Option<Entity<ui::ContextMenu>>,
3374 ) {
3375 self.custom_context_menu = Some(Box::new(f))
3376 }
3377
3378 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3379 self.completion_provider = provider;
3380 }
3381
3382 #[cfg(any(test, feature = "test-support"))]
3383 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3384 self.completion_provider.clone()
3385 }
3386
3387 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3388 self.semantics_provider.clone()
3389 }
3390
3391 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3392 self.semantics_provider = provider;
3393 }
3394
3395 pub fn set_edit_prediction_provider<T>(
3396 &mut self,
3397 provider: Option<Entity<T>>,
3398 window: &mut Window,
3399 cx: &mut Context<Self>,
3400 ) where
3401 T: EditPredictionDelegate,
3402 {
3403 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3404 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3405 if this.focus_handle.is_focused(window) {
3406 this.update_visible_edit_prediction(window, cx);
3407 }
3408 }),
3409 provider: Arc::new(provider),
3410 });
3411 self.update_edit_prediction_settings(cx);
3412 self.refresh_edit_prediction(false, false, window, cx);
3413 }
3414
3415 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3416 self.placeholder_display_map
3417 .as_ref()
3418 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3419 }
3420
3421 pub fn set_placeholder_text(
3422 &mut self,
3423 placeholder_text: &str,
3424 window: &mut Window,
3425 cx: &mut Context<Self>,
3426 ) {
3427 let multibuffer = cx
3428 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3429
3430 let style = window.text_style();
3431
3432 self.placeholder_display_map = Some(cx.new(|cx| {
3433 DisplayMap::new(
3434 multibuffer,
3435 style.font(),
3436 style.font_size.to_pixels(window.rem_size()),
3437 None,
3438 FILE_HEADER_HEIGHT,
3439 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3440 Default::default(),
3441 DiagnosticSeverity::Off,
3442 cx,
3443 )
3444 }));
3445 cx.notify();
3446 }
3447
3448 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3449 self.cursor_shape = cursor_shape;
3450
3451 // Disrupt blink for immediate user feedback that the cursor shape has changed
3452 self.blink_manager.update(cx, BlinkManager::show_cursor);
3453
3454 cx.notify();
3455 }
3456
3457 pub fn cursor_shape(&self) -> CursorShape {
3458 self.cursor_shape
3459 }
3460
3461 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3462 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3463 }
3464
3465 pub fn set_current_line_highlight(
3466 &mut self,
3467 current_line_highlight: Option<CurrentLineHighlight>,
3468 ) {
3469 self.current_line_highlight = current_line_highlight;
3470 }
3471
3472 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3473 self.collapse_matches = collapse_matches;
3474 }
3475
3476 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3477 if self.collapse_matches {
3478 return range.start..range.start;
3479 }
3480 range.clone()
3481 }
3482
3483 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3484 self.display_map.read(cx).clip_at_line_ends
3485 }
3486
3487 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3488 if self.display_map.read(cx).clip_at_line_ends != clip {
3489 self.display_map
3490 .update(cx, |map, _| map.clip_at_line_ends = clip);
3491 }
3492 }
3493
3494 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3495 self.input_enabled = input_enabled;
3496 }
3497
3498 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3499 self.expects_character_input = expects_character_input;
3500 }
3501
3502 pub fn set_edit_predictions_hidden_for_vim_mode(
3503 &mut self,
3504 hidden: bool,
3505 window: &mut Window,
3506 cx: &mut Context<Self>,
3507 ) {
3508 if hidden != self.edit_predictions_hidden_for_vim_mode {
3509 self.edit_predictions_hidden_for_vim_mode = hidden;
3510 if hidden {
3511 self.update_visible_edit_prediction(window, cx);
3512 } else {
3513 self.refresh_edit_prediction(true, false, window, cx);
3514 }
3515 }
3516 }
3517
3518 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3519 self.menu_edit_predictions_policy = value;
3520 }
3521
3522 pub fn set_autoindent(&mut self, autoindent: bool) {
3523 if autoindent {
3524 self.autoindent_mode = Some(AutoindentMode::EachLine);
3525 } else {
3526 self.autoindent_mode = None;
3527 }
3528 }
3529
3530 pub fn capability(&self, cx: &App) -> Capability {
3531 if self.read_only {
3532 Capability::ReadOnly
3533 } else {
3534 self.buffer.read(cx).capability()
3535 }
3536 }
3537
3538 pub fn read_only(&self, cx: &App) -> bool {
3539 self.read_only || self.buffer.read(cx).read_only()
3540 }
3541
3542 pub fn set_read_only(&mut self, read_only: bool) {
3543 self.read_only = read_only;
3544 }
3545
3546 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3547 self.use_autoclose = autoclose;
3548 }
3549
3550 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3551 self.use_auto_surround = auto_surround;
3552 }
3553
3554 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3555 self.auto_replace_emoji_shortcode = auto_replace;
3556 }
3557
3558 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3559 self.buffer_serialization = should_serialize.then(|| {
3560 BufferSerialization::new(
3561 ProjectSettings::get_global(cx)
3562 .session
3563 .restore_unsaved_buffers,
3564 )
3565 })
3566 }
3567
3568 fn should_serialize_buffer(&self) -> bool {
3569 self.buffer_serialization.is_some()
3570 }
3571
3572 pub fn toggle_edit_predictions(
3573 &mut self,
3574 _: &ToggleEditPrediction,
3575 window: &mut Window,
3576 cx: &mut Context<Self>,
3577 ) {
3578 if self.show_edit_predictions_override.is_some() {
3579 self.set_show_edit_predictions(None, window, cx);
3580 } else {
3581 let show_edit_predictions = !self.edit_predictions_enabled();
3582 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3583 }
3584 }
3585
3586 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3587 self.show_completions_on_input_override = show_completions_on_input;
3588 }
3589
3590 pub fn set_show_edit_predictions(
3591 &mut self,
3592 show_edit_predictions: Option<bool>,
3593 window: &mut Window,
3594 cx: &mut Context<Self>,
3595 ) {
3596 self.show_edit_predictions_override = show_edit_predictions;
3597 self.update_edit_prediction_settings(cx);
3598
3599 if let Some(false) = show_edit_predictions {
3600 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3601 } else {
3602 self.refresh_edit_prediction(false, true, window, cx);
3603 }
3604 }
3605
3606 fn edit_predictions_disabled_in_scope(
3607 &self,
3608 buffer: &Entity<Buffer>,
3609 buffer_position: language::Anchor,
3610 cx: &App,
3611 ) -> bool {
3612 let snapshot = buffer.read(cx).snapshot();
3613 let settings = snapshot.settings_at(buffer_position, cx);
3614
3615 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3616 return false;
3617 };
3618
3619 scope.override_name().is_some_and(|scope_name| {
3620 settings
3621 .edit_predictions_disabled_in
3622 .iter()
3623 .any(|s| s == scope_name)
3624 })
3625 }
3626
3627 pub fn set_use_modal_editing(&mut self, to: bool) {
3628 self.use_modal_editing = to;
3629 }
3630
3631 pub fn use_modal_editing(&self) -> bool {
3632 self.use_modal_editing
3633 }
3634
3635 fn selections_did_change(
3636 &mut self,
3637 local: bool,
3638 old_cursor_position: &Anchor,
3639 effects: SelectionEffects,
3640 window: &mut Window,
3641 cx: &mut Context<Self>,
3642 ) {
3643 window.invalidate_character_coordinates();
3644
3645 // Copy selections to primary selection buffer
3646 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3647 if local {
3648 let selections = self
3649 .selections
3650 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3651 let buffer_handle = self.buffer.read(cx).read(cx);
3652
3653 let mut text = String::new();
3654 for (index, selection) in selections.iter().enumerate() {
3655 let text_for_selection = buffer_handle
3656 .text_for_range(selection.start..selection.end)
3657 .collect::<String>();
3658
3659 text.push_str(&text_for_selection);
3660 if index != selections.len() - 1 {
3661 text.push('\n');
3662 }
3663 }
3664
3665 if !text.is_empty() {
3666 cx.write_to_primary(ClipboardItem::new_string(text));
3667 }
3668 }
3669
3670 let selection_anchors = self.selections.disjoint_anchors_arc();
3671
3672 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3673 self.buffer.update(cx, |buffer, cx| {
3674 buffer.set_active_selections(
3675 &selection_anchors,
3676 self.selections.line_mode(),
3677 self.cursor_shape,
3678 cx,
3679 )
3680 });
3681 }
3682 let display_map = self
3683 .display_map
3684 .update(cx, |display_map, cx| display_map.snapshot(cx));
3685 let buffer = display_map.buffer_snapshot();
3686 if self.selections.count() == 1 {
3687 self.add_selections_state = None;
3688 }
3689 self.select_next_state = None;
3690 self.select_prev_state = None;
3691 self.select_syntax_node_history.try_clear();
3692 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3693 self.snippet_stack.invalidate(&selection_anchors, buffer);
3694 self.take_rename(false, window, cx);
3695
3696 let newest_selection = self.selections.newest_anchor();
3697 let new_cursor_position = newest_selection.head();
3698 let selection_start = newest_selection.start;
3699
3700 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3701 self.push_to_nav_history(
3702 *old_cursor_position,
3703 Some(new_cursor_position.to_point(buffer)),
3704 false,
3705 effects.nav_history == Some(true),
3706 cx,
3707 );
3708 }
3709
3710 if local {
3711 if let Some((anchor, _)) = buffer.anchor_to_buffer_anchor(new_cursor_position) {
3712 self.register_buffer(anchor.buffer_id, cx);
3713 }
3714
3715 let mut context_menu = self.context_menu.borrow_mut();
3716 let completion_menu = match context_menu.as_ref() {
3717 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3718 Some(CodeContextMenu::CodeActions(_)) => {
3719 *context_menu = None;
3720 None
3721 }
3722 None => None,
3723 };
3724 let completion_position = completion_menu.map(|menu| menu.initial_position);
3725 drop(context_menu);
3726
3727 if effects.completions
3728 && let Some(completion_position) = completion_position
3729 {
3730 let start_offset = selection_start.to_offset(buffer);
3731 let position_matches = start_offset == completion_position.to_offset(buffer);
3732 let continue_showing = if let Some((snap, ..)) =
3733 buffer.point_to_buffer_offset(completion_position)
3734 && !snap.capability.editable()
3735 {
3736 false
3737 } else if position_matches {
3738 if self.snippet_stack.is_empty() {
3739 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3740 == Some(CharKind::Word)
3741 } else {
3742 // Snippet choices can be shown even when the cursor is in whitespace.
3743 // Dismissing the menu with actions like backspace is handled by
3744 // invalidation regions.
3745 true
3746 }
3747 } else {
3748 false
3749 };
3750
3751 if continue_showing {
3752 self.open_or_update_completions_menu(None, None, false, window, cx);
3753 } else {
3754 self.hide_context_menu(window, cx);
3755 }
3756 }
3757
3758 hide_hover(self, cx);
3759
3760 if old_cursor_position.to_display_point(&display_map).row()
3761 != new_cursor_position.to_display_point(&display_map).row()
3762 {
3763 self.available_code_actions.take();
3764 }
3765 self.refresh_code_actions(window, cx);
3766 self.refresh_document_highlights(cx);
3767 refresh_linked_ranges(self, window, cx);
3768
3769 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3770 self.refresh_matching_bracket_highlights(&display_map, cx);
3771 self.refresh_outline_symbols_at_cursor(cx);
3772 self.update_visible_edit_prediction(window, cx);
3773 self.inline_blame_popover.take();
3774 if self.git_blame_inline_enabled {
3775 self.start_inline_blame_timer(window, cx);
3776 }
3777 }
3778
3779 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3780
3781 if local && !self.suppress_selection_callback {
3782 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3783 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3784 callback(cursor_position, window, cx);
3785 }
3786 }
3787
3788 cx.emit(EditorEvent::SelectionsChanged { local });
3789
3790 let selections = &self.selections.disjoint_anchors_arc();
3791 if selections.len() == 1 {
3792 cx.emit(SearchEvent::ActiveMatchChanged)
3793 }
3794 if local && let Some(buffer_snapshot) = buffer.as_singleton() {
3795 let inmemory_selections = selections
3796 .iter()
3797 .map(|s| {
3798 let start = s.range().start.text_anchor_in(buffer_snapshot);
3799 let end = s.range().end.text_anchor_in(buffer_snapshot);
3800 (start..end).to_point(buffer_snapshot)
3801 })
3802 .collect();
3803 self.update_restoration_data(cx, |data| {
3804 data.selections = inmemory_selections;
3805 });
3806
3807 if WorkspaceSettings::get(None, cx).restore_on_startup
3808 != RestoreOnStartupBehavior::EmptyTab
3809 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3810 {
3811 let snapshot = self.buffer().read(cx).snapshot(cx);
3812 let selections = selections.clone();
3813 let background_executor = cx.background_executor().clone();
3814 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3815 let db = EditorDb::global(cx);
3816 self.serialize_selections = cx.background_spawn(async move {
3817 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3818 let db_selections = selections
3819 .iter()
3820 .map(|selection| {
3821 (
3822 selection.start.to_offset(&snapshot).0,
3823 selection.end.to_offset(&snapshot).0,
3824 )
3825 })
3826 .collect();
3827
3828 db.save_editor_selections(editor_id, workspace_id, db_selections)
3829 .await
3830 .with_context(|| {
3831 format!(
3832 "persisting editor selections for editor {editor_id}, \
3833 workspace {workspace_id:?}"
3834 )
3835 })
3836 .log_err();
3837 });
3838 }
3839 }
3840
3841 cx.notify();
3842 }
3843
3844 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3845 use text::ToOffset as _;
3846
3847 if self.mode.is_minimap()
3848 || WorkspaceSettings::get(None, cx).restore_on_startup
3849 == RestoreOnStartupBehavior::EmptyTab
3850 {
3851 return;
3852 }
3853
3854 let display_snapshot = self
3855 .display_map
3856 .update(cx, |display_map, cx| display_map.snapshot(cx));
3857 let Some(buffer_snapshot) = display_snapshot.buffer_snapshot().as_singleton() else {
3858 return;
3859 };
3860 let inmemory_folds = display_snapshot
3861 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3862 .map(|fold| {
3863 let start = fold.range.start.text_anchor_in(buffer_snapshot);
3864 let end = fold.range.end.text_anchor_in(buffer_snapshot);
3865 (start..end).to_point(buffer_snapshot)
3866 })
3867 .collect();
3868 self.update_restoration_data(cx, |data| {
3869 data.folds = inmemory_folds;
3870 });
3871
3872 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3873 return;
3874 };
3875
3876 // Get file path for path-based fold storage (survives tab close)
3877 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3878 project::File::from_dyn(buffer.read(cx).file())
3879 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3880 }) else {
3881 return;
3882 };
3883
3884 let background_executor = cx.background_executor().clone();
3885 const FINGERPRINT_LEN: usize = 32;
3886 let db_folds = display_snapshot
3887 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3888 .map(|fold| {
3889 let start = fold
3890 .range
3891 .start
3892 .text_anchor_in(buffer_snapshot)
3893 .to_offset(buffer_snapshot);
3894 let end = fold
3895 .range
3896 .end
3897 .text_anchor_in(buffer_snapshot)
3898 .to_offset(buffer_snapshot);
3899
3900 // Extract fingerprints - content at fold boundaries for validation on restore
3901 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3902 // content that might change independently.
3903 // start_fp: first min(32, fold_len) bytes of fold content
3904 // end_fp: last min(32, fold_len) bytes of fold content
3905 // Clip to character boundaries to handle multibyte UTF-8 characters.
3906 let fold_len = end - start;
3907 let start_fp_end = buffer_snapshot
3908 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3909 let start_fp: String = buffer_snapshot
3910 .text_for_range(start..start_fp_end)
3911 .collect();
3912 let end_fp_start = buffer_snapshot
3913 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3914 let end_fp: String = buffer_snapshot.text_for_range(end_fp_start..end).collect();
3915
3916 (start, end, start_fp, end_fp)
3917 })
3918 .collect::<Vec<_>>();
3919 let db = EditorDb::global(cx);
3920 self.serialize_folds = cx.background_spawn(async move {
3921 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3922 if db_folds.is_empty() {
3923 // No folds - delete any persisted folds for this file
3924 db.delete_file_folds(workspace_id, file_path)
3925 .await
3926 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3927 .log_err();
3928 } else {
3929 db.save_file_folds(workspace_id, file_path, db_folds)
3930 .await
3931 .with_context(|| {
3932 format!("persisting file folds for workspace {workspace_id:?}")
3933 })
3934 .log_err();
3935 }
3936 });
3937 }
3938
3939 pub fn sync_selections(
3940 &mut self,
3941 other: Entity<Editor>,
3942 cx: &mut Context<Self>,
3943 ) -> gpui::Subscription {
3944 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3945 if !other_selections.is_empty() {
3946 self.selections
3947 .change_with(&self.display_snapshot(cx), |selections| {
3948 selections.select_anchors(other_selections);
3949 });
3950 }
3951
3952 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3953 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3954 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3955 if other_selections.is_empty() {
3956 return;
3957 }
3958 let snapshot = this.display_snapshot(cx);
3959 this.selections.change_with(&snapshot, |selections| {
3960 selections.select_anchors(other_selections);
3961 });
3962 }
3963 });
3964
3965 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3966 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3967 let these_selections = this.selections.disjoint_anchors().to_vec();
3968 if these_selections.is_empty() {
3969 return;
3970 }
3971 other.update(cx, |other_editor, cx| {
3972 let snapshot = other_editor.display_snapshot(cx);
3973 other_editor
3974 .selections
3975 .change_with(&snapshot, |selections| {
3976 selections.select_anchors(these_selections);
3977 })
3978 });
3979 }
3980 });
3981
3982 Subscription::join(other_subscription, this_subscription)
3983 }
3984
3985 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3986 if self.buffer().read(cx).is_singleton() {
3987 return;
3988 }
3989 let snapshot = self.buffer.read(cx).snapshot(cx);
3990 let buffer_ids: HashSet<BufferId> = self
3991 .selections
3992 .disjoint_anchor_ranges()
3993 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3994 .collect();
3995 for buffer_id in buffer_ids {
3996 self.unfold_buffer(buffer_id, cx);
3997 }
3998 }
3999
4000 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
4001 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
4002 /// effects of selection change occur at the end of the transaction.
4003 pub fn change_selections<R>(
4004 &mut self,
4005 effects: SelectionEffects,
4006 window: &mut Window,
4007 cx: &mut Context<Self>,
4008 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
4009 ) -> R {
4010 let snapshot = self.display_snapshot(cx);
4011 if let Some(state) = &mut self.deferred_selection_effects_state {
4012 state.effects.scroll = effects.scroll.or(state.effects.scroll);
4013 state.effects.completions = effects.completions;
4014 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
4015 let (changed, result) = self.selections.change_with(&snapshot, change);
4016 state.changed |= changed;
4017 return result;
4018 }
4019 let mut state = DeferredSelectionEffectsState {
4020 changed: false,
4021 effects,
4022 old_cursor_position: self.selections.newest_anchor().head(),
4023 history_entry: SelectionHistoryEntry {
4024 selections: self.selections.disjoint_anchors_arc(),
4025 select_next_state: self.select_next_state.clone(),
4026 select_prev_state: self.select_prev_state.clone(),
4027 add_selections_state: self.add_selections_state.clone(),
4028 },
4029 };
4030 let (changed, result) = self.selections.change_with(&snapshot, change);
4031 state.changed = state.changed || changed;
4032 if self.defer_selection_effects {
4033 self.deferred_selection_effects_state = Some(state);
4034 } else {
4035 self.apply_selection_effects(state, window, cx);
4036 }
4037 result
4038 }
4039
4040 /// Defers the effects of selection change, so that the effects of multiple calls to
4041 /// `change_selections` are applied at the end. This way these intermediate states aren't added
4042 /// to selection history and the state of popovers based on selection position aren't
4043 /// erroneously updated.
4044 pub fn with_selection_effects_deferred<R>(
4045 &mut self,
4046 window: &mut Window,
4047 cx: &mut Context<Self>,
4048 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
4049 ) -> R {
4050 let already_deferred = self.defer_selection_effects;
4051 self.defer_selection_effects = true;
4052 let result = update(self, window, cx);
4053 if !already_deferred {
4054 self.defer_selection_effects = false;
4055 if let Some(state) = self.deferred_selection_effects_state.take() {
4056 self.apply_selection_effects(state, window, cx);
4057 }
4058 }
4059 result
4060 }
4061
4062 fn apply_selection_effects(
4063 &mut self,
4064 state: DeferredSelectionEffectsState,
4065 window: &mut Window,
4066 cx: &mut Context<Self>,
4067 ) {
4068 if state.changed {
4069 self.selection_history.push(state.history_entry);
4070
4071 if let Some(autoscroll) = state.effects.scroll {
4072 self.request_autoscroll(autoscroll, cx);
4073 }
4074
4075 let old_cursor_position = &state.old_cursor_position;
4076
4077 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
4078
4079 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
4080 self.show_signature_help_auto(window, cx);
4081 }
4082 }
4083 }
4084
4085 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4086 where
4087 I: IntoIterator<Item = (Range<S>, T)>,
4088 S: ToOffset,
4089 T: Into<Arc<str>>,
4090 {
4091 if self.read_only(cx) {
4092 return;
4093 }
4094
4095 self.buffer
4096 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
4097 }
4098
4099 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4100 where
4101 I: IntoIterator<Item = (Range<S>, T)>,
4102 S: ToOffset,
4103 T: Into<Arc<str>>,
4104 {
4105 if self.read_only(cx) {
4106 return;
4107 }
4108
4109 self.buffer.update(cx, |buffer, cx| {
4110 buffer.edit(edits, self.autoindent_mode.clone(), cx)
4111 });
4112 }
4113
4114 pub fn edit_with_block_indent<I, S, T>(
4115 &mut self,
4116 edits: I,
4117 original_indent_columns: Vec<Option<u32>>,
4118 cx: &mut Context<Self>,
4119 ) where
4120 I: IntoIterator<Item = (Range<S>, T)>,
4121 S: ToOffset,
4122 T: Into<Arc<str>>,
4123 {
4124 if self.read_only(cx) {
4125 return;
4126 }
4127
4128 self.buffer.update(cx, |buffer, cx| {
4129 buffer.edit(
4130 edits,
4131 Some(AutoindentMode::Block {
4132 original_indent_columns,
4133 }),
4134 cx,
4135 )
4136 });
4137 }
4138
4139 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
4140 self.hide_context_menu(window, cx);
4141
4142 match phase {
4143 SelectPhase::Begin {
4144 position,
4145 add,
4146 click_count,
4147 } => self.begin_selection(position, add, click_count, window, cx),
4148 SelectPhase::BeginColumnar {
4149 position,
4150 goal_column,
4151 reset,
4152 mode,
4153 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4154 SelectPhase::Extend {
4155 position,
4156 click_count,
4157 } => self.extend_selection(position, click_count, window, cx),
4158 SelectPhase::Update {
4159 position,
4160 goal_column,
4161 scroll_delta,
4162 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4163 SelectPhase::End => self.end_selection(window, cx),
4164 }
4165 }
4166
4167 fn extend_selection(
4168 &mut self,
4169 position: DisplayPoint,
4170 click_count: usize,
4171 window: &mut Window,
4172 cx: &mut Context<Self>,
4173 ) {
4174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4175 let tail = self
4176 .selections
4177 .newest::<MultiBufferOffset>(&display_map)
4178 .tail();
4179 let click_count = click_count.max(match self.selections.select_mode() {
4180 SelectMode::Character => 1,
4181 SelectMode::Word(_) => 2,
4182 SelectMode::Line(_) => 3,
4183 SelectMode::All => 4,
4184 });
4185 self.begin_selection(position, false, click_count, window, cx);
4186
4187 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4188
4189 let current_selection = match self.selections.select_mode() {
4190 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4191 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4192 };
4193
4194 let mut pending_selection = self
4195 .selections
4196 .pending_anchor()
4197 .cloned()
4198 .expect("extend_selection not called with pending selection");
4199
4200 if pending_selection
4201 .start
4202 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4203 == Ordering::Greater
4204 {
4205 pending_selection.start = current_selection.start;
4206 }
4207 if pending_selection
4208 .end
4209 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4210 == Ordering::Less
4211 {
4212 pending_selection.end = current_selection.end;
4213 pending_selection.reversed = true;
4214 }
4215
4216 let mut pending_mode = self.selections.pending_mode().unwrap();
4217 match &mut pending_mode {
4218 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4219 _ => {}
4220 }
4221
4222 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4223 SelectionEffects::scroll(Autoscroll::fit())
4224 } else {
4225 SelectionEffects::no_scroll()
4226 };
4227
4228 self.change_selections(effects, window, cx, |s| {
4229 s.set_pending(pending_selection.clone(), pending_mode);
4230 s.set_is_extending(true);
4231 });
4232 }
4233
4234 fn begin_selection(
4235 &mut self,
4236 position: DisplayPoint,
4237 add: bool,
4238 click_count: usize,
4239 window: &mut Window,
4240 cx: &mut Context<Self>,
4241 ) {
4242 if !self.focus_handle.is_focused(window) {
4243 self.last_focused_descendant = None;
4244 window.focus(&self.focus_handle, cx);
4245 }
4246
4247 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4248 let buffer = display_map.buffer_snapshot();
4249 let position = display_map.clip_point(position, Bias::Left);
4250
4251 let start;
4252 let end;
4253 let mode;
4254 let mut auto_scroll;
4255 match click_count {
4256 1 => {
4257 start = buffer.anchor_before(position.to_point(&display_map));
4258 end = start;
4259 mode = SelectMode::Character;
4260 auto_scroll = true;
4261 }
4262 2 => {
4263 let position = display_map
4264 .clip_point(position, Bias::Left)
4265 .to_offset(&display_map, Bias::Left);
4266 let (range, _) = buffer.surrounding_word(position, None);
4267 start = buffer.anchor_before(range.start);
4268 end = buffer.anchor_before(range.end);
4269 mode = SelectMode::Word(start..end);
4270 auto_scroll = true;
4271 }
4272 3 => {
4273 let position = display_map
4274 .clip_point(position, Bias::Left)
4275 .to_point(&display_map);
4276 let line_start = display_map.prev_line_boundary(position).0;
4277 let next_line_start = buffer.clip_point(
4278 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4279 Bias::Left,
4280 );
4281 start = buffer.anchor_before(line_start);
4282 end = buffer.anchor_before(next_line_start);
4283 mode = SelectMode::Line(start..end);
4284 auto_scroll = true;
4285 }
4286 _ => {
4287 start = buffer.anchor_before(MultiBufferOffset(0));
4288 end = buffer.anchor_before(buffer.len());
4289 mode = SelectMode::All;
4290 auto_scroll = false;
4291 }
4292 }
4293 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4294
4295 let point_to_delete: Option<usize> = {
4296 let selected_points: Vec<Selection<Point>> =
4297 self.selections.disjoint_in_range(start..end, &display_map);
4298
4299 if !add || click_count > 1 {
4300 None
4301 } else if !selected_points.is_empty() {
4302 Some(selected_points[0].id)
4303 } else {
4304 let clicked_point_already_selected =
4305 self.selections.disjoint_anchors().iter().find(|selection| {
4306 selection.start.to_point(buffer) == start.to_point(buffer)
4307 || selection.end.to_point(buffer) == end.to_point(buffer)
4308 });
4309
4310 clicked_point_already_selected.map(|selection| selection.id)
4311 }
4312 };
4313
4314 let selections_count = self.selections.count();
4315 let effects = if auto_scroll {
4316 SelectionEffects::default()
4317 } else {
4318 SelectionEffects::no_scroll()
4319 };
4320
4321 self.change_selections(effects, window, cx, |s| {
4322 if let Some(point_to_delete) = point_to_delete {
4323 s.delete(point_to_delete);
4324
4325 if selections_count == 1 {
4326 s.set_pending_anchor_range(start..end, mode);
4327 }
4328 } else {
4329 if !add {
4330 s.clear_disjoint();
4331 }
4332
4333 s.set_pending_anchor_range(start..end, mode);
4334 }
4335 });
4336 }
4337
4338 fn begin_columnar_selection(
4339 &mut self,
4340 position: DisplayPoint,
4341 goal_column: u32,
4342 reset: bool,
4343 mode: ColumnarMode,
4344 window: &mut Window,
4345 cx: &mut Context<Self>,
4346 ) {
4347 if !self.focus_handle.is_focused(window) {
4348 self.last_focused_descendant = None;
4349 window.focus(&self.focus_handle, cx);
4350 }
4351
4352 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4353
4354 if reset {
4355 let pointer_position = display_map
4356 .buffer_snapshot()
4357 .anchor_before(position.to_point(&display_map));
4358
4359 self.change_selections(
4360 SelectionEffects::scroll(Autoscroll::newest()),
4361 window,
4362 cx,
4363 |s| {
4364 s.clear_disjoint();
4365 s.set_pending_anchor_range(
4366 pointer_position..pointer_position,
4367 SelectMode::Character,
4368 );
4369 },
4370 );
4371 };
4372
4373 let tail = self.selections.newest::<Point>(&display_map).tail();
4374 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4375 self.columnar_selection_state = match mode {
4376 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4377 selection_tail: selection_anchor,
4378 display_point: if reset {
4379 if position.column() != goal_column {
4380 Some(DisplayPoint::new(position.row(), goal_column))
4381 } else {
4382 None
4383 }
4384 } else {
4385 None
4386 },
4387 }),
4388 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4389 selection_tail: selection_anchor,
4390 }),
4391 };
4392
4393 if !reset {
4394 self.select_columns(position, goal_column, &display_map, window, cx);
4395 }
4396 }
4397
4398 fn update_selection(
4399 &mut self,
4400 position: DisplayPoint,
4401 goal_column: u32,
4402 scroll_delta: gpui::Point<f32>,
4403 window: &mut Window,
4404 cx: &mut Context<Self>,
4405 ) {
4406 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4407
4408 if self.columnar_selection_state.is_some() {
4409 self.select_columns(position, goal_column, &display_map, window, cx);
4410 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4411 let buffer = display_map.buffer_snapshot();
4412 let head;
4413 let tail;
4414 let mode = self.selections.pending_mode().unwrap();
4415 match &mode {
4416 SelectMode::Character => {
4417 head = position.to_point(&display_map);
4418 tail = pending.tail().to_point(buffer);
4419 }
4420 SelectMode::Word(original_range) => {
4421 let offset = display_map
4422 .clip_point(position, Bias::Left)
4423 .to_offset(&display_map, Bias::Left);
4424 let original_range = original_range.to_offset(buffer);
4425
4426 let head_offset = if buffer.is_inside_word(offset, None)
4427 || original_range.contains(&offset)
4428 {
4429 let (word_range, _) = buffer.surrounding_word(offset, None);
4430 if word_range.start < original_range.start {
4431 word_range.start
4432 } else {
4433 word_range.end
4434 }
4435 } else {
4436 offset
4437 };
4438
4439 head = head_offset.to_point(buffer);
4440 if head_offset <= original_range.start {
4441 tail = original_range.end.to_point(buffer);
4442 } else {
4443 tail = original_range.start.to_point(buffer);
4444 }
4445 }
4446 SelectMode::Line(original_range) => {
4447 let original_range = original_range.to_point(display_map.buffer_snapshot());
4448
4449 let position = display_map
4450 .clip_point(position, Bias::Left)
4451 .to_point(&display_map);
4452 let line_start = display_map.prev_line_boundary(position).0;
4453 let next_line_start = buffer.clip_point(
4454 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4455 Bias::Left,
4456 );
4457
4458 if line_start < original_range.start {
4459 head = line_start
4460 } else {
4461 head = next_line_start
4462 }
4463
4464 if head <= original_range.start {
4465 tail = original_range.end;
4466 } else {
4467 tail = original_range.start;
4468 }
4469 }
4470 SelectMode::All => {
4471 return;
4472 }
4473 };
4474
4475 if head < tail {
4476 pending.start = buffer.anchor_before(head);
4477 pending.end = buffer.anchor_before(tail);
4478 pending.reversed = true;
4479 } else {
4480 pending.start = buffer.anchor_before(tail);
4481 pending.end = buffer.anchor_before(head);
4482 pending.reversed = false;
4483 }
4484
4485 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4486 s.set_pending(pending.clone(), mode);
4487 });
4488 } else {
4489 log::error!("update_selection dispatched with no pending selection");
4490 return;
4491 }
4492
4493 self.apply_scroll_delta(scroll_delta, window, cx);
4494 cx.notify();
4495 }
4496
4497 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4498 self.columnar_selection_state.take();
4499 if let Some(pending_mode) = self.selections.pending_mode() {
4500 let selections = self
4501 .selections
4502 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4503 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4504 s.select(selections);
4505 s.clear_pending();
4506 if s.is_extending() {
4507 s.set_is_extending(false);
4508 } else {
4509 s.set_select_mode(pending_mode);
4510 }
4511 });
4512 }
4513 }
4514
4515 fn select_columns(
4516 &mut self,
4517 head: DisplayPoint,
4518 goal_column: u32,
4519 display_map: &DisplaySnapshot,
4520 window: &mut Window,
4521 cx: &mut Context<Self>,
4522 ) {
4523 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4524 return;
4525 };
4526
4527 let tail = match columnar_state {
4528 ColumnarSelectionState::FromMouse {
4529 selection_tail,
4530 display_point,
4531 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4532 ColumnarSelectionState::FromSelection { selection_tail } => {
4533 selection_tail.to_display_point(display_map)
4534 }
4535 };
4536
4537 let start_row = cmp::min(tail.row(), head.row());
4538 let end_row = cmp::max(tail.row(), head.row());
4539 let start_column = cmp::min(tail.column(), goal_column);
4540 let end_column = cmp::max(tail.column(), goal_column);
4541 let reversed = start_column < tail.column();
4542
4543 let selection_ranges = (start_row.0..=end_row.0)
4544 .map(DisplayRow)
4545 .filter_map(|row| {
4546 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4547 || start_column <= display_map.line_len(row))
4548 && !display_map.is_block_line(row)
4549 {
4550 let start = display_map
4551 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4552 .to_point(display_map);
4553 let end = display_map
4554 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4555 .to_point(display_map);
4556 if reversed {
4557 Some(end..start)
4558 } else {
4559 Some(start..end)
4560 }
4561 } else {
4562 None
4563 }
4564 })
4565 .collect::<Vec<_>>();
4566 if selection_ranges.is_empty() {
4567 return;
4568 }
4569
4570 let ranges = match columnar_state {
4571 ColumnarSelectionState::FromMouse { .. } => {
4572 let mut non_empty_ranges = selection_ranges
4573 .iter()
4574 .filter(|selection_range| selection_range.start != selection_range.end)
4575 .peekable();
4576 if non_empty_ranges.peek().is_some() {
4577 non_empty_ranges.cloned().collect()
4578 } else {
4579 selection_ranges
4580 }
4581 }
4582 _ => selection_ranges,
4583 };
4584
4585 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4586 s.select_ranges(ranges);
4587 });
4588 cx.notify();
4589 }
4590
4591 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4592 self.selections
4593 .all_adjusted(snapshot)
4594 .iter()
4595 .any(|selection| !selection.is_empty())
4596 }
4597
4598 pub fn has_pending_nonempty_selection(&self) -> bool {
4599 let pending_nonempty_selection = match self.selections.pending_anchor() {
4600 Some(Selection { start, end, .. }) => start != end,
4601 None => false,
4602 };
4603
4604 pending_nonempty_selection
4605 || (self.columnar_selection_state.is_some()
4606 && self.selections.disjoint_anchors().len() > 1)
4607 }
4608
4609 pub fn has_pending_selection(&self) -> bool {
4610 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4611 }
4612
4613 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4614 self.selection_mark_mode = false;
4615 self.selection_drag_state = SelectionDragState::None;
4616
4617 if self.dismiss_menus_and_popups(true, window, cx) {
4618 cx.notify();
4619 return;
4620 }
4621 if self.clear_expanded_diff_hunks(cx) {
4622 cx.notify();
4623 return;
4624 }
4625 if self.show_git_blame_gutter {
4626 self.show_git_blame_gutter = false;
4627 cx.notify();
4628 return;
4629 }
4630
4631 if self.mode.is_full()
4632 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4633 {
4634 cx.notify();
4635 return;
4636 }
4637
4638 cx.propagate();
4639 }
4640
4641 pub fn dismiss_menus_and_popups(
4642 &mut self,
4643 is_user_requested: bool,
4644 window: &mut Window,
4645 cx: &mut Context<Self>,
4646 ) -> bool {
4647 let mut dismissed = false;
4648
4649 dismissed |= self.take_rename(false, window, cx).is_some();
4650 dismissed |= self.hide_blame_popover(true, cx);
4651 dismissed |= hide_hover(self, cx);
4652 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4653 dismissed |= self.hide_context_menu(window, cx).is_some();
4654 dismissed |= self.mouse_context_menu.take().is_some();
4655 dismissed |= is_user_requested
4656 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4657 dismissed |= self.snippet_stack.pop().is_some();
4658 if self.diff_review_drag_state.is_some() {
4659 self.cancel_diff_review_drag(cx);
4660 dismissed = true;
4661 }
4662 if !self.diff_review_overlays.is_empty() {
4663 self.dismiss_all_diff_review_overlays(cx);
4664 dismissed = true;
4665 }
4666
4667 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4668 self.dismiss_diagnostics(cx);
4669 dismissed = true;
4670 }
4671
4672 dismissed
4673 }
4674
4675 fn linked_editing_ranges_for(
4676 &self,
4677 query_range: Range<text::Anchor>,
4678 cx: &App,
4679 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4680 use text::ToOffset as TO;
4681
4682 if self.linked_edit_ranges.is_empty() {
4683 return None;
4684 }
4685 if query_range.start.buffer_id != query_range.end.buffer_id {
4686 return None;
4687 };
4688 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
4689 let buffer = self.buffer.read(cx).buffer(query_range.end.buffer_id)?;
4690 let buffer_snapshot = buffer.read(cx).snapshot();
4691 let (base_range, linked_ranges) = self.linked_edit_ranges.get(
4692 buffer_snapshot.remote_id(),
4693 query_range.clone(),
4694 &buffer_snapshot,
4695 )?;
4696 // find offset from the start of current range to current cursor position
4697 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4698
4699 let start_offset = TO::to_offset(&query_range.start, &buffer_snapshot);
4700 let start_difference = start_offset - start_byte_offset;
4701 let end_offset = TO::to_offset(&query_range.end, &buffer_snapshot);
4702 let end_difference = end_offset - start_byte_offset;
4703
4704 // Current range has associated linked ranges.
4705 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4706 for range in linked_ranges.iter() {
4707 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4708 let end_offset = start_offset + end_difference;
4709 let start_offset = start_offset + start_difference;
4710 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4711 continue;
4712 }
4713 if self.selections.disjoint_anchor_ranges().any(|s| {
4714 let Some((selection_start, _)) =
4715 multibuffer_snapshot.anchor_to_buffer_anchor(s.start)
4716 else {
4717 return false;
4718 };
4719 let Some((selection_end, _)) = multibuffer_snapshot.anchor_to_buffer_anchor(s.end)
4720 else {
4721 return false;
4722 };
4723 if selection_start.buffer_id != query_range.start.buffer_id
4724 || selection_end.buffer_id != query_range.end.buffer_id
4725 {
4726 return false;
4727 }
4728 TO::to_offset(&selection_start, &buffer_snapshot) <= end_offset
4729 && TO::to_offset(&selection_end, &buffer_snapshot) >= start_offset
4730 }) {
4731 continue;
4732 }
4733 let start = buffer_snapshot.anchor_after(start_offset);
4734 let end = buffer_snapshot.anchor_after(end_offset);
4735 linked_edits
4736 .entry(buffer.clone())
4737 .or_default()
4738 .push(start..end);
4739 }
4740 Some(linked_edits)
4741 }
4742
4743 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4744 let text: Arc<str> = text.into();
4745
4746 if self.read_only(cx) {
4747 return;
4748 }
4749
4750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4751
4752 self.unfold_buffers_with_selections(cx);
4753
4754 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4755 let mut bracket_inserted = false;
4756 let mut edits = Vec::new();
4757 let mut linked_edits = LinkedEdits::new();
4758 let mut new_selections = Vec::with_capacity(selections.len());
4759 let mut new_autoclose_regions = Vec::new();
4760 let snapshot = self.buffer.read(cx).read(cx);
4761 let mut clear_linked_edit_ranges = false;
4762 let mut all_selections_read_only = true;
4763 let mut has_adjacent_edits = false;
4764 let mut in_adjacent_group = false;
4765
4766 let mut regions = self
4767 .selections_with_autoclose_regions(selections, &snapshot)
4768 .peekable();
4769
4770 while let Some((selection, autoclose_region)) = regions.next() {
4771 if snapshot
4772 .point_to_buffer_point(selection.head())
4773 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4774 {
4775 continue;
4776 }
4777 if snapshot
4778 .point_to_buffer_point(selection.tail())
4779 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4780 {
4781 // note, ideally we'd clip the tail to the closest writeable region towards the head
4782 continue;
4783 }
4784 all_selections_read_only = false;
4785
4786 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4787 // Determine if the inserted text matches the opening or closing
4788 // bracket of any of this language's bracket pairs.
4789 let mut bracket_pair = None;
4790 let mut is_bracket_pair_start = false;
4791 let mut is_bracket_pair_end = false;
4792 if !text.is_empty() {
4793 let mut bracket_pair_matching_end = None;
4794 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4795 // and they are removing the character that triggered IME popup.
4796 for (pair, enabled) in scope.brackets() {
4797 if !pair.close && !pair.surround {
4798 continue;
4799 }
4800
4801 if enabled && pair.start.ends_with(text.as_ref()) {
4802 let prefix_len = pair.start.len() - text.len();
4803 let preceding_text_matches_prefix = prefix_len == 0
4804 || (selection.start.column >= (prefix_len as u32)
4805 && snapshot.contains_str_at(
4806 Point::new(
4807 selection.start.row,
4808 selection.start.column - (prefix_len as u32),
4809 ),
4810 &pair.start[..prefix_len],
4811 ));
4812 if preceding_text_matches_prefix {
4813 bracket_pair = Some(pair.clone());
4814 is_bracket_pair_start = true;
4815 break;
4816 }
4817 }
4818 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4819 {
4820 // take first bracket pair matching end, but don't break in case a later bracket
4821 // pair matches start
4822 bracket_pair_matching_end = Some(pair.clone());
4823 }
4824 }
4825 if let Some(end) = bracket_pair_matching_end
4826 && bracket_pair.is_none()
4827 {
4828 bracket_pair = Some(end);
4829 is_bracket_pair_end = true;
4830 }
4831 }
4832
4833 if let Some(bracket_pair) = bracket_pair {
4834 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4835 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4836 let auto_surround =
4837 self.use_auto_surround && snapshot_settings.use_auto_surround;
4838 if selection.is_empty() {
4839 if is_bracket_pair_start {
4840 // If the inserted text is a suffix of an opening bracket and the
4841 // selection is preceded by the rest of the opening bracket, then
4842 // insert the closing bracket.
4843 let following_text_allows_autoclose = snapshot
4844 .chars_at(selection.start)
4845 .next()
4846 .is_none_or(|c| scope.should_autoclose_before(c));
4847
4848 let preceding_text_allows_autoclose = selection.start.column == 0
4849 || snapshot
4850 .reversed_chars_at(selection.start)
4851 .next()
4852 .is_none_or(|c| {
4853 bracket_pair.start != bracket_pair.end
4854 || !snapshot
4855 .char_classifier_at(selection.start)
4856 .is_word(c)
4857 });
4858
4859 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4860 && bracket_pair.start.len() == 1
4861 {
4862 let target = bracket_pair.start.chars().next().unwrap();
4863 let mut byte_offset = 0u32;
4864 let current_line_count = snapshot
4865 .reversed_chars_at(selection.start)
4866 .take_while(|&c| c != '\n')
4867 .filter(|c| {
4868 byte_offset += c.len_utf8() as u32;
4869 if *c != target {
4870 return false;
4871 }
4872
4873 let point = Point::new(
4874 selection.start.row,
4875 selection.start.column.saturating_sub(byte_offset),
4876 );
4877
4878 let is_enabled = snapshot
4879 .language_scope_at(point)
4880 .and_then(|scope| {
4881 scope
4882 .brackets()
4883 .find(|(pair, _)| {
4884 pair.start == bracket_pair.start
4885 })
4886 .map(|(_, enabled)| enabled)
4887 })
4888 .unwrap_or(true);
4889
4890 let is_delimiter = snapshot
4891 .language_scope_at(Point::new(
4892 point.row,
4893 point.column + 1,
4894 ))
4895 .and_then(|scope| {
4896 scope
4897 .brackets()
4898 .find(|(pair, _)| {
4899 pair.start == bracket_pair.start
4900 })
4901 .map(|(_, enabled)| !enabled)
4902 })
4903 .unwrap_or(false);
4904
4905 is_enabled && !is_delimiter
4906 })
4907 .count();
4908 current_line_count % 2 == 1
4909 } else {
4910 false
4911 };
4912
4913 if autoclose
4914 && bracket_pair.close
4915 && following_text_allows_autoclose
4916 && preceding_text_allows_autoclose
4917 && !is_closing_quote
4918 {
4919 let anchor = snapshot.anchor_before(selection.end);
4920 new_selections.push((selection.map(|_| anchor), text.len()));
4921 new_autoclose_regions.push((
4922 anchor,
4923 text.len(),
4924 selection.id,
4925 bracket_pair.clone(),
4926 ));
4927 edits.push((
4928 selection.range(),
4929 format!("{}{}", text, bracket_pair.end).into(),
4930 ));
4931 bracket_inserted = true;
4932 continue;
4933 }
4934 }
4935
4936 if let Some(region) = autoclose_region {
4937 // If the selection is followed by an auto-inserted closing bracket,
4938 // then don't insert that closing bracket again; just move the selection
4939 // past the closing bracket.
4940 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4941 && text.as_ref() == region.pair.end.as_str()
4942 && snapshot.contains_str_at(region.range.end, text.as_ref());
4943 if should_skip {
4944 let anchor = snapshot.anchor_after(selection.end);
4945 new_selections
4946 .push((selection.map(|_| anchor), region.pair.end.len()));
4947 continue;
4948 }
4949 }
4950
4951 let always_treat_brackets_as_autoclosed = snapshot
4952 .language_settings_at(selection.start, cx)
4953 .always_treat_brackets_as_autoclosed;
4954 if always_treat_brackets_as_autoclosed
4955 && is_bracket_pair_end
4956 && snapshot.contains_str_at(selection.end, text.as_ref())
4957 {
4958 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4959 // and the inserted text is a closing bracket and the selection is followed
4960 // by the closing bracket then move the selection past the closing bracket.
4961 let anchor = snapshot.anchor_after(selection.end);
4962 new_selections.push((selection.map(|_| anchor), text.len()));
4963 continue;
4964 }
4965 }
4966 // If an opening bracket is 1 character long and is typed while
4967 // text is selected, then surround that text with the bracket pair.
4968 else if auto_surround
4969 && bracket_pair.surround
4970 && is_bracket_pair_start
4971 && bracket_pair.start.chars().count() == 1
4972 {
4973 edits.push((selection.start..selection.start, text.clone()));
4974 edits.push((
4975 selection.end..selection.end,
4976 bracket_pair.end.as_str().into(),
4977 ));
4978 bracket_inserted = true;
4979 new_selections.push((
4980 Selection {
4981 id: selection.id,
4982 start: snapshot.anchor_after(selection.start),
4983 end: snapshot.anchor_before(selection.end),
4984 reversed: selection.reversed,
4985 goal: selection.goal,
4986 },
4987 0,
4988 ));
4989 continue;
4990 }
4991 }
4992 }
4993
4994 if self.auto_replace_emoji_shortcode
4995 && selection.is_empty()
4996 && text.as_ref().ends_with(':')
4997 && let Some(possible_emoji_short_code) =
4998 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4999 && !possible_emoji_short_code.is_empty()
5000 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
5001 {
5002 let emoji_shortcode_start = Point::new(
5003 selection.start.row,
5004 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
5005 );
5006
5007 // Remove shortcode from buffer
5008 edits.push((
5009 emoji_shortcode_start..selection.start,
5010 "".to_string().into(),
5011 ));
5012 new_selections.push((
5013 Selection {
5014 id: selection.id,
5015 start: snapshot.anchor_after(emoji_shortcode_start),
5016 end: snapshot.anchor_before(selection.start),
5017 reversed: selection.reversed,
5018 goal: selection.goal,
5019 },
5020 0,
5021 ));
5022
5023 // Insert emoji
5024 let selection_start_anchor = snapshot.anchor_after(selection.start);
5025 new_selections.push((selection.map(|_| selection_start_anchor), 0));
5026 edits.push((selection.start..selection.end, emoji.to_string().into()));
5027
5028 continue;
5029 }
5030
5031 let next_is_adjacent = regions
5032 .peek()
5033 .is_some_and(|(next, _)| selection.end == next.start);
5034
5035 // If not handling any auto-close operation, then just replace the selected
5036 // text with the given input and move the selection to the end of the
5037 // newly inserted text.
5038 let anchor = if in_adjacent_group || next_is_adjacent {
5039 // After edits the right bias would shift those anchor to the next visible fragment
5040 // but we want to resolve to the previous one
5041 snapshot.anchor_before(selection.end)
5042 } else {
5043 snapshot.anchor_after(selection.end)
5044 };
5045
5046 if !self.linked_edit_ranges.is_empty() {
5047 let start_anchor = snapshot.anchor_before(selection.start);
5048 let classifier = snapshot
5049 .char_classifier_at(start_anchor)
5050 .scope_context(Some(CharScopeContext::LinkedEdit));
5051
5052 if let Some((_, anchor_range)) =
5053 snapshot.anchor_range_to_buffer_anchor_range(start_anchor..anchor)
5054 {
5055 let is_word_char = text
5056 .chars()
5057 .next()
5058 .is_none_or(|char| classifier.is_word(char));
5059
5060 let is_dot = text.as_ref() == ".";
5061 let should_apply_linked_edit = is_word_char || is_dot;
5062
5063 if should_apply_linked_edit {
5064 linked_edits.push(&self, anchor_range, text.clone(), cx);
5065 } else {
5066 clear_linked_edit_ranges = true;
5067 }
5068 }
5069 }
5070
5071 new_selections.push((selection.map(|_| anchor), 0));
5072 edits.push((selection.start..selection.end, text.clone()));
5073
5074 has_adjacent_edits |= next_is_adjacent;
5075 in_adjacent_group = next_is_adjacent;
5076 }
5077
5078 if all_selections_read_only {
5079 return;
5080 }
5081
5082 drop(regions);
5083 drop(snapshot);
5084
5085 self.transact(window, cx, |this, window, cx| {
5086 if clear_linked_edit_ranges {
5087 this.linked_edit_ranges.clear();
5088 }
5089 let initial_buffer_versions =
5090 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
5091
5092 this.buffer.update(cx, |buffer, cx| {
5093 if has_adjacent_edits {
5094 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
5095 } else {
5096 buffer.edit(edits, this.autoindent_mode.clone(), cx);
5097 }
5098 });
5099 linked_edits.apply(cx);
5100 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
5101 let new_selection_deltas = new_selections.iter().map(|e| e.1);
5102 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5103 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
5104 new_anchor_selections,
5105 &map,
5106 )
5107 .zip(new_selection_deltas)
5108 .map(|(selection, delta)| Selection {
5109 id: selection.id,
5110 start: selection.start + delta,
5111 end: selection.end + delta,
5112 reversed: selection.reversed,
5113 goal: SelectionGoal::None,
5114 })
5115 .collect::<Vec<_>>();
5116
5117 let mut i = 0;
5118 for (position, delta, selection_id, pair) in new_autoclose_regions {
5119 let position = position.to_offset(map.buffer_snapshot()) + delta;
5120 let start = map.buffer_snapshot().anchor_before(position);
5121 let end = map.buffer_snapshot().anchor_after(position);
5122 while let Some(existing_state) = this.autoclose_regions.get(i) {
5123 match existing_state
5124 .range
5125 .start
5126 .cmp(&start, map.buffer_snapshot())
5127 {
5128 Ordering::Less => i += 1,
5129 Ordering::Greater => break,
5130 Ordering::Equal => {
5131 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
5132 Ordering::Less => i += 1,
5133 Ordering::Equal => break,
5134 Ordering::Greater => break,
5135 }
5136 }
5137 }
5138 }
5139 this.autoclose_regions.insert(
5140 i,
5141 AutocloseRegion {
5142 selection_id,
5143 range: start..end,
5144 pair,
5145 },
5146 );
5147 }
5148
5149 let had_active_edit_prediction = this.has_active_edit_prediction();
5150 this.change_selections(
5151 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
5152 window,
5153 cx,
5154 |s| s.select(new_selections),
5155 );
5156
5157 if !bracket_inserted
5158 && let Some(on_type_format_task) =
5159 this.trigger_on_type_formatting(text.to_string(), window, cx)
5160 {
5161 on_type_format_task.detach_and_log_err(cx);
5162 }
5163
5164 let editor_settings = EditorSettings::get_global(cx);
5165 if bracket_inserted
5166 && (editor_settings.auto_signature_help
5167 || editor_settings.show_signature_help_after_edits)
5168 {
5169 this.show_signature_help(&ShowSignatureHelp, window, cx);
5170 }
5171
5172 let trigger_in_words =
5173 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5174 if this.hard_wrap.is_some() {
5175 let latest: Range<Point> = this.selections.newest(&map).range();
5176 if latest.is_empty()
5177 && this
5178 .buffer()
5179 .read(cx)
5180 .snapshot(cx)
5181 .line_len(MultiBufferRow(latest.start.row))
5182 == latest.start.column
5183 {
5184 this.rewrap_impl(
5185 RewrapOptions {
5186 override_language_settings: true,
5187 preserve_existing_whitespace: true,
5188 line_length: None,
5189 },
5190 cx,
5191 )
5192 }
5193 }
5194 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5195 refresh_linked_ranges(this, window, cx);
5196 this.refresh_edit_prediction(true, false, window, cx);
5197 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5198 });
5199 }
5200
5201 fn find_possible_emoji_shortcode_at_position(
5202 snapshot: &MultiBufferSnapshot,
5203 position: Point,
5204 ) -> Option<String> {
5205 let mut chars = Vec::new();
5206 let mut found_colon = false;
5207 for char in snapshot.reversed_chars_at(position).take(100) {
5208 // Found a possible emoji shortcode in the middle of the buffer
5209 if found_colon {
5210 if char.is_whitespace() {
5211 chars.reverse();
5212 return Some(chars.iter().collect());
5213 }
5214 // If the previous character is not a whitespace, we are in the middle of a word
5215 // and we only want to complete the shortcode if the word is made up of other emojis
5216 let mut containing_word = String::new();
5217 for ch in snapshot
5218 .reversed_chars_at(position)
5219 .skip(chars.len() + 1)
5220 .take(100)
5221 {
5222 if ch.is_whitespace() {
5223 break;
5224 }
5225 containing_word.push(ch);
5226 }
5227 let containing_word = containing_word.chars().rev().collect::<String>();
5228 if util::word_consists_of_emojis(containing_word.as_str()) {
5229 chars.reverse();
5230 return Some(chars.iter().collect());
5231 }
5232 }
5233
5234 if char.is_whitespace() || !char.is_ascii() {
5235 return None;
5236 }
5237 if char == ':' {
5238 found_colon = true;
5239 } else {
5240 chars.push(char);
5241 }
5242 }
5243 // Found a possible emoji shortcode at the beginning of the buffer
5244 chars.reverse();
5245 Some(chars.iter().collect())
5246 }
5247
5248 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5249 if self.read_only(cx) {
5250 return;
5251 }
5252
5253 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5254 self.transact(window, cx, |this, window, cx| {
5255 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5256 let selections = this
5257 .selections
5258 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5259 let multi_buffer = this.buffer.read(cx);
5260 let buffer = multi_buffer.snapshot(cx);
5261 selections
5262 .iter()
5263 .map(|selection| {
5264 let start_point = selection.start.to_point(&buffer);
5265 let mut existing_indent =
5266 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5267 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5268 let start = selection.start;
5269 let end = selection.end;
5270 let selection_is_empty = start == end;
5271 let language_scope = buffer.language_scope_at(start);
5272 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5273 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5274 &buffer,
5275 start..end,
5276 language,
5277 )
5278 || NewlineConfig::insert_extra_newline_tree_sitter(
5279 &buffer,
5280 start..end,
5281 );
5282
5283 let mut newline_config = NewlineConfig::Newline {
5284 additional_indent: IndentSize::spaces(0),
5285 extra_line_additional_indent: if needs_extra_newline {
5286 Some(IndentSize::spaces(0))
5287 } else {
5288 None
5289 },
5290 prevent_auto_indent: false,
5291 };
5292
5293 let comment_delimiter = maybe!({
5294 if !selection_is_empty {
5295 return None;
5296 }
5297
5298 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5299 return None;
5300 }
5301
5302 return comment_delimiter_for_newline(
5303 &start_point,
5304 &buffer,
5305 language,
5306 );
5307 });
5308
5309 let doc_delimiter = maybe!({
5310 if !selection_is_empty {
5311 return None;
5312 }
5313
5314 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5315 return None;
5316 }
5317
5318 return documentation_delimiter_for_newline(
5319 &start_point,
5320 &buffer,
5321 language,
5322 &mut newline_config,
5323 );
5324 });
5325
5326 let list_delimiter = maybe!({
5327 if !selection_is_empty {
5328 return None;
5329 }
5330
5331 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5332 return None;
5333 }
5334
5335 return list_delimiter_for_newline(
5336 &start_point,
5337 &buffer,
5338 language,
5339 &mut newline_config,
5340 );
5341 });
5342
5343 (
5344 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5345 newline_config,
5346 )
5347 } else {
5348 (
5349 None,
5350 NewlineConfig::Newline {
5351 additional_indent: IndentSize::spaces(0),
5352 extra_line_additional_indent: None,
5353 prevent_auto_indent: false,
5354 },
5355 )
5356 };
5357
5358 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5359 NewlineConfig::ClearCurrentLine => {
5360 let row_start =
5361 buffer.point_to_offset(Point::new(start_point.row, 0));
5362 (row_start, String::new(), false)
5363 }
5364 NewlineConfig::UnindentCurrentLine { continuation } => {
5365 let row_start =
5366 buffer.point_to_offset(Point::new(start_point.row, 0));
5367 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5368 let tab_size_indent = IndentSize::spaces(tab_size.get());
5369 let reduced_indent =
5370 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5371 let mut new_text = String::new();
5372 new_text.extend(reduced_indent.chars());
5373 new_text.push_str(continuation);
5374 (row_start, new_text, true)
5375 }
5376 NewlineConfig::Newline {
5377 additional_indent,
5378 extra_line_additional_indent,
5379 prevent_auto_indent,
5380 } => {
5381 let auto_indent_mode =
5382 buffer.language_settings_at(start, cx).auto_indent;
5383 let preserve_indent =
5384 auto_indent_mode != language::AutoIndentMode::None;
5385 let apply_syntax_indent =
5386 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5387 let capacity_for_delimiter =
5388 delimiter.as_deref().map(str::len).unwrap_or_default();
5389 let existing_indent_len = if preserve_indent {
5390 existing_indent.len as usize
5391 } else {
5392 0
5393 };
5394 let extra_line_len = extra_line_additional_indent
5395 .map(|i| 1 + existing_indent_len + i.len as usize)
5396 .unwrap_or(0);
5397 let mut new_text = String::with_capacity(
5398 1 + capacity_for_delimiter
5399 + existing_indent_len
5400 + additional_indent.len as usize
5401 + extra_line_len,
5402 );
5403 new_text.push('\n');
5404 if preserve_indent {
5405 new_text.extend(existing_indent.chars());
5406 }
5407 new_text.extend(additional_indent.chars());
5408 if let Some(delimiter) = &delimiter {
5409 new_text.push_str(delimiter);
5410 }
5411 if let Some(extra_indent) = extra_line_additional_indent {
5412 new_text.push('\n');
5413 if preserve_indent {
5414 new_text.extend(existing_indent.chars());
5415 }
5416 new_text.extend(extra_indent.chars());
5417 }
5418 (
5419 start,
5420 new_text,
5421 *prevent_auto_indent || !apply_syntax_indent,
5422 )
5423 }
5424 };
5425
5426 let anchor = buffer.anchor_after(end);
5427 let new_selection = selection.map(|_| anchor);
5428 (
5429 ((edit_start..end, new_text), prevent_auto_indent),
5430 (newline_config.has_extra_line(), new_selection),
5431 )
5432 })
5433 .unzip()
5434 };
5435
5436 let mut auto_indent_edits = Vec::new();
5437 let mut edits = Vec::new();
5438 for (edit, prevent_auto_indent) in edits_with_flags {
5439 if prevent_auto_indent {
5440 edits.push(edit);
5441 } else {
5442 auto_indent_edits.push(edit);
5443 }
5444 }
5445 if !edits.is_empty() {
5446 this.edit(edits, cx);
5447 }
5448 if !auto_indent_edits.is_empty() {
5449 this.edit_with_autoindent(auto_indent_edits, cx);
5450 }
5451
5452 let buffer = this.buffer.read(cx).snapshot(cx);
5453 let new_selections = selection_info
5454 .into_iter()
5455 .map(|(extra_newline_inserted, new_selection)| {
5456 let mut cursor = new_selection.end.to_point(&buffer);
5457 if extra_newline_inserted {
5458 cursor.row -= 1;
5459 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5460 }
5461 new_selection.map(|_| cursor)
5462 })
5463 .collect();
5464
5465 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5466 this.refresh_edit_prediction(true, false, window, cx);
5467 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5468 task.detach_and_log_err(cx);
5469 }
5470 });
5471 }
5472
5473 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5474 if self.read_only(cx) {
5475 return;
5476 }
5477
5478 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5479
5480 let buffer = self.buffer.read(cx);
5481 let snapshot = buffer.snapshot(cx);
5482
5483 let mut edits = Vec::new();
5484 let mut rows = Vec::new();
5485
5486 for (rows_inserted, selection) in self
5487 .selections
5488 .all_adjusted(&self.display_snapshot(cx))
5489 .into_iter()
5490 .enumerate()
5491 {
5492 let cursor = selection.head();
5493 let row = cursor.row;
5494
5495 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5496
5497 let newline = "\n".to_string();
5498 edits.push((start_of_line..start_of_line, newline));
5499
5500 rows.push(row + rows_inserted as u32);
5501 }
5502
5503 self.transact(window, cx, |editor, window, cx| {
5504 editor.edit(edits, cx);
5505
5506 editor.change_selections(Default::default(), window, cx, |s| {
5507 let mut index = 0;
5508 s.move_cursors_with(&mut |map, _, _| {
5509 let row = rows[index];
5510 index += 1;
5511
5512 let point = Point::new(row, 0);
5513 let boundary = map.next_line_boundary(point).1;
5514 let clipped = map.clip_point(boundary, Bias::Left);
5515
5516 (clipped, SelectionGoal::None)
5517 });
5518 });
5519
5520 let mut indent_edits = Vec::new();
5521 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5522 for row in rows {
5523 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5524 for (row, indent) in indents {
5525 if indent.len == 0 {
5526 continue;
5527 }
5528
5529 let text = match indent.kind {
5530 IndentKind::Space => " ".repeat(indent.len as usize),
5531 IndentKind::Tab => "\t".repeat(indent.len as usize),
5532 };
5533 let point = Point::new(row.0, 0);
5534 indent_edits.push((point..point, text));
5535 }
5536 }
5537 editor.edit(indent_edits, cx);
5538 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5539 format.detach_and_log_err(cx);
5540 }
5541 });
5542 }
5543
5544 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5545 if self.read_only(cx) {
5546 return;
5547 }
5548
5549 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5550
5551 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5552 let mut rows = Vec::new();
5553 let mut rows_inserted = 0;
5554
5555 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5556 let cursor = selection.head();
5557 let row = cursor.row;
5558
5559 let point = Point::new(row, 0);
5560 let Some((buffer_handle, buffer_point)) =
5561 self.buffer.read(cx).point_to_buffer_point(point, cx)
5562 else {
5563 continue;
5564 };
5565
5566 buffer_edits
5567 .entry(buffer_handle.entity_id())
5568 .or_insert_with(|| (buffer_handle, Vec::new()))
5569 .1
5570 .push(buffer_point);
5571
5572 rows_inserted += 1;
5573 rows.push(row + rows_inserted);
5574 }
5575
5576 self.transact(window, cx, |editor, window, cx| {
5577 for (_, (buffer_handle, points)) in &buffer_edits {
5578 buffer_handle.update(cx, |buffer, cx| {
5579 let edits: Vec<_> = points
5580 .iter()
5581 .map(|point| {
5582 let target = Point::new(point.row + 1, 0);
5583 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5584 (start_of_line..start_of_line, "\n")
5585 })
5586 .collect();
5587 buffer.edit(edits, None, cx);
5588 });
5589 }
5590
5591 editor.change_selections(Default::default(), window, cx, |s| {
5592 let mut index = 0;
5593 s.move_cursors_with(&mut |map, _, _| {
5594 let row = rows[index];
5595 index += 1;
5596
5597 let point = Point::new(row, 0);
5598 let boundary = map.next_line_boundary(point).1;
5599 let clipped = map.clip_point(boundary, Bias::Left);
5600
5601 (clipped, SelectionGoal::None)
5602 });
5603 });
5604
5605 let mut indent_edits = Vec::new();
5606 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5607 for row in rows {
5608 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5609 for (row, indent) in indents {
5610 if indent.len == 0 {
5611 continue;
5612 }
5613
5614 let text = match indent.kind {
5615 IndentKind::Space => " ".repeat(indent.len as usize),
5616 IndentKind::Tab => "\t".repeat(indent.len as usize),
5617 };
5618 let point = Point::new(row.0, 0);
5619 indent_edits.push((point..point, text));
5620 }
5621 }
5622 editor.edit(indent_edits, cx);
5623 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5624 format.detach_and_log_err(cx);
5625 }
5626 });
5627 }
5628
5629 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5630 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5631 original_indent_columns: Vec::new(),
5632 });
5633 self.replace_selections(text, autoindent, window, cx, false);
5634 }
5635
5636 /// Replaces the editor's selections with the provided `text`, applying the
5637 /// given `autoindent_mode` (`None` will skip autoindentation).
5638 ///
5639 /// Early returns if the editor is in read-only mode, without applying any
5640 /// edits.
5641 fn replace_selections(
5642 &mut self,
5643 text: &str,
5644 autoindent_mode: Option<AutoindentMode>,
5645 window: &mut Window,
5646 cx: &mut Context<Self>,
5647 apply_linked_edits: bool,
5648 ) {
5649 if self.read_only(cx) {
5650 return;
5651 }
5652
5653 let text: Arc<str> = text.into();
5654 self.transact(window, cx, |this, window, cx| {
5655 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5656 let linked_edits = if apply_linked_edits {
5657 this.linked_edits_for_selections(text.clone(), cx)
5658 } else {
5659 LinkedEdits::new()
5660 };
5661
5662 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5663 let anchors = {
5664 let snapshot = buffer.read(cx);
5665 old_selections
5666 .iter()
5667 .map(|s| {
5668 let anchor = snapshot.anchor_after(s.head());
5669 s.map(|_| anchor)
5670 })
5671 .collect::<Vec<_>>()
5672 };
5673 buffer.edit(
5674 old_selections
5675 .iter()
5676 .map(|s| (s.start..s.end, text.clone())),
5677 autoindent_mode,
5678 cx,
5679 );
5680 anchors
5681 });
5682
5683 linked_edits.apply(cx);
5684
5685 this.change_selections(Default::default(), window, cx, |s| {
5686 s.select_anchors(selection_anchors);
5687 });
5688
5689 if apply_linked_edits {
5690 refresh_linked_ranges(this, window, cx);
5691 }
5692
5693 cx.notify();
5694 });
5695 }
5696
5697 /// Collects linked edits for the current selections, pairing each linked
5698 /// range with `text`.
5699 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5700 let multibuffer_snapshot = self.buffer().read(cx).snapshot(cx);
5701 let mut linked_edits = LinkedEdits::new();
5702 if !self.linked_edit_ranges.is_empty() {
5703 for selection in self.selections.disjoint_anchors() {
5704 let Some((_, range)) =
5705 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(selection.range())
5706 else {
5707 continue;
5708 };
5709 linked_edits.push(self, range, text.clone(), cx);
5710 }
5711 }
5712 linked_edits
5713 }
5714
5715 /// Deletes the content covered by the current selections and applies
5716 /// linked edits.
5717 pub fn delete_selections_with_linked_edits(
5718 &mut self,
5719 window: &mut Window,
5720 cx: &mut Context<Self>,
5721 ) {
5722 self.replace_selections("", None, window, cx, true);
5723 }
5724
5725 #[cfg(any(test, feature = "test-support"))]
5726 pub fn set_linked_edit_ranges_for_testing(
5727 &mut self,
5728 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5729 cx: &mut Context<Self>,
5730 ) -> Option<()> {
5731 let Some((buffer, _)) = self
5732 .buffer
5733 .read(cx)
5734 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5735 else {
5736 return None;
5737 };
5738 let buffer = buffer.read(cx);
5739 let buffer_id = buffer.remote_id();
5740 let mut linked_ranges = Vec::with_capacity(ranges.len());
5741 for (base_range, linked_ranges_points) in ranges {
5742 let base_anchor =
5743 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5744 let linked_anchors = linked_ranges_points
5745 .into_iter()
5746 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5747 .collect();
5748 linked_ranges.push((base_anchor, linked_anchors));
5749 }
5750 let mut map = HashMap::default();
5751 map.insert(buffer_id, linked_ranges);
5752 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5753 Some(())
5754 }
5755
5756 fn trigger_completion_on_input(
5757 &mut self,
5758 text: &str,
5759 trigger_in_words: bool,
5760 window: &mut Window,
5761 cx: &mut Context<Self>,
5762 ) {
5763 let completions_source = self
5764 .context_menu
5765 .borrow()
5766 .as_ref()
5767 .and_then(|menu| match menu {
5768 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5769 CodeContextMenu::CodeActions(_) => None,
5770 });
5771
5772 match completions_source {
5773 Some(CompletionsMenuSource::Words { .. }) => {
5774 self.open_or_update_completions_menu(
5775 Some(CompletionsMenuSource::Words {
5776 ignore_threshold: false,
5777 }),
5778 None,
5779 trigger_in_words,
5780 window,
5781 cx,
5782 );
5783 }
5784 _ => self.open_or_update_completions_menu(
5785 None,
5786 Some(text.to_owned()).filter(|x| !x.is_empty()),
5787 true,
5788 window,
5789 cx,
5790 ),
5791 }
5792 }
5793
5794 /// If any empty selections is touching the start of its innermost containing autoclose
5795 /// region, expand it to select the brackets.
5796 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5797 let selections = self
5798 .selections
5799 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5800 let buffer = self.buffer.read(cx).read(cx);
5801 let new_selections = self
5802 .selections_with_autoclose_regions(selections, &buffer)
5803 .map(|(mut selection, region)| {
5804 if !selection.is_empty() {
5805 return selection;
5806 }
5807
5808 if let Some(region) = region {
5809 let mut range = region.range.to_offset(&buffer);
5810 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5811 range.start -= region.pair.start.len();
5812 if buffer.contains_str_at(range.start, ®ion.pair.start)
5813 && buffer.contains_str_at(range.end, ®ion.pair.end)
5814 {
5815 range.end += region.pair.end.len();
5816 selection.start = range.start;
5817 selection.end = range.end;
5818
5819 return selection;
5820 }
5821 }
5822 }
5823
5824 let always_treat_brackets_as_autoclosed = buffer
5825 .language_settings_at(selection.start, cx)
5826 .always_treat_brackets_as_autoclosed;
5827
5828 if !always_treat_brackets_as_autoclosed {
5829 return selection;
5830 }
5831
5832 if let Some(scope) = buffer.language_scope_at(selection.start) {
5833 for (pair, enabled) in scope.brackets() {
5834 if !enabled || !pair.close {
5835 continue;
5836 }
5837
5838 if buffer.contains_str_at(selection.start, &pair.end) {
5839 let pair_start_len = pair.start.len();
5840 if buffer.contains_str_at(
5841 selection.start.saturating_sub_usize(pair_start_len),
5842 &pair.start,
5843 ) {
5844 selection.start -= pair_start_len;
5845 selection.end += pair.end.len();
5846
5847 return selection;
5848 }
5849 }
5850 }
5851 }
5852
5853 selection
5854 })
5855 .collect();
5856
5857 drop(buffer);
5858 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5859 selections.select(new_selections)
5860 });
5861 }
5862
5863 /// Iterate the given selections, and for each one, find the smallest surrounding
5864 /// autoclose region. This uses the ordering of the selections and the autoclose
5865 /// regions to avoid repeated comparisons.
5866 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5867 &'a self,
5868 selections: impl IntoIterator<Item = Selection<D>>,
5869 buffer: &'a MultiBufferSnapshot,
5870 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5871 let mut i = 0;
5872 let mut regions = self.autoclose_regions.as_slice();
5873 selections.into_iter().map(move |selection| {
5874 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5875
5876 let mut enclosing = None;
5877 while let Some(pair_state) = regions.get(i) {
5878 if pair_state.range.end.to_offset(buffer) < range.start {
5879 regions = ®ions[i + 1..];
5880 i = 0;
5881 } else if pair_state.range.start.to_offset(buffer) > range.end {
5882 break;
5883 } else {
5884 if pair_state.selection_id == selection.id {
5885 enclosing = Some(pair_state);
5886 }
5887 i += 1;
5888 }
5889 }
5890
5891 (selection, enclosing)
5892 })
5893 }
5894
5895 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5896 fn invalidate_autoclose_regions(
5897 &mut self,
5898 mut selections: &[Selection<Anchor>],
5899 buffer: &MultiBufferSnapshot,
5900 ) {
5901 self.autoclose_regions.retain(|state| {
5902 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5903 return false;
5904 }
5905
5906 let mut i = 0;
5907 while let Some(selection) = selections.get(i) {
5908 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5909 selections = &selections[1..];
5910 continue;
5911 }
5912 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5913 break;
5914 }
5915 if selection.id == state.selection_id {
5916 return true;
5917 } else {
5918 i += 1;
5919 }
5920 }
5921 false
5922 });
5923 }
5924
5925 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5926 let offset = position.to_offset(buffer);
5927 let (word_range, kind) =
5928 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5929 if offset > word_range.start && kind == Some(CharKind::Word) {
5930 Some(
5931 buffer
5932 .text_for_range(word_range.start..offset)
5933 .collect::<String>(),
5934 )
5935 } else {
5936 None
5937 }
5938 }
5939
5940 pub fn is_lsp_relevant(&self, file: Option<&Arc<dyn language::File>>, cx: &App) -> bool {
5941 let Some(project) = self.project() else {
5942 return false;
5943 };
5944 let Some(buffer_file) = project::File::from_dyn(file) else {
5945 return false;
5946 };
5947 let Some(entry_id) = buffer_file.project_entry_id() else {
5948 return false;
5949 };
5950 let project = project.read(cx);
5951 let Some(buffer_worktree) = project.worktree_for_id(buffer_file.worktree_id(cx), cx) else {
5952 return false;
5953 };
5954 let Some(worktree_entry) = buffer_worktree.read(cx).entry_for_id(entry_id) else {
5955 return false;
5956 };
5957 !worktree_entry.is_ignored
5958 }
5959
5960 pub fn visible_buffers(&self, cx: &mut Context<Editor>) -> Vec<Entity<Buffer>> {
5961 let display_snapshot = self.display_snapshot(cx);
5962 let visible_range = self.multi_buffer_visible_range(&display_snapshot, cx);
5963 let multi_buffer = self.buffer().read(cx);
5964 display_snapshot
5965 .buffer_snapshot()
5966 .range_to_buffer_ranges(visible_range)
5967 .into_iter()
5968 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5969 .filter_map(|(buffer_snapshot, _, _)| multi_buffer.buffer(buffer_snapshot.remote_id()))
5970 .collect()
5971 }
5972
5973 pub fn visible_buffer_ranges(
5974 &self,
5975 cx: &mut Context<Editor>,
5976 ) -> Vec<(
5977 BufferSnapshot,
5978 Range<BufferOffset>,
5979 ExcerptRange<text::Anchor>,
5980 )> {
5981 let display_snapshot = self.display_snapshot(cx);
5982 let visible_range = self.multi_buffer_visible_range(&display_snapshot, cx);
5983 display_snapshot
5984 .buffer_snapshot()
5985 .range_to_buffer_ranges(visible_range)
5986 .into_iter()
5987 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5988 .collect()
5989 }
5990
5991 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5992 TextLayoutDetails {
5993 text_system: window.text_system().clone(),
5994 editor_style: self.style.clone().unwrap(),
5995 rem_size: window.rem_size(),
5996 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5997 visible_rows: self.visible_line_count(),
5998 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5999 }
6000 }
6001
6002 fn trigger_on_type_formatting(
6003 &self,
6004 input: String,
6005 window: &mut Window,
6006 cx: &mut Context<Self>,
6007 ) -> Option<Task<Result<()>>> {
6008 if input.chars().count() != 1 {
6009 return None;
6010 }
6011
6012 let project = self.project()?;
6013 let position = self.selections.newest_anchor().head();
6014 let (buffer, buffer_position) = self
6015 .buffer
6016 .read(cx)
6017 .text_anchor_for_position(position, cx)?;
6018
6019 let settings = LanguageSettings::for_buffer_at(&buffer.read(cx), buffer_position, cx);
6020 if !settings.use_on_type_format {
6021 return None;
6022 }
6023
6024 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
6025 // hence we do LSP request & edit on host side only — add formats to host's history.
6026 let push_to_lsp_host_history = true;
6027 // If this is not the host, append its history with new edits.
6028 let push_to_client_history = project.read(cx).is_via_collab();
6029
6030 let on_type_formatting = project.update(cx, |project, cx| {
6031 project.on_type_format(
6032 buffer.clone(),
6033 buffer_position,
6034 input,
6035 push_to_lsp_host_history,
6036 cx,
6037 )
6038 });
6039 Some(cx.spawn_in(window, async move |editor, cx| {
6040 if let Some(transaction) = on_type_formatting.await? {
6041 if push_to_client_history {
6042 buffer.update(cx, |buffer, _| {
6043 buffer.push_transaction(transaction, Instant::now());
6044 buffer.finalize_last_transaction();
6045 });
6046 }
6047 editor.update(cx, |editor, cx| {
6048 editor.refresh_document_highlights(cx);
6049 })?;
6050 }
6051 Ok(())
6052 }))
6053 }
6054
6055 pub fn show_word_completions(
6056 &mut self,
6057 _: &ShowWordCompletions,
6058 window: &mut Window,
6059 cx: &mut Context<Self>,
6060 ) {
6061 self.open_or_update_completions_menu(
6062 Some(CompletionsMenuSource::Words {
6063 ignore_threshold: true,
6064 }),
6065 None,
6066 false,
6067 window,
6068 cx,
6069 );
6070 }
6071
6072 pub fn show_completions(
6073 &mut self,
6074 _: &ShowCompletions,
6075 window: &mut Window,
6076 cx: &mut Context<Self>,
6077 ) {
6078 self.open_or_update_completions_menu(None, None, false, window, cx);
6079 }
6080
6081 fn open_or_update_completions_menu(
6082 &mut self,
6083 requested_source: Option<CompletionsMenuSource>,
6084 trigger: Option<String>,
6085 trigger_in_words: bool,
6086 window: &mut Window,
6087 cx: &mut Context<Self>,
6088 ) {
6089 if self.pending_rename.is_some() {
6090 return;
6091 }
6092
6093 let completions_source = self
6094 .context_menu
6095 .borrow()
6096 .as_ref()
6097 .and_then(|menu| match menu {
6098 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
6099 CodeContextMenu::CodeActions(_) => None,
6100 });
6101
6102 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
6103
6104 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
6105 // inserted and selected. To handle that case, the start of the selection is used so that
6106 // the menu starts with all choices.
6107 let position = self
6108 .selections
6109 .newest_anchor()
6110 .start
6111 .bias_right(&multibuffer_snapshot);
6112
6113 if position.diff_base_anchor().is_some() {
6114 return;
6115 }
6116 let multibuffer_position = multibuffer_snapshot.anchor_before(position);
6117 let Some((buffer_position, _)) =
6118 multibuffer_snapshot.anchor_to_buffer_anchor(multibuffer_position)
6119 else {
6120 return;
6121 };
6122 let Some(buffer) = self.buffer.read(cx).buffer(buffer_position.buffer_id) else {
6123 return;
6124 };
6125 let buffer_snapshot = buffer.read(cx).snapshot();
6126
6127 let menu_is_open = matches!(
6128 self.context_menu.borrow().as_ref(),
6129 Some(CodeContextMenu::Completions(_))
6130 );
6131
6132 let language = buffer_snapshot
6133 .language_at(buffer_position)
6134 .map(|language| language.name());
6135 let language_settings = multibuffer_snapshot.language_settings_at(multibuffer_position, cx);
6136 let completion_settings = language_settings.completions.clone();
6137
6138 let show_completions_on_input = self
6139 .show_completions_on_input_override
6140 .unwrap_or(language_settings.show_completions_on_input);
6141 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
6142 return;
6143 }
6144
6145 let query: Option<Arc<String>> =
6146 Self::completion_query(&multibuffer_snapshot, multibuffer_position)
6147 .map(|query| query.into());
6148
6149 drop(multibuffer_snapshot);
6150
6151 // Hide the current completions menu when query is empty. Without this, cached
6152 // completions from before the trigger char may be reused (#32774).
6153 if query.is_none() && menu_is_open {
6154 self.hide_context_menu(window, cx);
6155 }
6156
6157 let mut ignore_word_threshold = false;
6158 let provider = match requested_source {
6159 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
6160 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
6161 ignore_word_threshold = ignore_threshold;
6162 None
6163 }
6164 Some(CompletionsMenuSource::SnippetChoices)
6165 | Some(CompletionsMenuSource::SnippetsOnly) => {
6166 log::error!("bug: SnippetChoices requested_source is not handled");
6167 None
6168 }
6169 };
6170
6171 let sort_completions = provider
6172 .as_ref()
6173 .is_some_and(|provider| provider.sort_completions());
6174
6175 let filter_completions = provider
6176 .as_ref()
6177 .is_none_or(|provider| provider.filter_completions());
6178
6179 let was_snippets_only = matches!(
6180 completions_source,
6181 Some(CompletionsMenuSource::SnippetsOnly)
6182 );
6183
6184 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
6185 if filter_completions {
6186 menu.filter(
6187 query.clone().unwrap_or_default(),
6188 buffer_position,
6189 &buffer,
6190 provider.clone(),
6191 window,
6192 cx,
6193 );
6194 }
6195 // When `is_incomplete` is false, no need to re-query completions when the current query
6196 // is a suffix of the initial query.
6197 let was_complete = !menu.is_incomplete;
6198 if was_complete && !was_snippets_only {
6199 // If the new query is a suffix of the old query (typing more characters) and
6200 // the previous result was complete, the existing completions can be filtered.
6201 //
6202 // Note that snippet completions are always complete.
6203 let query_matches = match (&menu.initial_query, &query) {
6204 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6205 (None, _) => true,
6206 _ => false,
6207 };
6208 if query_matches {
6209 let position_matches = if menu.initial_position == position {
6210 true
6211 } else {
6212 let snapshot = self.buffer.read(cx).read(cx);
6213 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6214 };
6215 if position_matches {
6216 return;
6217 }
6218 }
6219 }
6220 };
6221
6222 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6223 buffer_snapshot.surrounding_word(buffer_position, None)
6224 {
6225 let word_to_exclude = buffer_snapshot
6226 .text_for_range(word_range.clone())
6227 .collect::<String>();
6228 (
6229 buffer_snapshot.anchor_before(word_range.start)
6230 ..buffer_snapshot.anchor_after(buffer_position),
6231 Some(word_to_exclude),
6232 )
6233 } else {
6234 (buffer_position..buffer_position, None)
6235 };
6236
6237 let show_completion_documentation = buffer_snapshot
6238 .settings_at(buffer_position, cx)
6239 .show_completion_documentation;
6240
6241 // The document can be large, so stay in reasonable bounds when searching for words,
6242 // otherwise completion pop-up might be slow to appear.
6243 const WORD_LOOKUP_ROWS: u32 = 5_000;
6244 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6245 let min_word_search = buffer_snapshot.clip_point(
6246 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6247 Bias::Left,
6248 );
6249 let max_word_search = buffer_snapshot.clip_point(
6250 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6251 Bias::Right,
6252 );
6253 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6254 ..buffer_snapshot.point_to_offset(max_word_search);
6255
6256 let skip_digits = query
6257 .as_ref()
6258 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6259
6260 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6261 trigger.as_ref().is_none_or(|trigger| {
6262 provider.is_completion_trigger(
6263 &buffer,
6264 buffer_position,
6265 trigger,
6266 trigger_in_words,
6267 cx,
6268 )
6269 })
6270 });
6271
6272 let provider_responses = if let Some(provider) = &provider
6273 && load_provider_completions
6274 {
6275 let trigger_character =
6276 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6277 let completion_context = CompletionContext {
6278 trigger_kind: match &trigger_character {
6279 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6280 None => CompletionTriggerKind::INVOKED,
6281 },
6282 trigger_character,
6283 };
6284
6285 provider.completions(&buffer, buffer_position, completion_context, window, cx)
6286 } else {
6287 Task::ready(Ok(Vec::new()))
6288 };
6289
6290 let load_word_completions = if !self.word_completions_enabled {
6291 false
6292 } else if requested_source
6293 == Some(CompletionsMenuSource::Words {
6294 ignore_threshold: true,
6295 })
6296 {
6297 true
6298 } else {
6299 load_provider_completions
6300 && completion_settings.words != WordsCompletionMode::Disabled
6301 && (ignore_word_threshold || {
6302 let words_min_length = completion_settings.words_min_length;
6303 // check whether word has at least `words_min_length` characters
6304 let query_chars = query.iter().flat_map(|q| q.chars());
6305 query_chars.take(words_min_length).count() == words_min_length
6306 })
6307 };
6308
6309 let mut words = if load_word_completions {
6310 cx.background_spawn({
6311 let buffer_snapshot = buffer_snapshot.clone();
6312 async move {
6313 buffer_snapshot.words_in_range(WordsQuery {
6314 fuzzy_contents: None,
6315 range: word_search_range,
6316 skip_digits,
6317 })
6318 }
6319 })
6320 } else {
6321 Task::ready(BTreeMap::default())
6322 };
6323
6324 let snippets = if let Some(provider) = &provider
6325 && provider.show_snippets()
6326 && let Some(project) = self.project()
6327 {
6328 let char_classifier = buffer_snapshot
6329 .char_classifier_at(buffer_position)
6330 .scope_context(Some(CharScopeContext::Completion));
6331 project.update(cx, |project, cx| {
6332 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6333 })
6334 } else {
6335 Task::ready(Ok(CompletionResponse {
6336 completions: Vec::new(),
6337 display_options: Default::default(),
6338 is_incomplete: false,
6339 }))
6340 };
6341
6342 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6343
6344 let id = post_inc(&mut self.next_completion_id);
6345 let task = cx.spawn_in(window, async move |editor, cx| {
6346 let Ok(()) = editor.update(cx, |this, _| {
6347 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6348 }) else {
6349 return;
6350 };
6351
6352 // TODO: Ideally completions from different sources would be selectively re-queried, so
6353 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6354 let mut completions = Vec::new();
6355 let mut is_incomplete = false;
6356 let mut display_options: Option<CompletionDisplayOptions> = None;
6357 if let Some(provider_responses) = provider_responses.await.log_err()
6358 && !provider_responses.is_empty()
6359 {
6360 for response in provider_responses {
6361 completions.extend(response.completions);
6362 is_incomplete = is_incomplete || response.is_incomplete;
6363 match display_options.as_mut() {
6364 None => {
6365 display_options = Some(response.display_options);
6366 }
6367 Some(options) => options.merge(&response.display_options),
6368 }
6369 }
6370 if completion_settings.words == WordsCompletionMode::Fallback {
6371 words = Task::ready(BTreeMap::default());
6372 }
6373 }
6374 let display_options = display_options.unwrap_or_default();
6375
6376 let mut words = words.await;
6377 if let Some(word_to_exclude) = &word_to_exclude {
6378 words.remove(word_to_exclude);
6379 }
6380 for lsp_completion in &completions {
6381 words.remove(&lsp_completion.new_text);
6382 }
6383 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6384 replace_range: word_replace_range.clone(),
6385 new_text: word.clone(),
6386 label: CodeLabel::plain(word, None),
6387 match_start: None,
6388 snippet_deduplication_key: None,
6389 icon_path: None,
6390 documentation: None,
6391 source: CompletionSource::BufferWord {
6392 word_range,
6393 resolved: false,
6394 },
6395 insert_text_mode: Some(InsertTextMode::AS_IS),
6396 confirm: None,
6397 }));
6398
6399 completions.extend(
6400 snippets
6401 .await
6402 .into_iter()
6403 .flat_map(|response| response.completions),
6404 );
6405
6406 let menu = if completions.is_empty() {
6407 None
6408 } else {
6409 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6410 let languages = editor
6411 .workspace
6412 .as_ref()
6413 .and_then(|(workspace, _)| workspace.upgrade())
6414 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6415 let menu = CompletionsMenu::new(
6416 id,
6417 requested_source.unwrap_or(if load_provider_completions {
6418 CompletionsMenuSource::Normal
6419 } else {
6420 CompletionsMenuSource::SnippetsOnly
6421 }),
6422 sort_completions,
6423 show_completion_documentation,
6424 position,
6425 query.clone(),
6426 is_incomplete,
6427 buffer.clone(),
6428 completions.into(),
6429 editor
6430 .context_menu()
6431 .borrow_mut()
6432 .as_ref()
6433 .map(|menu| menu.primary_scroll_handle()),
6434 display_options,
6435 snippet_sort_order,
6436 languages,
6437 language,
6438 cx,
6439 );
6440
6441 let query = if filter_completions { query } else { None };
6442 let matches_task = menu.do_async_filtering(
6443 query.unwrap_or_default(),
6444 buffer_position,
6445 &buffer,
6446 cx,
6447 );
6448 (menu, matches_task)
6449 }) else {
6450 return;
6451 };
6452
6453 let matches = matches_task.await;
6454
6455 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6456 // Newer menu already set, so exit.
6457 if let Some(CodeContextMenu::Completions(prev_menu)) =
6458 editor.context_menu.borrow().as_ref()
6459 && prev_menu.id > id
6460 {
6461 return;
6462 };
6463
6464 // Only valid to take prev_menu because either the new menu is immediately set
6465 // below, or the menu is hidden.
6466 if let Some(CodeContextMenu::Completions(prev_menu)) =
6467 editor.context_menu.borrow_mut().take()
6468 {
6469 let position_matches =
6470 if prev_menu.initial_position == menu.initial_position {
6471 true
6472 } else {
6473 let snapshot = editor.buffer.read(cx).read(cx);
6474 prev_menu.initial_position.to_offset(&snapshot)
6475 == menu.initial_position.to_offset(&snapshot)
6476 };
6477 if position_matches {
6478 // Preserve markdown cache before `set_filter_results` because it will
6479 // try to populate the documentation cache.
6480 menu.preserve_markdown_cache(prev_menu);
6481 }
6482 };
6483
6484 menu.set_filter_results(matches, provider, window, cx);
6485 }) else {
6486 return;
6487 };
6488
6489 menu.visible().then_some(menu)
6490 };
6491
6492 editor
6493 .update_in(cx, |editor, window, cx| {
6494 if editor.focus_handle.is_focused(window)
6495 && let Some(menu) = menu
6496 {
6497 *editor.context_menu.borrow_mut() =
6498 Some(CodeContextMenu::Completions(menu));
6499
6500 crate::hover_popover::hide_hover(editor, cx);
6501 if editor.show_edit_predictions_in_menu() {
6502 editor.update_visible_edit_prediction(window, cx);
6503 } else {
6504 editor
6505 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6506 }
6507
6508 cx.notify();
6509 return;
6510 }
6511
6512 if editor.completion_tasks.len() <= 1 {
6513 // If there are no more completion tasks and the last menu was empty, we should hide it.
6514 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6515 // If it was already hidden and we don't show edit predictions in the menu,
6516 // we should also show the edit prediction when available.
6517 if was_hidden && editor.show_edit_predictions_in_menu() {
6518 editor.update_visible_edit_prediction(window, cx);
6519 }
6520 }
6521 })
6522 .ok();
6523 });
6524
6525 self.completion_tasks.push((id, task));
6526 }
6527
6528 #[cfg(any(test, feature = "test-support"))]
6529 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6530 let menu = self.context_menu.borrow();
6531 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6532 let completions = menu.completions.borrow();
6533 Some(completions.to_vec())
6534 } else {
6535 None
6536 }
6537 }
6538
6539 pub fn with_completions_menu_matching_id<R>(
6540 &self,
6541 id: CompletionId,
6542 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6543 ) -> R {
6544 let mut context_menu = self.context_menu.borrow_mut();
6545 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6546 return f(None);
6547 };
6548 if completions_menu.id != id {
6549 return f(None);
6550 }
6551 f(Some(completions_menu))
6552 }
6553
6554 pub fn confirm_completion(
6555 &mut self,
6556 action: &ConfirmCompletion,
6557 window: &mut Window,
6558 cx: &mut Context<Self>,
6559 ) -> Option<Task<Result<()>>> {
6560 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6561 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6562 }
6563
6564 pub fn confirm_completion_insert(
6565 &mut self,
6566 _: &ConfirmCompletionInsert,
6567 window: &mut Window,
6568 cx: &mut Context<Self>,
6569 ) -> Option<Task<Result<()>>> {
6570 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6571 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6572 }
6573
6574 pub fn confirm_completion_replace(
6575 &mut self,
6576 _: &ConfirmCompletionReplace,
6577 window: &mut Window,
6578 cx: &mut Context<Self>,
6579 ) -> Option<Task<Result<()>>> {
6580 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6581 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6582 }
6583
6584 pub fn compose_completion(
6585 &mut self,
6586 action: &ComposeCompletion,
6587 window: &mut Window,
6588 cx: &mut Context<Self>,
6589 ) -> Option<Task<Result<()>>> {
6590 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6591 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6592 }
6593
6594 fn do_completion(
6595 &mut self,
6596 item_ix: Option<usize>,
6597 intent: CompletionIntent,
6598 window: &mut Window,
6599 cx: &mut Context<Editor>,
6600 ) -> Option<Task<Result<()>>> {
6601 use language::ToOffset as _;
6602
6603 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6604 else {
6605 return None;
6606 };
6607
6608 let candidate_id = {
6609 let entries = completions_menu.entries.borrow();
6610 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6611 if self.show_edit_predictions_in_menu() {
6612 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6613 }
6614 mat.candidate_id
6615 };
6616
6617 let completion = completions_menu
6618 .completions
6619 .borrow()
6620 .get(candidate_id)?
6621 .clone();
6622 cx.stop_propagation();
6623
6624 let buffer_handle = completions_menu.buffer.clone();
6625 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
6626 let (initial_position, _) =
6627 multibuffer_snapshot.anchor_to_buffer_anchor(completions_menu.initial_position)?;
6628
6629 let CompletionEdit {
6630 new_text,
6631 snippet,
6632 replace_range,
6633 } = process_completion_for_edit(&completion, intent, &buffer_handle, &initial_position, cx);
6634
6635 let buffer = buffer_handle.read(cx).snapshot();
6636 let newest_selection = self.selections.newest_anchor();
6637
6638 let Some(replace_range_multibuffer) =
6639 multibuffer_snapshot.buffer_anchor_range_to_anchor_range(replace_range.clone())
6640 else {
6641 return None;
6642 };
6643
6644 let Some((buffer_snapshot, newest_range_buffer)) =
6645 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(newest_selection.range())
6646 else {
6647 return None;
6648 };
6649
6650 let old_text = buffer
6651 .text_for_range(replace_range.clone())
6652 .collect::<String>();
6653 let lookbehind = newest_range_buffer
6654 .start
6655 .to_offset(buffer_snapshot)
6656 .saturating_sub(replace_range.start.to_offset(&buffer_snapshot));
6657 let lookahead = replace_range
6658 .end
6659 .to_offset(&buffer_snapshot)
6660 .saturating_sub(newest_range_buffer.end.to_offset(&buffer));
6661 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6662 let suffix = &old_text[lookbehind.min(old_text.len())..];
6663
6664 let selections = self
6665 .selections
6666 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6667 let mut ranges = Vec::new();
6668 let mut all_commit_ranges = Vec::new();
6669 let mut linked_edits = LinkedEdits::new();
6670
6671 let text: Arc<str> = new_text.clone().into();
6672 for selection in &selections {
6673 let range = if selection.id == newest_selection.id {
6674 replace_range_multibuffer.clone()
6675 } else {
6676 let mut range = selection.range();
6677
6678 // if prefix is present, don't duplicate it
6679 if multibuffer_snapshot
6680 .contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix)
6681 {
6682 range.start = range.start.saturating_sub_usize(lookbehind);
6683
6684 // if suffix is also present, mimic the newest cursor and replace it
6685 if selection.id != newest_selection.id
6686 && multibuffer_snapshot.contains_str_at(range.end, suffix)
6687 {
6688 range.end += lookahead;
6689 }
6690 }
6691 range.to_anchors(&multibuffer_snapshot)
6692 };
6693
6694 ranges.push(range.clone());
6695
6696 let start_anchor = multibuffer_snapshot.anchor_before(range.start);
6697 let end_anchor = multibuffer_snapshot.anchor_after(range.end);
6698
6699 if let Some((buffer_snapshot_2, anchor_range)) =
6700 multibuffer_snapshot.anchor_range_to_buffer_anchor_range(start_anchor..end_anchor)
6701 && buffer_snapshot_2.remote_id() == buffer_snapshot.remote_id()
6702 {
6703 all_commit_ranges.push(anchor_range.clone());
6704 if !self.linked_edit_ranges.is_empty() {
6705 linked_edits.push(&self, anchor_range, text.clone(), cx);
6706 }
6707 }
6708 }
6709
6710 let common_prefix_len = old_text
6711 .chars()
6712 .zip(new_text.chars())
6713 .take_while(|(a, b)| a == b)
6714 .map(|(a, _)| a.len_utf8())
6715 .sum::<usize>();
6716
6717 cx.emit(EditorEvent::InputHandled {
6718 utf16_range_to_replace: None,
6719 text: new_text[common_prefix_len..].into(),
6720 });
6721
6722 let tx_id = self.transact(window, cx, |editor, window, cx| {
6723 if let Some(mut snippet) = snippet {
6724 snippet.text = new_text.to_string();
6725 let offset_ranges = ranges
6726 .iter()
6727 .map(|range| range.to_offset(&multibuffer_snapshot))
6728 .collect::<Vec<_>>();
6729 editor
6730 .insert_snippet(&offset_ranges, snippet, window, cx)
6731 .log_err();
6732 } else {
6733 editor.buffer.update(cx, |multi_buffer, cx| {
6734 let auto_indent = match completion.insert_text_mode {
6735 Some(InsertTextMode::AS_IS) => None,
6736 _ => editor.autoindent_mode.clone(),
6737 };
6738 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6739 multi_buffer.edit(edits, auto_indent, cx);
6740 });
6741 }
6742 linked_edits.apply(cx);
6743 editor.refresh_edit_prediction(true, false, window, cx);
6744 });
6745 self.invalidate_autoclose_regions(
6746 &self.selections.disjoint_anchors_arc(),
6747 &multibuffer_snapshot,
6748 );
6749
6750 let show_new_completions_on_confirm = completion
6751 .confirm
6752 .as_ref()
6753 .is_some_and(|confirm| confirm(intent, window, cx));
6754 if show_new_completions_on_confirm {
6755 self.open_or_update_completions_menu(None, None, false, window, cx);
6756 }
6757
6758 let provider = self.completion_provider.as_ref()?;
6759
6760 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6761 let command = lsp_store.as_ref().and_then(|lsp_store| {
6762 let CompletionSource::Lsp {
6763 lsp_completion,
6764 server_id,
6765 ..
6766 } = &completion.source
6767 else {
6768 return None;
6769 };
6770 let lsp_command = lsp_completion.command.as_ref()?;
6771 let available_commands = lsp_store
6772 .read(cx)
6773 .lsp_server_capabilities
6774 .get(server_id)
6775 .and_then(|server_capabilities| {
6776 server_capabilities
6777 .execute_command_provider
6778 .as_ref()
6779 .map(|options| options.commands.as_slice())
6780 })?;
6781 if available_commands.contains(&lsp_command.command) {
6782 Some(CodeAction {
6783 server_id: *server_id,
6784 range: language::Anchor::min_min_range_for_buffer(buffer.remote_id()),
6785 lsp_action: LspAction::Command(lsp_command.clone()),
6786 resolved: false,
6787 })
6788 } else {
6789 None
6790 }
6791 });
6792
6793 drop(completion);
6794 let apply_edits = provider.apply_additional_edits_for_completion(
6795 buffer_handle.clone(),
6796 completions_menu.completions.clone(),
6797 candidate_id,
6798 true,
6799 all_commit_ranges,
6800 cx,
6801 );
6802
6803 let editor_settings = EditorSettings::get_global(cx);
6804 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6805 // After the code completion is finished, users often want to know what signatures are needed.
6806 // so we should automatically call signature_help
6807 self.show_signature_help(&ShowSignatureHelp, window, cx);
6808 }
6809
6810 Some(cx.spawn_in(window, async move |editor, cx| {
6811 let additional_edits_tx = apply_edits.await?;
6812
6813 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6814 let title = command.lsp_action.title().to_owned();
6815 let project_transaction = lsp_store
6816 .update(cx, |lsp_store, cx| {
6817 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6818 })
6819 .await
6820 .context("applying post-completion command")?;
6821 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6822 Self::open_project_transaction(
6823 &editor,
6824 workspace.downgrade(),
6825 project_transaction,
6826 title,
6827 cx,
6828 )
6829 .await?;
6830 }
6831 }
6832
6833 if let Some(tx_id) = tx_id
6834 && let Some(additional_edits_tx) = additional_edits_tx
6835 {
6836 editor
6837 .update(cx, |editor, cx| {
6838 editor.buffer.update(cx, |buffer, cx| {
6839 buffer.merge_transactions(additional_edits_tx.id, tx_id, cx)
6840 });
6841 })
6842 .context("merge transactions")?;
6843 }
6844
6845 Ok(())
6846 }))
6847 }
6848
6849 pub fn toggle_code_actions(
6850 &mut self,
6851 action: &ToggleCodeActions,
6852 window: &mut Window,
6853 cx: &mut Context<Self>,
6854 ) {
6855 let quick_launch = action.quick_launch;
6856 let mut context_menu = self.context_menu.borrow_mut();
6857 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6858 if code_actions.deployed_from == action.deployed_from {
6859 // Toggle if we're selecting the same one
6860 *context_menu = None;
6861 cx.notify();
6862 return;
6863 } else {
6864 // Otherwise, clear it and start a new one
6865 *context_menu = None;
6866 cx.notify();
6867 }
6868 }
6869 drop(context_menu);
6870 let snapshot = self.snapshot(window, cx);
6871 let deployed_from = action.deployed_from.clone();
6872 let action = action.clone();
6873 self.completion_tasks.clear();
6874 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6875
6876 let multibuffer_point = match &action.deployed_from {
6877 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6878 DisplayPoint::new(*row, 0).to_point(&snapshot)
6879 }
6880 _ => self
6881 .selections
6882 .newest::<Point>(&snapshot.display_snapshot)
6883 .head(),
6884 };
6885 let Some((buffer, buffer_row)) = snapshot
6886 .buffer_snapshot()
6887 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6888 .and_then(|(buffer_snapshot, range)| {
6889 self.buffer()
6890 .read(cx)
6891 .buffer(buffer_snapshot.remote_id())
6892 .map(|buffer| (buffer, range.start.row))
6893 })
6894 else {
6895 return;
6896 };
6897 let buffer_id = buffer.read(cx).remote_id();
6898 let tasks = self
6899 .runnables
6900 .runnables((buffer_id, buffer_row))
6901 .map(|t| Arc::new(t.to_owned()));
6902
6903 if !self.focus_handle.is_focused(window) {
6904 return;
6905 }
6906 let project = self.project.clone();
6907
6908 let code_actions_task = match deployed_from {
6909 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6910 _ => self.code_actions(buffer_row, window, cx),
6911 };
6912
6913 let runnable_task = match deployed_from {
6914 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6915 _ => {
6916 let mut task_context_task = Task::ready(None);
6917 if let Some(tasks) = &tasks
6918 && let Some(project) = project
6919 {
6920 task_context_task =
6921 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6922 }
6923
6924 cx.spawn_in(window, {
6925 let buffer = buffer.clone();
6926 async move |editor, cx| {
6927 let task_context = task_context_task.await;
6928
6929 let resolved_tasks =
6930 tasks
6931 .zip(task_context.clone())
6932 .map(|(tasks, task_context)| ResolvedTasks {
6933 templates: tasks.resolve(&task_context).collect(),
6934 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6935 multibuffer_point.row,
6936 tasks.column,
6937 )),
6938 });
6939 let debug_scenarios = editor
6940 .update(cx, |editor, cx| {
6941 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6942 })?
6943 .await;
6944 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6945 }
6946 })
6947 }
6948 };
6949
6950 cx.spawn_in(window, async move |editor, cx| {
6951 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6952 let code_actions = code_actions_task.await;
6953 let spawn_straight_away = quick_launch
6954 && resolved_tasks
6955 .as_ref()
6956 .is_some_and(|tasks| tasks.templates.len() == 1)
6957 && code_actions
6958 .as_ref()
6959 .is_none_or(|actions| actions.is_empty())
6960 && debug_scenarios.is_empty();
6961
6962 editor.update_in(cx, |editor, window, cx| {
6963 crate::hover_popover::hide_hover(editor, cx);
6964 let actions = CodeActionContents::new(
6965 resolved_tasks,
6966 code_actions,
6967 debug_scenarios,
6968 task_context.unwrap_or_default(),
6969 );
6970
6971 // Don't show the menu if there are no actions available
6972 if actions.is_empty() {
6973 cx.notify();
6974 return Task::ready(Ok(()));
6975 }
6976
6977 *editor.context_menu.borrow_mut() =
6978 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6979 buffer,
6980 actions,
6981 selected_item: Default::default(),
6982 scroll_handle: UniformListScrollHandle::default(),
6983 deployed_from,
6984 }));
6985 cx.notify();
6986 if spawn_straight_away
6987 && let Some(task) = editor.confirm_code_action(
6988 &ConfirmCodeAction { item_ix: Some(0) },
6989 window,
6990 cx,
6991 )
6992 {
6993 return task;
6994 }
6995
6996 Task::ready(Ok(()))
6997 })
6998 })
6999 .detach_and_log_err(cx);
7000 }
7001
7002 fn debug_scenarios(
7003 &mut self,
7004 resolved_tasks: &Option<ResolvedTasks>,
7005 buffer: &Entity<Buffer>,
7006 cx: &mut App,
7007 ) -> Task<Vec<task::DebugScenario>> {
7008 maybe!({
7009 let project = self.project()?;
7010 let dap_store = project.read(cx).dap_store();
7011 let mut scenarios = vec![];
7012 let resolved_tasks = resolved_tasks.as_ref()?;
7013 let buffer = buffer.read(cx);
7014 let language = buffer.language()?;
7015 let debug_adapter = LanguageSettings::for_buffer(&buffer, cx)
7016 .debuggers
7017 .first()
7018 .map(SharedString::from)
7019 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
7020
7021 dap_store.update(cx, |dap_store, cx| {
7022 for (_, task) in &resolved_tasks.templates {
7023 let maybe_scenario = dap_store.debug_scenario_for_build_task(
7024 task.original_task().clone(),
7025 debug_adapter.clone().into(),
7026 task.display_label().to_owned().into(),
7027 cx,
7028 );
7029 scenarios.push(maybe_scenario);
7030 }
7031 });
7032 Some(cx.background_spawn(async move {
7033 futures::future::join_all(scenarios)
7034 .await
7035 .into_iter()
7036 .flatten()
7037 .collect::<Vec<_>>()
7038 }))
7039 })
7040 .unwrap_or_else(|| Task::ready(vec![]))
7041 }
7042
7043 fn code_actions(
7044 &mut self,
7045 buffer_row: u32,
7046 window: &mut Window,
7047 cx: &mut Context<Self>,
7048 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
7049 let mut task = self.code_actions_task.take();
7050 cx.spawn_in(window, async move |editor, cx| {
7051 while let Some(prev_task) = task {
7052 prev_task.await.log_err();
7053 task = editor
7054 .update(cx, |this, _| this.code_actions_task.take())
7055 .ok()?;
7056 }
7057
7058 editor
7059 .update(cx, |editor, cx| {
7060 editor
7061 .available_code_actions
7062 .clone()
7063 .and_then(|(location, code_actions)| {
7064 let snapshot = location.buffer.read(cx).snapshot();
7065 let point_range = location.range.to_point(&snapshot);
7066 let point_range = point_range.start.row..=point_range.end.row;
7067 if point_range.contains(&buffer_row) {
7068 Some(code_actions)
7069 } else {
7070 None
7071 }
7072 })
7073 })
7074 .ok()
7075 .flatten()
7076 })
7077 }
7078
7079 pub fn confirm_code_action(
7080 &mut self,
7081 action: &ConfirmCodeAction,
7082 window: &mut Window,
7083 cx: &mut Context<Self>,
7084 ) -> Option<Task<Result<()>>> {
7085 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
7086
7087 let actions_menu =
7088 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
7089 menu
7090 } else {
7091 return None;
7092 };
7093
7094 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
7095 let action = actions_menu.actions.get(action_ix)?;
7096 let title = action.label();
7097 let buffer = actions_menu.buffer;
7098 let workspace = self.workspace()?;
7099
7100 match action {
7101 CodeActionsItem::Task(task_source_kind, resolved_task) => {
7102 workspace.update(cx, |workspace, cx| {
7103 workspace.schedule_resolved_task(
7104 task_source_kind,
7105 resolved_task,
7106 false,
7107 window,
7108 cx,
7109 );
7110
7111 Some(Task::ready(Ok(())))
7112 })
7113 }
7114 CodeActionsItem::CodeAction { action, provider } => {
7115 let apply_code_action =
7116 provider.apply_code_action(buffer, action, true, window, cx);
7117 let workspace = workspace.downgrade();
7118 Some(cx.spawn_in(window, async move |editor, cx| {
7119 let project_transaction = apply_code_action.await?;
7120 Self::open_project_transaction(
7121 &editor,
7122 workspace,
7123 project_transaction,
7124 title,
7125 cx,
7126 )
7127 .await
7128 }))
7129 }
7130 CodeActionsItem::DebugScenario(scenario) => {
7131 let context = actions_menu.actions.context.into();
7132
7133 workspace.update(cx, |workspace, cx| {
7134 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
7135 workspace.start_debug_session(
7136 scenario,
7137 context,
7138 Some(buffer),
7139 None,
7140 window,
7141 cx,
7142 );
7143 });
7144 Some(Task::ready(Ok(())))
7145 }
7146 }
7147 }
7148
7149 fn open_transaction_for_hidden_buffers(
7150 workspace: Entity<Workspace>,
7151 transaction: ProjectTransaction,
7152 title: String,
7153 window: &mut Window,
7154 cx: &mut Context<Self>,
7155 ) {
7156 if transaction.0.is_empty() {
7157 return;
7158 }
7159
7160 let edited_buffers_already_open = {
7161 let other_editors: Vec<Entity<Editor>> = workspace
7162 .read(cx)
7163 .panes()
7164 .iter()
7165 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
7166 .filter(|editor| editor.entity_id() != cx.entity_id())
7167 .collect();
7168
7169 transaction.0.keys().all(|buffer| {
7170 other_editors.iter().any(|editor| {
7171 let multi_buffer = editor.read(cx).buffer();
7172 multi_buffer.read(cx).is_singleton()
7173 && multi_buffer
7174 .read(cx)
7175 .as_singleton()
7176 .map_or(false, |singleton| {
7177 singleton.entity_id() == buffer.entity_id()
7178 })
7179 })
7180 })
7181 };
7182 if !edited_buffers_already_open {
7183 let workspace = workspace.downgrade();
7184 cx.defer_in(window, move |_, window, cx| {
7185 cx.spawn_in(window, async move |editor, cx| {
7186 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
7187 .await
7188 .ok()
7189 })
7190 .detach();
7191 });
7192 }
7193 }
7194
7195 pub async fn open_project_transaction(
7196 editor: &WeakEntity<Editor>,
7197 workspace: WeakEntity<Workspace>,
7198 transaction: ProjectTransaction,
7199 title: String,
7200 cx: &mut AsyncWindowContext,
7201 ) -> Result<()> {
7202 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7203 cx.update(|_, cx| {
7204 entries.sort_unstable_by_key(|(buffer, _)| {
7205 buffer.read(cx).file().map(|f| f.path().clone())
7206 });
7207 })?;
7208 if entries.is_empty() {
7209 return Ok(());
7210 }
7211
7212 // If the project transaction's edits are all contained within this editor, then
7213 // avoid opening a new editor to display them.
7214
7215 if let [(buffer, transaction)] = &*entries {
7216 let cursor_excerpt = editor.update(cx, |editor, cx| {
7217 let snapshot = editor.buffer().read(cx).snapshot(cx);
7218 let head = editor.selections.newest_anchor().head();
7219 let (buffer_snapshot, excerpt_range) = snapshot.excerpt_containing(head..head)?;
7220 if buffer_snapshot.remote_id() != buffer.read(cx).remote_id() {
7221 return None;
7222 }
7223 Some(excerpt_range)
7224 })?;
7225
7226 if let Some(excerpt_range) = cursor_excerpt {
7227 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7228 let excerpt_range = excerpt_range.context.to_offset(buffer);
7229 buffer
7230 .edited_ranges_for_transaction::<usize>(transaction)
7231 .all(|range| {
7232 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7233 })
7234 });
7235
7236 if all_edits_within_excerpt {
7237 return Ok(());
7238 }
7239 }
7240 }
7241
7242 let mut ranges_to_highlight = Vec::new();
7243 let excerpt_buffer = cx.new(|cx| {
7244 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7245 for (buffer_handle, transaction) in &entries {
7246 let edited_ranges = buffer_handle
7247 .read(cx)
7248 .edited_ranges_for_transaction::<Point>(transaction)
7249 .collect::<Vec<_>>();
7250 multibuffer.set_excerpts_for_path(
7251 PathKey::for_buffer(buffer_handle, cx),
7252 buffer_handle.clone(),
7253 edited_ranges.clone(),
7254 multibuffer_context_lines(cx),
7255 cx,
7256 );
7257 let snapshot = multibuffer.snapshot(cx);
7258 let buffer_snapshot = buffer_handle.read(cx).snapshot();
7259 ranges_to_highlight.extend(edited_ranges.into_iter().filter_map(|range| {
7260 let text_range = buffer_snapshot.anchor_range_inside(range);
7261 let start = snapshot.anchor_in_buffer(text_range.start)?;
7262 let end = snapshot.anchor_in_buffer(text_range.end)?;
7263 Some(start..end)
7264 }));
7265 }
7266 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7267 multibuffer
7268 });
7269
7270 workspace.update_in(cx, |workspace, window, cx| {
7271 let project = workspace.project().clone();
7272 let editor =
7273 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7274 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7275 editor.update(cx, |editor, cx| {
7276 editor.highlight_background(
7277 HighlightKey::Editor,
7278 &ranges_to_highlight,
7279 |_, theme| theme.colors().editor_highlighted_line_background,
7280 cx,
7281 );
7282 });
7283 })?;
7284
7285 Ok(())
7286 }
7287
7288 pub fn clear_code_action_providers(&mut self) {
7289 self.code_action_providers.clear();
7290 self.available_code_actions.take();
7291 }
7292
7293 pub fn add_code_action_provider(
7294 &mut self,
7295 provider: Rc<dyn CodeActionProvider>,
7296 window: &mut Window,
7297 cx: &mut Context<Self>,
7298 ) {
7299 if self
7300 .code_action_providers
7301 .iter()
7302 .any(|existing_provider| existing_provider.id() == provider.id())
7303 {
7304 return;
7305 }
7306
7307 self.code_action_providers.push(provider);
7308 self.refresh_code_actions(window, cx);
7309 }
7310
7311 pub fn remove_code_action_provider(
7312 &mut self,
7313 id: Arc<str>,
7314 window: &mut Window,
7315 cx: &mut Context<Self>,
7316 ) {
7317 self.code_action_providers
7318 .retain(|provider| provider.id() != id);
7319 self.refresh_code_actions(window, cx);
7320 }
7321
7322 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7323 !self.code_action_providers.is_empty()
7324 && EditorSettings::get_global(cx).toolbar.code_actions
7325 }
7326
7327 pub fn has_available_code_actions(&self) -> bool {
7328 self.available_code_actions
7329 .as_ref()
7330 .is_some_and(|(_, actions)| !actions.is_empty())
7331 }
7332
7333 fn render_inline_code_actions(
7334 &self,
7335 icon_size: ui::IconSize,
7336 display_row: DisplayRow,
7337 is_active: bool,
7338 cx: &mut Context<Self>,
7339 ) -> AnyElement {
7340 let show_tooltip = !self.context_menu_visible();
7341 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7342 .icon_size(icon_size)
7343 .shape(ui::IconButtonShape::Square)
7344 .icon_color(ui::Color::Hidden)
7345 .toggle_state(is_active)
7346 .when(show_tooltip, |this| {
7347 this.tooltip({
7348 let focus_handle = self.focus_handle.clone();
7349 move |_window, cx| {
7350 Tooltip::for_action_in(
7351 "Toggle Code Actions",
7352 &ToggleCodeActions {
7353 deployed_from: None,
7354 quick_launch: false,
7355 },
7356 &focus_handle,
7357 cx,
7358 )
7359 }
7360 })
7361 })
7362 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7363 window.focus(&editor.focus_handle(cx), cx);
7364 editor.toggle_code_actions(
7365 &crate::actions::ToggleCodeActions {
7366 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7367 display_row,
7368 )),
7369 quick_launch: false,
7370 },
7371 window,
7372 cx,
7373 );
7374 }))
7375 .into_any_element()
7376 }
7377
7378 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7379 &self.context_menu
7380 }
7381
7382 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7383 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7384 cx.background_executor()
7385 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7386 .await;
7387
7388 let (start_buffer, start, _, end, _newest_selection) = this
7389 .update(cx, |this, cx| {
7390 let newest_selection = this.selections.newest_anchor().clone();
7391 if newest_selection.head().diff_base_anchor().is_some() {
7392 return None;
7393 }
7394 let display_snapshot = this.display_snapshot(cx);
7395 let newest_selection_adjusted =
7396 this.selections.newest_adjusted(&display_snapshot);
7397 let buffer = this.buffer.read(cx);
7398
7399 let (start_buffer, start) =
7400 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7401 let (end_buffer, end) =
7402 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7403
7404 Some((start_buffer, start, end_buffer, end, newest_selection))
7405 })?
7406 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7407 .context(
7408 "Expected selection to lie in a single buffer when refreshing code actions",
7409 )?;
7410 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7411 let providers = this.code_action_providers.clone();
7412 let tasks = this
7413 .code_action_providers
7414 .iter()
7415 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7416 .collect::<Vec<_>>();
7417 (providers, tasks)
7418 })?;
7419
7420 let mut actions = Vec::new();
7421 for (provider, provider_actions) in
7422 providers.into_iter().zip(future::join_all(tasks).await)
7423 {
7424 if let Some(provider_actions) = provider_actions.log_err() {
7425 actions.extend(provider_actions.into_iter().map(|action| {
7426 AvailableCodeAction {
7427 action,
7428 provider: provider.clone(),
7429 }
7430 }));
7431 }
7432 }
7433
7434 this.update(cx, |this, cx| {
7435 this.available_code_actions = if actions.is_empty() {
7436 None
7437 } else {
7438 Some((
7439 Location {
7440 buffer: start_buffer,
7441 range: start..end,
7442 },
7443 actions.into(),
7444 ))
7445 };
7446 cx.notify();
7447 })
7448 }));
7449 }
7450
7451 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7452 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7453 self.show_git_blame_inline = false;
7454
7455 self.show_git_blame_inline_delay_task =
7456 Some(cx.spawn_in(window, async move |this, cx| {
7457 cx.background_executor().timer(delay).await;
7458
7459 this.update(cx, |this, cx| {
7460 this.show_git_blame_inline = true;
7461 cx.notify();
7462 })
7463 .log_err();
7464 }));
7465 }
7466 }
7467
7468 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7469 let snapshot = self.snapshot(window, cx);
7470 let cursor = self
7471 .selections
7472 .newest::<Point>(&snapshot.display_snapshot)
7473 .head();
7474 let Some((buffer, point)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor) else {
7475 return;
7476 };
7477
7478 if self.blame.is_none() {
7479 self.start_git_blame(true, window, cx);
7480 }
7481 let Some(blame) = self.blame.as_ref() else {
7482 return;
7483 };
7484
7485 let row_info = RowInfo {
7486 buffer_id: Some(buffer.remote_id()),
7487 buffer_row: Some(point.row),
7488 ..Default::default()
7489 };
7490 let Some((buffer, blame_entry)) = blame
7491 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7492 .flatten()
7493 else {
7494 return;
7495 };
7496
7497 let anchor = self.selections.newest_anchor().head();
7498 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7499 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7500 self.show_blame_popover(
7501 buffer,
7502 &blame_entry,
7503 position + last_bounds.origin,
7504 true,
7505 cx,
7506 );
7507 };
7508 }
7509
7510 fn show_blame_popover(
7511 &mut self,
7512 buffer: BufferId,
7513 blame_entry: &BlameEntry,
7514 position: gpui::Point<Pixels>,
7515 ignore_timeout: bool,
7516 cx: &mut Context<Self>,
7517 ) {
7518 if let Some(state) = &mut self.inline_blame_popover {
7519 state.hide_task.take();
7520 } else {
7521 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7522 let blame_entry = blame_entry.clone();
7523 let show_task = cx.spawn(async move |editor, cx| {
7524 if !ignore_timeout {
7525 cx.background_executor()
7526 .timer(std::time::Duration::from_millis(blame_popover_delay))
7527 .await;
7528 }
7529 editor
7530 .update(cx, |editor, cx| {
7531 editor.inline_blame_popover_show_task.take();
7532 let Some(blame) = editor.blame.as_ref() else {
7533 return;
7534 };
7535 let blame = blame.read(cx);
7536 let details = blame.details_for_entry(buffer, &blame_entry);
7537 let markdown = cx.new(|cx| {
7538 Markdown::new(
7539 details
7540 .as_ref()
7541 .map(|message| message.message.clone())
7542 .unwrap_or_default(),
7543 None,
7544 None,
7545 cx,
7546 )
7547 });
7548 editor.inline_blame_popover = Some(InlineBlamePopover {
7549 position,
7550 hide_task: None,
7551 popover_bounds: None,
7552 popover_state: InlineBlamePopoverState {
7553 scroll_handle: ScrollHandle::new(),
7554 commit_message: details,
7555 markdown,
7556 },
7557 keyboard_grace: ignore_timeout,
7558 });
7559 cx.notify();
7560 })
7561 .ok();
7562 });
7563 self.inline_blame_popover_show_task = Some(show_task);
7564 }
7565 }
7566
7567 pub fn has_mouse_context_menu(&self) -> bool {
7568 self.mouse_context_menu.is_some()
7569 }
7570
7571 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7572 self.inline_blame_popover_show_task.take();
7573 if let Some(state) = &mut self.inline_blame_popover {
7574 let hide_task = cx.spawn(async move |editor, cx| {
7575 if !ignore_timeout {
7576 cx.background_executor()
7577 .timer(std::time::Duration::from_millis(100))
7578 .await;
7579 }
7580 editor
7581 .update(cx, |editor, cx| {
7582 editor.inline_blame_popover.take();
7583 cx.notify();
7584 })
7585 .ok();
7586 });
7587 state.hide_task = Some(hide_task);
7588 true
7589 } else {
7590 false
7591 }
7592 }
7593
7594 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7595 if self.pending_rename.is_some() {
7596 return None;
7597 }
7598
7599 let provider = self.semantics_provider.clone()?;
7600 let buffer = self.buffer.read(cx);
7601 let newest_selection = self.selections.newest_anchor().clone();
7602 let cursor_position = newest_selection.head();
7603 let (cursor_buffer, cursor_buffer_position) =
7604 buffer.text_anchor_for_position(cursor_position, cx)?;
7605 let (tail_buffer, tail_buffer_position) =
7606 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7607 if cursor_buffer != tail_buffer {
7608 return None;
7609 }
7610
7611 let snapshot = cursor_buffer.read(cx).snapshot();
7612 let word_ranges = cx.background_spawn(async move {
7613 // this might look odd to put on the background thread, but
7614 // `surrounding_word` can be quite expensive as it calls into
7615 // tree-sitter language scopes
7616 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7617 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7618 (start_word_range, end_word_range)
7619 });
7620
7621 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7622 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7623 let (start_word_range, end_word_range) = word_ranges.await;
7624 if start_word_range != end_word_range {
7625 this.update(cx, |this, cx| {
7626 this.document_highlights_task.take();
7627 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7628 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7629 })
7630 .ok();
7631 return;
7632 }
7633 cx.background_executor()
7634 .timer(Duration::from_millis(debounce))
7635 .await;
7636
7637 let highlights = if let Some(highlights) = cx.update(|cx| {
7638 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7639 }) {
7640 highlights.await.log_err()
7641 } else {
7642 None
7643 };
7644
7645 if let Some(highlights) = highlights {
7646 this.update(cx, |this, cx| {
7647 if this.pending_rename.is_some() {
7648 return;
7649 }
7650
7651 let buffer = this.buffer.read(cx);
7652 if buffer
7653 .text_anchor_for_position(cursor_position, cx)
7654 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7655 {
7656 return;
7657 }
7658
7659 let mut write_ranges = Vec::new();
7660 let mut read_ranges = Vec::new();
7661 let multibuffer_snapshot = buffer.snapshot(cx);
7662 for highlight in highlights {
7663 for range in
7664 multibuffer_snapshot.buffer_range_to_excerpt_ranges(highlight.range)
7665 {
7666 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7667 write_ranges.push(range);
7668 } else {
7669 read_ranges.push(range);
7670 }
7671 }
7672 }
7673
7674 this.highlight_background(
7675 HighlightKey::DocumentHighlightRead,
7676 &read_ranges,
7677 |_, theme| theme.colors().editor_document_highlight_read_background,
7678 cx,
7679 );
7680 this.highlight_background(
7681 HighlightKey::DocumentHighlightWrite,
7682 &write_ranges,
7683 |_, theme| theme.colors().editor_document_highlight_write_background,
7684 cx,
7685 );
7686 cx.notify();
7687 })
7688 .log_err();
7689 }
7690 }));
7691 None
7692 }
7693
7694 fn prepare_highlight_query_from_selection(
7695 &mut self,
7696 snapshot: &DisplaySnapshot,
7697 cx: &mut Context<Editor>,
7698 ) -> Option<(String, Range<Anchor>)> {
7699 if matches!(self.mode, EditorMode::SingleLine) {
7700 return None;
7701 }
7702 if !EditorSettings::get_global(cx).selection_highlight {
7703 return None;
7704 }
7705 if self.selections.count() != 1 || self.selections.line_mode() {
7706 return None;
7707 }
7708 let selection = self.selections.newest::<Point>(&snapshot);
7709 // If the selection spans multiple rows OR it is empty
7710 if selection.start.row != selection.end.row
7711 || selection.start.column == selection.end.column
7712 {
7713 return None;
7714 }
7715 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7716 let query = snapshot
7717 .buffer_snapshot()
7718 .text_for_range(selection_anchor_range.clone())
7719 .collect::<String>();
7720 if query.trim().is_empty() {
7721 return None;
7722 }
7723 Some((query, selection_anchor_range))
7724 }
7725
7726 #[ztracing::instrument(skip_all)]
7727 fn update_selection_occurrence_highlights(
7728 &mut self,
7729 multi_buffer_snapshot: MultiBufferSnapshot,
7730 query_text: String,
7731 query_range: Range<Anchor>,
7732 multi_buffer_range_to_query: Range<Point>,
7733 use_debounce: bool,
7734 window: &mut Window,
7735 cx: &mut Context<Editor>,
7736 ) -> Task<()> {
7737 cx.spawn_in(window, async move |editor, cx| {
7738 if use_debounce {
7739 cx.background_executor()
7740 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7741 .await;
7742 }
7743 let match_task = cx.background_spawn(async move {
7744 let buffer_ranges = multi_buffer_snapshot
7745 .range_to_buffer_ranges(
7746 multi_buffer_range_to_query.start..multi_buffer_range_to_query.end,
7747 )
7748 .into_iter()
7749 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7750 let mut match_ranges = Vec::new();
7751 let Ok(regex) = project::search::SearchQuery::text(
7752 query_text,
7753 false,
7754 false,
7755 false,
7756 Default::default(),
7757 Default::default(),
7758 false,
7759 None,
7760 ) else {
7761 return Vec::default();
7762 };
7763 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7764 for (buffer_snapshot, search_range, _) in buffer_ranges {
7765 match_ranges.extend(
7766 regex
7767 .search(
7768 &buffer_snapshot,
7769 Some(search_range.start.0..search_range.end.0),
7770 )
7771 .await
7772 .into_iter()
7773 .filter_map(|match_range| {
7774 let match_start = buffer_snapshot
7775 .anchor_after(search_range.start + match_range.start);
7776 let match_end = buffer_snapshot
7777 .anchor_before(search_range.start + match_range.end);
7778 {
7779 let range = multi_buffer_snapshot
7780 .anchor_in_buffer(match_start)?
7781 ..multi_buffer_snapshot.anchor_in_buffer(match_end)?;
7782 Some(range).filter(|match_anchor_range| {
7783 match_anchor_range != &query_range
7784 })
7785 }
7786 }),
7787 );
7788 }
7789 match_ranges
7790 });
7791 let match_ranges = match_task.await;
7792 editor
7793 .update_in(cx, |editor, _, cx| {
7794 if use_debounce {
7795 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7796 editor.debounced_selection_highlight_complete = true;
7797 } else if editor.debounced_selection_highlight_complete {
7798 return;
7799 }
7800 if !match_ranges.is_empty() {
7801 editor.highlight_background(
7802 HighlightKey::SelectedTextHighlight,
7803 &match_ranges,
7804 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7805 cx,
7806 )
7807 }
7808 })
7809 .log_err();
7810 })
7811 }
7812
7813 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7814 struct NewlineFold;
7815 let type_id = std::any::TypeId::of::<NewlineFold>();
7816 if !self.mode.is_single_line() {
7817 return;
7818 }
7819 let snapshot = self.snapshot(window, cx);
7820 if snapshot.buffer_snapshot().max_point().row == 0 {
7821 return;
7822 }
7823 let task = cx.background_spawn(async move {
7824 let new_newlines = snapshot
7825 .buffer_chars_at(MultiBufferOffset(0))
7826 .filter_map(|(c, i)| {
7827 if c == '\n' {
7828 Some(
7829 snapshot.buffer_snapshot().anchor_after(i)
7830 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7831 )
7832 } else {
7833 None
7834 }
7835 })
7836 .collect::<Vec<_>>();
7837 let existing_newlines = snapshot
7838 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7839 .filter_map(|fold| {
7840 if fold.placeholder.type_tag == Some(type_id) {
7841 Some(fold.range.start..fold.range.end)
7842 } else {
7843 None
7844 }
7845 })
7846 .collect::<Vec<_>>();
7847
7848 (new_newlines, existing_newlines)
7849 });
7850 self.folding_newlines = cx.spawn(async move |this, cx| {
7851 let (new_newlines, existing_newlines) = task.await;
7852 if new_newlines == existing_newlines {
7853 return;
7854 }
7855 let placeholder = FoldPlaceholder {
7856 render: Arc::new(move |_, _, cx| {
7857 div()
7858 .bg(cx.theme().status().hint_background)
7859 .border_b_1()
7860 .size_full()
7861 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7862 .border_color(cx.theme().status().hint)
7863 .child("\\n")
7864 .into_any()
7865 }),
7866 constrain_width: false,
7867 merge_adjacent: false,
7868 type_tag: Some(type_id),
7869 collapsed_text: None,
7870 };
7871 let creases = new_newlines
7872 .into_iter()
7873 .map(|range| Crease::simple(range, placeholder.clone()))
7874 .collect();
7875 this.update(cx, |this, cx| {
7876 this.display_map.update(cx, |display_map, cx| {
7877 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7878 display_map.fold(creases, cx);
7879 });
7880 })
7881 .ok();
7882 });
7883 }
7884
7885 #[ztracing::instrument(skip_all)]
7886 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7887 if !self.lsp_data_enabled() {
7888 return;
7889 }
7890 let cursor = self.selections.newest_anchor().head();
7891 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7892
7893 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7894 self.outline_symbols_at_cursor =
7895 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7896 cx.emit(EditorEvent::OutlineSymbolsChanged);
7897 cx.notify();
7898 } else {
7899 let syntax = cx.theme().syntax().clone();
7900 let background_task = cx.background_spawn(async move {
7901 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7902 });
7903 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7904 cx.spawn(async move |this, cx| {
7905 let symbols = background_task.await;
7906 this.update(cx, |this, cx| {
7907 this.outline_symbols_at_cursor = symbols;
7908 cx.emit(EditorEvent::OutlineSymbolsChanged);
7909 cx.notify();
7910 })
7911 .ok();
7912 });
7913 }
7914 }
7915
7916 #[ztracing::instrument(skip_all)]
7917 fn refresh_selected_text_highlights(
7918 &mut self,
7919 snapshot: &DisplaySnapshot,
7920 on_buffer_edit: bool,
7921 window: &mut Window,
7922 cx: &mut Context<Editor>,
7923 ) {
7924 let Some((query_text, query_range)) =
7925 self.prepare_highlight_query_from_selection(snapshot, cx)
7926 else {
7927 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7928 self.quick_selection_highlight_task.take();
7929 self.debounced_selection_highlight_task.take();
7930 self.debounced_selection_highlight_complete = false;
7931 return;
7932 };
7933 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7934 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7935 let query_changed = self
7936 .quick_selection_highlight_task
7937 .as_ref()
7938 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7939 if query_changed {
7940 self.debounced_selection_highlight_complete = false;
7941 }
7942 if on_buffer_edit || query_changed {
7943 self.quick_selection_highlight_task = Some((
7944 query_range.clone(),
7945 self.update_selection_occurrence_highlights(
7946 snapshot.buffer.clone(),
7947 query_text.clone(),
7948 query_range.clone(),
7949 self.multi_buffer_visible_range(&display_snapshot, cx),
7950 false,
7951 window,
7952 cx,
7953 ),
7954 ));
7955 }
7956 if on_buffer_edit
7957 || self
7958 .debounced_selection_highlight_task
7959 .as_ref()
7960 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7961 {
7962 let multi_buffer_start = multi_buffer_snapshot
7963 .anchor_before(MultiBufferOffset(0))
7964 .to_point(&multi_buffer_snapshot);
7965 let multi_buffer_end = multi_buffer_snapshot
7966 .anchor_after(multi_buffer_snapshot.len())
7967 .to_point(&multi_buffer_snapshot);
7968 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7969 self.debounced_selection_highlight_task = Some((
7970 query_range.clone(),
7971 self.update_selection_occurrence_highlights(
7972 snapshot.buffer.clone(),
7973 query_text,
7974 query_range,
7975 multi_buffer_full_range,
7976 true,
7977 window,
7978 cx,
7979 ),
7980 ));
7981 }
7982 }
7983
7984 pub fn multi_buffer_visible_range(
7985 &self,
7986 display_snapshot: &DisplaySnapshot,
7987 cx: &App,
7988 ) -> Range<Point> {
7989 let visible_start = self
7990 .scroll_manager
7991 .native_anchor(display_snapshot, cx)
7992 .anchor
7993 .to_point(display_snapshot.buffer_snapshot())
7994 .to_display_point(display_snapshot);
7995
7996 let mut target_end = visible_start;
7997 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
7998
7999 visible_start.to_point(display_snapshot)
8000 ..display_snapshot
8001 .clip_point(target_end, Bias::Right)
8002 .to_point(display_snapshot)
8003 }
8004
8005 pub fn refresh_edit_prediction(
8006 &mut self,
8007 debounce: bool,
8008 user_requested: bool,
8009 window: &mut Window,
8010 cx: &mut Context<Self>,
8011 ) -> Option<()> {
8012 if self.leader_id.is_some() {
8013 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8014 return None;
8015 }
8016
8017 let cursor = self.selections.newest_anchor().head();
8018 let (buffer, cursor_buffer_position) =
8019 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8020
8021 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8022 return None;
8023 }
8024
8025 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
8026 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8027 return None;
8028 }
8029
8030 self.update_visible_edit_prediction(window, cx);
8031
8032 if !user_requested
8033 && (!self.should_show_edit_predictions()
8034 || !self.is_focused(window)
8035 || buffer.read(cx).is_empty())
8036 {
8037 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8038 return None;
8039 }
8040
8041 self.edit_prediction_provider()?
8042 .refresh(buffer, cursor_buffer_position, debounce, cx);
8043 Some(())
8044 }
8045
8046 fn show_edit_predictions_in_menu(&self) -> bool {
8047 match self.edit_prediction_settings {
8048 EditPredictionSettings::Disabled => false,
8049 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
8050 }
8051 }
8052
8053 pub fn edit_predictions_enabled(&self) -> bool {
8054 match self.edit_prediction_settings {
8055 EditPredictionSettings::Disabled => false,
8056 EditPredictionSettings::Enabled { .. } => true,
8057 }
8058 }
8059
8060 fn edit_prediction_requires_modifier(&self) -> bool {
8061 match self.edit_prediction_settings {
8062 EditPredictionSettings::Disabled => false,
8063 EditPredictionSettings::Enabled {
8064 preview_requires_modifier,
8065 ..
8066 } => preview_requires_modifier,
8067 }
8068 }
8069
8070 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
8071 if self.edit_prediction_provider.is_none() {
8072 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8073 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8074 return;
8075 }
8076
8077 let selection = self.selections.newest_anchor();
8078 let cursor = selection.head();
8079
8080 if let Some((buffer, cursor_buffer_position)) =
8081 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8082 {
8083 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8084 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8085 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8086 return;
8087 }
8088 self.edit_prediction_settings =
8089 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8090 }
8091 }
8092
8093 fn edit_prediction_settings_at_position(
8094 &self,
8095 buffer: &Entity<Buffer>,
8096 buffer_position: language::Anchor,
8097 cx: &App,
8098 ) -> EditPredictionSettings {
8099 if !self.mode.is_full()
8100 || !self.show_edit_predictions_override.unwrap_or(true)
8101 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
8102 {
8103 return EditPredictionSettings::Disabled;
8104 }
8105
8106 if !LanguageSettings::for_buffer(&buffer.read(cx), cx).show_edit_predictions {
8107 return EditPredictionSettings::Disabled;
8108 };
8109
8110 let by_provider = matches!(
8111 self.menu_edit_predictions_policy,
8112 MenuEditPredictionsPolicy::ByProvider
8113 );
8114
8115 let show_in_menu = by_provider
8116 && self
8117 .edit_prediction_provider
8118 .as_ref()
8119 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
8120
8121 let file = buffer.read(cx).file();
8122 let preview_requires_modifier =
8123 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
8124
8125 EditPredictionSettings::Enabled {
8126 show_in_menu,
8127 preview_requires_modifier,
8128 }
8129 }
8130
8131 fn should_show_edit_predictions(&self) -> bool {
8132 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
8133 }
8134
8135 pub fn edit_prediction_preview_is_active(&self) -> bool {
8136 matches!(
8137 self.edit_prediction_preview,
8138 EditPredictionPreview::Active { .. }
8139 )
8140 }
8141
8142 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
8143 let cursor = self.selections.newest_anchor().head();
8144 if let Some((buffer, cursor_position)) =
8145 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8146 {
8147 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
8148 } else {
8149 false
8150 }
8151 }
8152
8153 pub fn supports_minimap(&self, cx: &App) -> bool {
8154 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
8155 }
8156
8157 fn edit_predictions_enabled_in_buffer(
8158 &self,
8159 buffer: &Entity<Buffer>,
8160 buffer_position: language::Anchor,
8161 cx: &App,
8162 ) -> bool {
8163 maybe!({
8164 if self.read_only(cx) || self.leader_id.is_some() {
8165 return Some(false);
8166 }
8167 let provider = self.edit_prediction_provider()?;
8168 if !provider.is_enabled(buffer, buffer_position, cx) {
8169 return Some(false);
8170 }
8171 let buffer = buffer.read(cx);
8172 let Some(file) = buffer.file() else {
8173 return Some(true);
8174 };
8175 let settings = all_language_settings(Some(file), cx);
8176 Some(settings.edit_predictions_enabled_for_file(file, cx))
8177 })
8178 .unwrap_or(false)
8179 }
8180
8181 pub fn show_edit_prediction(
8182 &mut self,
8183 _: &ShowEditPrediction,
8184 window: &mut Window,
8185 cx: &mut Context<Self>,
8186 ) {
8187 if !self.has_active_edit_prediction() {
8188 self.refresh_edit_prediction(false, true, window, cx);
8189 return;
8190 }
8191
8192 self.update_visible_edit_prediction(window, cx);
8193 }
8194
8195 pub fn display_cursor_names(
8196 &mut self,
8197 _: &DisplayCursorNames,
8198 window: &mut Window,
8199 cx: &mut Context<Self>,
8200 ) {
8201 self.show_cursor_names(window, cx);
8202 }
8203
8204 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8205 self.show_cursor_names = true;
8206 cx.notify();
8207 cx.spawn_in(window, async move |this, cx| {
8208 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8209 this.update(cx, |this, cx| {
8210 this.show_cursor_names = false;
8211 cx.notify()
8212 })
8213 .ok()
8214 })
8215 .detach();
8216 }
8217
8218 pub fn accept_partial_edit_prediction(
8219 &mut self,
8220 granularity: EditPredictionGranularity,
8221 window: &mut Window,
8222 cx: &mut Context<Self>,
8223 ) {
8224 if self.show_edit_predictions_in_menu() {
8225 self.hide_context_menu(window, cx);
8226 }
8227
8228 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8229 return;
8230 };
8231
8232 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8233 return;
8234 }
8235
8236 match &active_edit_prediction.completion {
8237 EditPrediction::MoveWithin { target, .. } => {
8238 let target = *target;
8239
8240 if matches!(granularity, EditPredictionGranularity::Full) {
8241 if let Some(position_map) = &self.last_position_map {
8242 let target_row = target.to_display_point(&position_map.snapshot).row();
8243 let is_visible = position_map.visible_row_range.contains(&target_row);
8244
8245 if is_visible || !self.edit_prediction_requires_modifier() {
8246 self.unfold_ranges(&[target..target], true, false, cx);
8247 self.change_selections(
8248 SelectionEffects::scroll(Autoscroll::newest()),
8249 window,
8250 cx,
8251 |selections| {
8252 selections.select_anchor_ranges([target..target]);
8253 },
8254 );
8255 self.clear_row_highlights::<EditPredictionPreview>();
8256 self.edit_prediction_preview
8257 .set_previous_scroll_position(None);
8258 } else {
8259 // Highlight and request scroll
8260 self.edit_prediction_preview
8261 .set_previous_scroll_position(Some(
8262 position_map.snapshot.scroll_anchor,
8263 ));
8264 self.highlight_rows::<EditPredictionPreview>(
8265 target..target,
8266 cx.theme().colors().editor_highlighted_line_background,
8267 RowHighlightOptions {
8268 autoscroll: true,
8269 ..Default::default()
8270 },
8271 cx,
8272 );
8273 self.request_autoscroll(Autoscroll::fit(), cx);
8274 }
8275 }
8276 } else {
8277 self.change_selections(
8278 SelectionEffects::scroll(Autoscroll::newest()),
8279 window,
8280 cx,
8281 |selections| {
8282 selections.select_anchor_ranges([target..target]);
8283 },
8284 );
8285 }
8286 }
8287 EditPrediction::MoveOutside { snapshot, target } => {
8288 if let Some(workspace) = self.workspace() {
8289 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8290 .detach_and_log_err(cx);
8291 }
8292 }
8293 EditPrediction::Edit {
8294 edits,
8295 cursor_position,
8296 ..
8297 } => {
8298 self.report_edit_prediction_event(
8299 active_edit_prediction.completion_id.clone(),
8300 true,
8301 cx,
8302 );
8303
8304 match granularity {
8305 EditPredictionGranularity::Full => {
8306 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8307
8308 // Compute fallback cursor position BEFORE applying the edit,
8309 // so the anchor tracks through the edit correctly
8310 let fallback_cursor_target = {
8311 let snapshot = self.buffer.read(cx).snapshot(cx);
8312 edits.last().unwrap().0.end.bias_right(&snapshot)
8313 };
8314
8315 self.buffer.update(cx, |buffer, cx| {
8316 buffer.edit(edits.iter().cloned(), None, cx)
8317 });
8318
8319 if let Some(provider) = self.edit_prediction_provider() {
8320 provider.accept(cx);
8321 }
8322
8323 // Resolve cursor position after the edit is applied
8324 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8325 // The anchor tracks through the edit, then we add the offset
8326 let snapshot = self.buffer.read(cx).snapshot(cx);
8327 let base_offset = anchor.to_offset(&snapshot).0;
8328 let target_offset =
8329 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8330 snapshot.anchor_after(target_offset)
8331 } else {
8332 fallback_cursor_target
8333 };
8334
8335 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8336 s.select_anchor_ranges([cursor_target..cursor_target]);
8337 });
8338
8339 let selections = self.selections.disjoint_anchors_arc();
8340 if let Some(transaction_id_now) =
8341 self.buffer.read(cx).last_transaction_id(cx)
8342 {
8343 if transaction_id_prev != Some(transaction_id_now) {
8344 self.selection_history
8345 .insert_transaction(transaction_id_now, selections);
8346 }
8347 }
8348
8349 self.update_visible_edit_prediction(window, cx);
8350 if self.active_edit_prediction.is_none() {
8351 self.refresh_edit_prediction(true, true, window, cx);
8352 }
8353 cx.notify();
8354 }
8355 _ => {
8356 let snapshot = self.buffer.read(cx).snapshot(cx);
8357 let cursor_offset = self
8358 .selections
8359 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8360 .head();
8361
8362 let insertion = edits.iter().find_map(|(range, text)| {
8363 let range = range.to_offset(&snapshot);
8364 if range.is_empty() && range.start == cursor_offset {
8365 Some(text)
8366 } else {
8367 None
8368 }
8369 });
8370
8371 if let Some(text) = insertion {
8372 let text_to_insert = match granularity {
8373 EditPredictionGranularity::Word => {
8374 let mut partial = text
8375 .chars()
8376 .by_ref()
8377 .take_while(|c| c.is_alphabetic())
8378 .collect::<String>();
8379 if partial.is_empty() {
8380 partial = text
8381 .chars()
8382 .by_ref()
8383 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8384 .collect::<String>();
8385 }
8386 partial
8387 }
8388 EditPredictionGranularity::Line => {
8389 if let Some(line) = text.split_inclusive('\n').next() {
8390 line.to_string()
8391 } else {
8392 text.to_string()
8393 }
8394 }
8395 EditPredictionGranularity::Full => unreachable!(),
8396 };
8397
8398 cx.emit(EditorEvent::InputHandled {
8399 utf16_range_to_replace: None,
8400 text: text_to_insert.clone().into(),
8401 });
8402
8403 self.replace_selections(&text_to_insert, None, window, cx, false);
8404 self.refresh_edit_prediction(true, true, window, cx);
8405 cx.notify();
8406 } else {
8407 self.accept_partial_edit_prediction(
8408 EditPredictionGranularity::Full,
8409 window,
8410 cx,
8411 );
8412 }
8413 }
8414 }
8415 }
8416 }
8417 }
8418
8419 pub fn accept_next_word_edit_prediction(
8420 &mut self,
8421 _: &AcceptNextWordEditPrediction,
8422 window: &mut Window,
8423 cx: &mut Context<Self>,
8424 ) {
8425 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8426 }
8427
8428 pub fn accept_next_line_edit_prediction(
8429 &mut self,
8430 _: &AcceptNextLineEditPrediction,
8431 window: &mut Window,
8432 cx: &mut Context<Self>,
8433 ) {
8434 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8435 }
8436
8437 pub fn accept_edit_prediction(
8438 &mut self,
8439 _: &AcceptEditPrediction,
8440 window: &mut Window,
8441 cx: &mut Context<Self>,
8442 ) {
8443 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8444 }
8445
8446 fn discard_edit_prediction(
8447 &mut self,
8448 reason: EditPredictionDiscardReason,
8449 cx: &mut Context<Self>,
8450 ) -> bool {
8451 if reason == EditPredictionDiscardReason::Rejected {
8452 let completion_id = self
8453 .active_edit_prediction
8454 .as_ref()
8455 .and_then(|active_completion| active_completion.completion_id.clone());
8456
8457 self.report_edit_prediction_event(completion_id, false, cx);
8458 }
8459
8460 if let Some(provider) = self.edit_prediction_provider() {
8461 provider.discard(reason, cx);
8462 }
8463
8464 self.take_active_edit_prediction(reason == EditPredictionDiscardReason::Ignored, cx)
8465 }
8466
8467 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8468 let Some(provider) = self.edit_prediction_provider() else {
8469 return;
8470 };
8471
8472 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
8473 let Some((position, _)) =
8474 buffer_snapshot.anchor_to_buffer_anchor(self.selections.newest_anchor().head())
8475 else {
8476 return;
8477 };
8478 let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
8479 return;
8480 };
8481
8482 let extension = buffer
8483 .read(cx)
8484 .file()
8485 .and_then(|file| Some(file.path().extension()?.to_string()));
8486
8487 let event_type = match accepted {
8488 true => "Edit Prediction Accepted",
8489 false => "Edit Prediction Discarded",
8490 };
8491 telemetry::event!(
8492 event_type,
8493 provider = provider.name(),
8494 prediction_id = id,
8495 suggestion_accepted = accepted,
8496 file_extension = extension,
8497 );
8498 }
8499
8500 fn open_editor_at_anchor(
8501 snapshot: &language::BufferSnapshot,
8502 target: language::Anchor,
8503 workspace: &Entity<Workspace>,
8504 window: &mut Window,
8505 cx: &mut App,
8506 ) -> Task<Result<()>> {
8507 workspace.update(cx, |workspace, cx| {
8508 let path = snapshot.file().map(|file| file.full_path(cx));
8509 let Some(path) =
8510 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8511 else {
8512 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8513 };
8514 let target = text::ToPoint::to_point(&target, snapshot);
8515 let item = workspace.open_path(path, None, true, window, cx);
8516 window.spawn(cx, async move |cx| {
8517 let Some(editor) = item.await?.downcast::<Editor>() else {
8518 return Ok(());
8519 };
8520 editor
8521 .update_in(cx, |editor, window, cx| {
8522 editor.go_to_singleton_buffer_point(target, window, cx);
8523 })
8524 .ok();
8525 anyhow::Ok(())
8526 })
8527 })
8528 }
8529
8530 pub fn has_active_edit_prediction(&self) -> bool {
8531 self.active_edit_prediction.is_some()
8532 }
8533
8534 fn take_active_edit_prediction(
8535 &mut self,
8536 preserve_stale_in_menu: bool,
8537 cx: &mut Context<Self>,
8538 ) -> bool {
8539 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8540 if !preserve_stale_in_menu {
8541 self.stale_edit_prediction_in_menu = None;
8542 }
8543 return false;
8544 };
8545
8546 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8547 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8548 self.stale_edit_prediction_in_menu =
8549 preserve_stale_in_menu.then_some(active_edit_prediction);
8550 true
8551 }
8552
8553 /// Returns true when we're displaying the edit prediction popover below the cursor
8554 /// like we are not previewing and the LSP autocomplete menu is visible
8555 /// or we are in `when_holding_modifier` mode.
8556 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8557 if self.edit_prediction_preview_is_active()
8558 || !self.show_edit_predictions_in_menu()
8559 || !self.edit_predictions_enabled()
8560 {
8561 return false;
8562 }
8563
8564 if self.has_visible_completions_menu() {
8565 return true;
8566 }
8567
8568 has_completion && self.edit_prediction_requires_modifier()
8569 }
8570
8571 fn handle_modifiers_changed(
8572 &mut self,
8573 modifiers: Modifiers,
8574 position_map: &PositionMap,
8575 window: &mut Window,
8576 cx: &mut Context<Self>,
8577 ) {
8578 self.update_edit_prediction_settings(cx);
8579
8580 // Ensure that the edit prediction preview is updated, even when not
8581 // enabled, if there's an active edit prediction preview.
8582 if self.show_edit_predictions_in_menu()
8583 || self.edit_prediction_requires_modifier()
8584 || matches!(
8585 self.edit_prediction_preview,
8586 EditPredictionPreview::Active { .. }
8587 )
8588 {
8589 self.update_edit_prediction_preview(&modifiers, window, cx);
8590 }
8591
8592 self.update_selection_mode(&modifiers, position_map, window, cx);
8593
8594 let mouse_position = window.mouse_position();
8595 if !position_map.text_hitbox.is_hovered(window) {
8596 return;
8597 }
8598
8599 self.update_hovered_link(
8600 position_map.point_for_position(mouse_position),
8601 Some(mouse_position),
8602 &position_map.snapshot,
8603 modifiers,
8604 window,
8605 cx,
8606 )
8607 }
8608
8609 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8610 match EditorSettings::get_global(cx).multi_cursor_modifier {
8611 MultiCursorModifier::Alt => modifiers.secondary(),
8612 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8613 }
8614 }
8615
8616 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8617 match EditorSettings::get_global(cx).multi_cursor_modifier {
8618 MultiCursorModifier::Alt => modifiers.alt,
8619 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8620 }
8621 }
8622
8623 fn columnar_selection_mode(
8624 modifiers: &Modifiers,
8625 cx: &mut Context<Self>,
8626 ) -> Option<ColumnarMode> {
8627 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8628 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8629 Some(ColumnarMode::FromMouse)
8630 } else if Self::is_alt_pressed(modifiers, cx) {
8631 Some(ColumnarMode::FromSelection)
8632 } else {
8633 None
8634 }
8635 } else {
8636 None
8637 }
8638 }
8639
8640 fn update_selection_mode(
8641 &mut self,
8642 modifiers: &Modifiers,
8643 position_map: &PositionMap,
8644 window: &mut Window,
8645 cx: &mut Context<Self>,
8646 ) {
8647 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8648 return;
8649 };
8650 if self.selections.pending_anchor().is_none() {
8651 return;
8652 }
8653
8654 let mouse_position = window.mouse_position();
8655 let point_for_position = position_map.point_for_position(mouse_position);
8656 let position = point_for_position.previous_valid;
8657
8658 self.select(
8659 SelectPhase::BeginColumnar {
8660 position,
8661 reset: false,
8662 mode,
8663 goal_column: point_for_position.exact_unclipped.column(),
8664 },
8665 window,
8666 cx,
8667 );
8668 }
8669
8670 fn update_edit_prediction_preview(
8671 &mut self,
8672 modifiers: &Modifiers,
8673 window: &mut Window,
8674 cx: &mut Context<Self>,
8675 ) {
8676 let modifiers_held = self.edit_prediction_preview_modifiers_held(modifiers, window, cx);
8677
8678 if modifiers_held {
8679 if matches!(
8680 self.edit_prediction_preview,
8681 EditPredictionPreview::Inactive { .. }
8682 ) {
8683 self.edit_prediction_preview = EditPredictionPreview::Active {
8684 previous_scroll_position: None,
8685 since: Instant::now(),
8686 };
8687
8688 self.update_visible_edit_prediction(window, cx);
8689 cx.notify();
8690 }
8691 } else if let EditPredictionPreview::Active {
8692 previous_scroll_position,
8693 since,
8694 } = self.edit_prediction_preview
8695 {
8696 if let (Some(previous_scroll_position), Some(position_map)) =
8697 (previous_scroll_position, self.last_position_map.as_ref())
8698 {
8699 self.set_scroll_position(
8700 previous_scroll_position
8701 .scroll_position(&position_map.snapshot.display_snapshot),
8702 window,
8703 cx,
8704 );
8705 }
8706
8707 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8708 released_too_fast: since.elapsed() < Duration::from_millis(200),
8709 };
8710 self.clear_row_highlights::<EditPredictionPreview>();
8711 self.update_visible_edit_prediction(window, cx);
8712 cx.notify();
8713 }
8714 }
8715
8716 fn update_visible_edit_prediction(
8717 &mut self,
8718 _window: &mut Window,
8719 cx: &mut Context<Self>,
8720 ) -> Option<()> {
8721 if self.ime_transaction.is_some() {
8722 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8723 return None;
8724 }
8725
8726 let selection = self.selections.newest_anchor();
8727 let multibuffer = self.buffer.read(cx).snapshot(cx);
8728 let cursor = selection.head();
8729 let (cursor_text_anchor, _) = multibuffer.anchor_to_buffer_anchor(cursor)?;
8730 let buffer = self.buffer.read(cx).buffer(cursor_text_anchor.buffer_id)?;
8731
8732 // Check project-level disable_ai setting for the current buffer
8733 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8734 return None;
8735 }
8736 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8737
8738 let show_in_menu = self.show_edit_predictions_in_menu();
8739 let completions_menu_has_precedence = !show_in_menu
8740 && (self.context_menu.borrow().is_some()
8741 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8742
8743 if completions_menu_has_precedence
8744 || !offset_selection.is_empty()
8745 || self
8746 .active_edit_prediction
8747 .as_ref()
8748 .is_some_and(|completion| {
8749 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8750 return false;
8751 };
8752 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8753 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8754 !invalidation_range.contains(&offset_selection.head())
8755 })
8756 {
8757 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8758 return None;
8759 }
8760
8761 self.take_active_edit_prediction(true, cx);
8762 let Some(provider) = self.edit_prediction_provider() else {
8763 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8764 return None;
8765 };
8766
8767 self.edit_prediction_settings =
8768 self.edit_prediction_settings_at_position(&buffer, cursor_text_anchor, cx);
8769
8770 self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor);
8771
8772 if self.in_leading_whitespace {
8773 let cursor_point = cursor.to_point(&multibuffer);
8774 let mut suggested_indent = None;
8775 multibuffer.suggested_indents_callback(
8776 cursor_point.row..cursor_point.row + 1,
8777 &mut |_, indent| {
8778 suggested_indent = Some(indent);
8779 ControlFlow::Break(())
8780 },
8781 cx,
8782 );
8783
8784 if let Some(indent) = suggested_indent
8785 && indent.len == cursor_point.column
8786 {
8787 self.in_leading_whitespace = false;
8788 }
8789 }
8790
8791 let edit_prediction = provider.suggest(&buffer, cursor_text_anchor, cx)?;
8792
8793 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8794 {
8795 edit_prediction_types::EditPrediction::Local {
8796 id,
8797 edits,
8798 cursor_position,
8799 edit_preview,
8800 } => (id, edits, cursor_position, edit_preview),
8801 edit_prediction_types::EditPrediction::Jump {
8802 id,
8803 snapshot,
8804 target,
8805 } => {
8806 if let Some(provider) = &self.edit_prediction_provider {
8807 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8808 }
8809 self.stale_edit_prediction_in_menu = None;
8810 self.active_edit_prediction = Some(EditPredictionState {
8811 inlay_ids: vec![],
8812 completion: EditPrediction::MoveOutside { snapshot, target },
8813 completion_id: id,
8814 invalidation_range: None,
8815 });
8816 cx.notify();
8817 return Some(());
8818 }
8819 };
8820
8821 let edits = edits
8822 .into_iter()
8823 .flat_map(|(range, new_text)| {
8824 Some((
8825 multibuffer.buffer_anchor_range_to_anchor_range(range)?,
8826 new_text,
8827 ))
8828 })
8829 .collect::<Vec<_>>();
8830 if edits.is_empty() {
8831 return None;
8832 }
8833
8834 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8835 let anchor = multibuffer.anchor_in_excerpt(predicted.anchor)?;
8836 Some((anchor, predicted.offset))
8837 });
8838
8839 let first_edit_start = edits.first().unwrap().0.start;
8840 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8841 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8842
8843 let last_edit_end = edits.last().unwrap().0.end;
8844 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8845 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8846
8847 let cursor_row = cursor.to_point(&multibuffer).row;
8848
8849 let snapshot = multibuffer
8850 .buffer_for_id(cursor_text_anchor.buffer_id)
8851 .cloned()?;
8852
8853 let mut inlay_ids = Vec::new();
8854 let invalidation_row_range;
8855 let move_invalidation_row_range = if cursor_row < edit_start_row {
8856 Some(cursor_row..edit_end_row)
8857 } else if cursor_row > edit_end_row {
8858 Some(edit_start_row..cursor_row)
8859 } else {
8860 None
8861 };
8862 let supports_jump = self
8863 .edit_prediction_provider
8864 .as_ref()
8865 .map(|provider| provider.provider.supports_jump_to_edit())
8866 .unwrap_or(true);
8867
8868 let is_move = supports_jump
8869 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8870 let completion = if is_move {
8871 if let Some(provider) = &self.edit_prediction_provider {
8872 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8873 }
8874 invalidation_row_range =
8875 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8876 let target = first_edit_start;
8877 EditPrediction::MoveWithin { target, snapshot }
8878 } else {
8879 let show_completions_in_menu = self.has_visible_completions_menu();
8880 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8881 && !self.edit_predictions_hidden_for_vim_mode;
8882
8883 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8884 if provider.show_tab_accept_marker() {
8885 EditDisplayMode::TabAccept
8886 } else {
8887 EditDisplayMode::Inline
8888 }
8889 } else {
8890 EditDisplayMode::DiffPopover
8891 };
8892
8893 let report_shown = match display_mode {
8894 EditDisplayMode::DiffPopover | EditDisplayMode::Inline => {
8895 show_completions_in_buffer || show_completions_in_menu
8896 }
8897 EditDisplayMode::TabAccept => {
8898 show_completions_in_menu || self.edit_prediction_preview_is_active()
8899 }
8900 };
8901
8902 if report_shown && let Some(provider) = &self.edit_prediction_provider {
8903 let suggestion_display_type = match display_mode {
8904 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8905 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8906 SuggestionDisplayType::GhostText
8907 }
8908 };
8909 provider.provider.did_show(suggestion_display_type, cx);
8910 }
8911
8912 if show_completions_in_buffer {
8913 if edits
8914 .iter()
8915 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8916 {
8917 let mut inlays = Vec::new();
8918 for (range, new_text) in &edits {
8919 let inlay = Inlay::edit_prediction(
8920 post_inc(&mut self.next_inlay_id),
8921 range.start,
8922 new_text.as_ref(),
8923 );
8924 inlay_ids.push(inlay.id);
8925 inlays.push(inlay);
8926 }
8927
8928 self.splice_inlays(&[], inlays, cx);
8929 } else {
8930 let background_color = cx.theme().status().deleted_background;
8931 self.highlight_text(
8932 HighlightKey::EditPredictionHighlight,
8933 edits.iter().map(|(range, _)| range.clone()).collect(),
8934 HighlightStyle {
8935 background_color: Some(background_color),
8936 ..Default::default()
8937 },
8938 cx,
8939 );
8940 }
8941 }
8942
8943 invalidation_row_range = edit_start_row..edit_end_row;
8944
8945 EditPrediction::Edit {
8946 edits,
8947 cursor_position,
8948 edit_preview,
8949 display_mode,
8950 snapshot,
8951 }
8952 };
8953
8954 let invalidation_range = multibuffer
8955 .anchor_before(Point::new(invalidation_row_range.start, 0))
8956 ..multibuffer.anchor_after(Point::new(
8957 invalidation_row_range.end,
8958 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8959 ));
8960
8961 self.stale_edit_prediction_in_menu = None;
8962 self.active_edit_prediction = Some(EditPredictionState {
8963 inlay_ids,
8964 completion,
8965 completion_id,
8966 invalidation_range: Some(invalidation_range),
8967 });
8968
8969 cx.notify();
8970
8971 Some(())
8972 }
8973
8974 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8975 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8976 }
8977
8978 /// Get all display points of breakpoints that will be rendered within editor
8979 ///
8980 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8981 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8982 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8983 fn active_breakpoints(
8984 &self,
8985 range: Range<DisplayRow>,
8986 window: &mut Window,
8987 cx: &mut Context<Self>,
8988 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8989 let mut breakpoint_display_points = HashMap::default();
8990
8991 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8992 return breakpoint_display_points;
8993 };
8994
8995 let snapshot = self.snapshot(window, cx);
8996
8997 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8998
8999 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
9000 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
9001
9002 for (buffer_snapshot, range, _) in
9003 multi_buffer_snapshot.range_to_buffer_ranges(range.start..range.end)
9004 {
9005 let Some(buffer) = self.buffer().read(cx).buffer(buffer_snapshot.remote_id()) else {
9006 continue;
9007 };
9008 let breakpoints = breakpoint_store.read(cx).breakpoints(
9009 &buffer,
9010 Some(
9011 buffer_snapshot.anchor_before(range.start)
9012 ..buffer_snapshot.anchor_after(range.end),
9013 ),
9014 &buffer_snapshot,
9015 cx,
9016 );
9017 for (breakpoint, state) in breakpoints {
9018 let Some(multi_buffer_anchor) =
9019 multi_buffer_snapshot.anchor_in_excerpt(breakpoint.position)
9020 else {
9021 continue;
9022 };
9023 let position = multi_buffer_anchor
9024 .to_point(&multi_buffer_snapshot)
9025 .to_display_point(&snapshot);
9026
9027 breakpoint_display_points.insert(
9028 position.row(),
9029 (multi_buffer_anchor, breakpoint.bp.clone(), state),
9030 );
9031 }
9032 }
9033
9034 breakpoint_display_points
9035 }
9036
9037 fn breakpoint_context_menu(
9038 &self,
9039 anchor: Anchor,
9040 window: &mut Window,
9041 cx: &mut Context<Self>,
9042 ) -> Entity<ui::ContextMenu> {
9043 let weak_editor = cx.weak_entity();
9044 let focus_handle = self.focus_handle(cx);
9045
9046 let row = self
9047 .buffer
9048 .read(cx)
9049 .snapshot(cx)
9050 .summary_for_anchor::<Point>(&anchor)
9051 .row;
9052
9053 let breakpoint = self
9054 .breakpoint_at_row(row, window, cx)
9055 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
9056
9057 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
9058 "Edit Log Breakpoint"
9059 } else {
9060 "Set Log Breakpoint"
9061 };
9062
9063 let condition_breakpoint_msg = if breakpoint
9064 .as_ref()
9065 .is_some_and(|bp| bp.1.condition.is_some())
9066 {
9067 "Edit Condition Breakpoint"
9068 } else {
9069 "Set Condition Breakpoint"
9070 };
9071
9072 let hit_condition_breakpoint_msg = if breakpoint
9073 .as_ref()
9074 .is_some_and(|bp| bp.1.hit_condition.is_some())
9075 {
9076 "Edit Hit Condition Breakpoint"
9077 } else {
9078 "Set Hit Condition Breakpoint"
9079 };
9080
9081 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
9082 "Unset Breakpoint"
9083 } else {
9084 "Set Breakpoint"
9085 };
9086
9087 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
9088
9089 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
9090 BreakpointState::Enabled => Some("Disable"),
9091 BreakpointState::Disabled => Some("Enable"),
9092 });
9093
9094 let (anchor, breakpoint) =
9095 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
9096
9097 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
9098 menu.on_blur_subscription(Subscription::new(|| {}))
9099 .context(focus_handle)
9100 .when(run_to_cursor, |this| {
9101 let weak_editor = weak_editor.clone();
9102 this.entry("Run to Cursor", None, move |window, cx| {
9103 weak_editor
9104 .update(cx, |editor, cx| {
9105 editor.change_selections(
9106 SelectionEffects::no_scroll(),
9107 window,
9108 cx,
9109 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
9110 );
9111 })
9112 .ok();
9113
9114 window.dispatch_action(Box::new(RunToCursor), cx);
9115 })
9116 .separator()
9117 })
9118 .when_some(toggle_state_msg, |this, msg| {
9119 this.entry(msg, None, {
9120 let weak_editor = weak_editor.clone();
9121 let breakpoint = breakpoint.clone();
9122 move |_window, cx| {
9123 weak_editor
9124 .update(cx, |this, cx| {
9125 this.edit_breakpoint_at_anchor(
9126 anchor,
9127 breakpoint.as_ref().clone(),
9128 BreakpointEditAction::InvertState,
9129 cx,
9130 );
9131 })
9132 .log_err();
9133 }
9134 })
9135 })
9136 .entry(set_breakpoint_msg, None, {
9137 let weak_editor = weak_editor.clone();
9138 let breakpoint = breakpoint.clone();
9139 move |_window, cx| {
9140 weak_editor
9141 .update(cx, |this, cx| {
9142 this.edit_breakpoint_at_anchor(
9143 anchor,
9144 breakpoint.as_ref().clone(),
9145 BreakpointEditAction::Toggle,
9146 cx,
9147 );
9148 })
9149 .log_err();
9150 }
9151 })
9152 .entry(log_breakpoint_msg, None, {
9153 let breakpoint = breakpoint.clone();
9154 let weak_editor = weak_editor.clone();
9155 move |window, cx| {
9156 weak_editor
9157 .update(cx, |this, cx| {
9158 this.add_edit_breakpoint_block(
9159 anchor,
9160 breakpoint.as_ref(),
9161 BreakpointPromptEditAction::Log,
9162 window,
9163 cx,
9164 );
9165 })
9166 .log_err();
9167 }
9168 })
9169 .entry(condition_breakpoint_msg, None, {
9170 let breakpoint = breakpoint.clone();
9171 let weak_editor = weak_editor.clone();
9172 move |window, cx| {
9173 weak_editor
9174 .update(cx, |this, cx| {
9175 this.add_edit_breakpoint_block(
9176 anchor,
9177 breakpoint.as_ref(),
9178 BreakpointPromptEditAction::Condition,
9179 window,
9180 cx,
9181 );
9182 })
9183 .log_err();
9184 }
9185 })
9186 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
9187 weak_editor
9188 .update(cx, |this, cx| {
9189 this.add_edit_breakpoint_block(
9190 anchor,
9191 breakpoint.as_ref(),
9192 BreakpointPromptEditAction::HitCondition,
9193 window,
9194 cx,
9195 );
9196 })
9197 .log_err();
9198 })
9199 })
9200 }
9201
9202 fn render_breakpoint(
9203 &self,
9204 position: Anchor,
9205 row: DisplayRow,
9206 breakpoint: &Breakpoint,
9207 state: Option<BreakpointSessionState>,
9208 cx: &mut Context<Self>,
9209 ) -> IconButton {
9210 let is_rejected = state.is_some_and(|s| !s.verified);
9211 // Is it a breakpoint that shows up when hovering over gutter?
9212 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9213 (false, false),
9214 |PhantomBreakpointIndicator {
9215 is_active,
9216 display_row,
9217 collides_with_existing_breakpoint,
9218 }| {
9219 (
9220 is_active && display_row == row,
9221 collides_with_existing_breakpoint,
9222 )
9223 },
9224 );
9225
9226 let (color, icon) = {
9227 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9228 (false, false) => ui::IconName::DebugBreakpoint,
9229 (true, false) => ui::IconName::DebugLogBreakpoint,
9230 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9231 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9232 };
9233
9234 let theme_colors = cx.theme().colors();
9235
9236 let color = if is_phantom {
9237 if collides_with_existing {
9238 Color::Custom(
9239 theme_colors
9240 .debugger_accent
9241 .blend(theme_colors.text.opacity(0.6)),
9242 )
9243 } else {
9244 Color::Hint
9245 }
9246 } else if is_rejected {
9247 Color::Disabled
9248 } else {
9249 Color::Debugger
9250 };
9251
9252 (color, icon)
9253 };
9254
9255 let breakpoint = Arc::from(breakpoint.clone());
9256
9257 let alt_as_text = gpui::Keystroke {
9258 modifiers: Modifiers::secondary_key(),
9259 ..Default::default()
9260 };
9261 let primary_action_text = if breakpoint.is_disabled() {
9262 "Enable breakpoint"
9263 } else if is_phantom && !collides_with_existing {
9264 "Set breakpoint"
9265 } else {
9266 "Unset breakpoint"
9267 };
9268 let focus_handle = self.focus_handle.clone();
9269
9270 let meta = if is_rejected {
9271 SharedString::from("No executable code is associated with this line.")
9272 } else if collides_with_existing && !breakpoint.is_disabled() {
9273 SharedString::from(format!(
9274 "{alt_as_text}-click to disable,\nright-click for more options."
9275 ))
9276 } else {
9277 SharedString::from("Right-click for more options.")
9278 };
9279 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9280 .icon_size(IconSize::XSmall)
9281 .size(ui::ButtonSize::None)
9282 .when(is_rejected, |this| {
9283 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9284 })
9285 .icon_color(color)
9286 .style(ButtonStyle::Transparent)
9287 .on_click(cx.listener({
9288 move |editor, event: &ClickEvent, window, cx| {
9289 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9290 BreakpointEditAction::InvertState
9291 } else {
9292 BreakpointEditAction::Toggle
9293 };
9294
9295 window.focus(&editor.focus_handle(cx), cx);
9296 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9297 editor.edit_breakpoint_at_anchor(
9298 position,
9299 breakpoint.as_ref().clone(),
9300 edit_action,
9301 cx,
9302 );
9303 }
9304 }))
9305 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9306 editor.set_breakpoint_context_menu(
9307 row,
9308 Some(position),
9309 event.position(),
9310 window,
9311 cx,
9312 );
9313 }))
9314 .tooltip(move |_window, cx| {
9315 Tooltip::with_meta_in(
9316 primary_action_text,
9317 Some(&ToggleBreakpoint),
9318 meta.clone(),
9319 &focus_handle,
9320 cx,
9321 )
9322 })
9323 }
9324
9325 fn build_tasks_context(
9326 project: &Entity<Project>,
9327 buffer: &Entity<Buffer>,
9328 buffer_row: u32,
9329 tasks: &Arc<RunnableTasks>,
9330 cx: &mut Context<Self>,
9331 ) -> Task<Option<task::TaskContext>> {
9332 let position = Point::new(buffer_row, tasks.column);
9333 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9334 let location = Location {
9335 buffer: buffer.clone(),
9336 range: range_start..range_start,
9337 };
9338 // Fill in the environmental variables from the tree-sitter captures
9339 let mut captured_task_variables = TaskVariables::default();
9340 for (capture_name, value) in tasks.extra_variables.clone() {
9341 captured_task_variables.insert(
9342 task::VariableName::Custom(capture_name.into()),
9343 value.clone(),
9344 );
9345 }
9346 project.update(cx, |project, cx| {
9347 project.task_store().update(cx, |task_store, cx| {
9348 task_store.task_context_for_location(captured_task_variables, location, cx)
9349 })
9350 })
9351 }
9352
9353 pub fn context_menu_visible(&self) -> bool {
9354 !self.edit_prediction_preview_is_active()
9355 && self
9356 .context_menu
9357 .borrow()
9358 .as_ref()
9359 .is_some_and(|menu| menu.visible())
9360 }
9361
9362 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9363 self.context_menu
9364 .borrow()
9365 .as_ref()
9366 .map(|menu| menu.origin())
9367 }
9368
9369 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9370 self.context_menu_options = Some(options);
9371 }
9372
9373 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9374 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9375
9376 fn render_edit_prediction_popover(
9377 &mut self,
9378 text_bounds: &Bounds<Pixels>,
9379 content_origin: gpui::Point<Pixels>,
9380 right_margin: Pixels,
9381 editor_snapshot: &EditorSnapshot,
9382 visible_row_range: Range<DisplayRow>,
9383 scroll_top: ScrollOffset,
9384 scroll_bottom: ScrollOffset,
9385 line_layouts: &[LineWithInvisibles],
9386 line_height: Pixels,
9387 scroll_position: gpui::Point<ScrollOffset>,
9388 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9389 newest_selection_head: Option<DisplayPoint>,
9390 editor_width: Pixels,
9391 style: &EditorStyle,
9392 window: &mut Window,
9393 cx: &mut App,
9394 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9395 if self.mode().is_minimap() {
9396 return None;
9397 }
9398 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9399
9400 if self.edit_prediction_visible_in_cursor_popover(true) {
9401 return None;
9402 }
9403
9404 match &active_edit_prediction.completion {
9405 EditPrediction::MoveWithin { target, .. } => {
9406 let target_display_point = target.to_display_point(editor_snapshot);
9407
9408 if self.edit_prediction_requires_modifier() {
9409 if !self.edit_prediction_preview_is_active() {
9410 return None;
9411 }
9412
9413 self.render_edit_prediction_modifier_jump_popover(
9414 text_bounds,
9415 content_origin,
9416 visible_row_range,
9417 line_layouts,
9418 line_height,
9419 scroll_pixel_position,
9420 newest_selection_head,
9421 target_display_point,
9422 window,
9423 cx,
9424 )
9425 } else {
9426 self.render_edit_prediction_eager_jump_popover(
9427 text_bounds,
9428 content_origin,
9429 editor_snapshot,
9430 visible_row_range,
9431 scroll_top,
9432 scroll_bottom,
9433 line_height,
9434 scroll_pixel_position,
9435 target_display_point,
9436 editor_width,
9437 window,
9438 cx,
9439 )
9440 }
9441 }
9442 EditPrediction::Edit {
9443 display_mode: EditDisplayMode::Inline,
9444 ..
9445 } => None,
9446 EditPrediction::Edit {
9447 display_mode: EditDisplayMode::TabAccept,
9448 edits,
9449 ..
9450 } => {
9451 let range = &edits.first()?.0;
9452 let target_display_point = range.end.to_display_point(editor_snapshot);
9453
9454 self.render_edit_prediction_end_of_line_popover(
9455 "Accept",
9456 editor_snapshot,
9457 visible_row_range,
9458 target_display_point,
9459 line_height,
9460 scroll_pixel_position,
9461 content_origin,
9462 editor_width,
9463 window,
9464 cx,
9465 )
9466 }
9467 EditPrediction::Edit {
9468 edits,
9469 edit_preview,
9470 display_mode: EditDisplayMode::DiffPopover,
9471 snapshot,
9472 ..
9473 } => self.render_edit_prediction_diff_popover(
9474 text_bounds,
9475 content_origin,
9476 right_margin,
9477 editor_snapshot,
9478 visible_row_range,
9479 line_layouts,
9480 line_height,
9481 scroll_position,
9482 scroll_pixel_position,
9483 newest_selection_head,
9484 editor_width,
9485 style,
9486 edits,
9487 edit_preview,
9488 snapshot,
9489 window,
9490 cx,
9491 ),
9492 EditPrediction::MoveOutside { snapshot, .. } => {
9493 let mut element = self
9494 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9495 .into_any();
9496
9497 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9498 let origin_x = text_bounds.size.width - size.width - px(30.);
9499 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9500 element.prepaint_at(origin, window, cx);
9501
9502 Some((element, origin))
9503 }
9504 }
9505 }
9506
9507 fn render_edit_prediction_modifier_jump_popover(
9508 &mut self,
9509 text_bounds: &Bounds<Pixels>,
9510 content_origin: gpui::Point<Pixels>,
9511 visible_row_range: Range<DisplayRow>,
9512 line_layouts: &[LineWithInvisibles],
9513 line_height: Pixels,
9514 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9515 newest_selection_head: Option<DisplayPoint>,
9516 target_display_point: DisplayPoint,
9517 window: &mut Window,
9518 cx: &mut App,
9519 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9520 let scrolled_content_origin =
9521 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9522
9523 const SCROLL_PADDING_Y: Pixels = px(12.);
9524
9525 if target_display_point.row() < visible_row_range.start {
9526 return self.render_edit_prediction_scroll_popover(
9527 &|_| SCROLL_PADDING_Y,
9528 IconName::ArrowUp,
9529 visible_row_range,
9530 line_layouts,
9531 newest_selection_head,
9532 scrolled_content_origin,
9533 window,
9534 cx,
9535 );
9536 } else if target_display_point.row() >= visible_row_range.end {
9537 return self.render_edit_prediction_scroll_popover(
9538 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9539 IconName::ArrowDown,
9540 visible_row_range,
9541 line_layouts,
9542 newest_selection_head,
9543 scrolled_content_origin,
9544 window,
9545 cx,
9546 );
9547 }
9548
9549 const POLE_WIDTH: Pixels = px(2.);
9550
9551 let line_layout =
9552 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9553 let target_column = target_display_point.column() as usize;
9554
9555 let target_x = line_layout.x_for_index(target_column);
9556 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9557 - scroll_pixel_position.y;
9558
9559 let flag_on_right = target_x < text_bounds.size.width / 2.;
9560
9561 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9562 border_color.l += 0.001;
9563
9564 let mut element = v_flex()
9565 .items_end()
9566 .when(flag_on_right, |el| el.items_start())
9567 .child(if flag_on_right {
9568 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9569 .rounded_bl(px(0.))
9570 .rounded_tl(px(0.))
9571 .border_l_2()
9572 .border_color(border_color)
9573 } else {
9574 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9575 .rounded_br(px(0.))
9576 .rounded_tr(px(0.))
9577 .border_r_2()
9578 .border_color(border_color)
9579 })
9580 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9581 .into_any();
9582
9583 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9584
9585 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9586 - point(
9587 if flag_on_right {
9588 POLE_WIDTH
9589 } else {
9590 size.width - POLE_WIDTH
9591 },
9592 size.height - line_height,
9593 );
9594
9595 origin.x = origin.x.max(content_origin.x);
9596
9597 element.prepaint_at(origin, window, cx);
9598
9599 Some((element, origin))
9600 }
9601
9602 fn render_edit_prediction_scroll_popover(
9603 &mut self,
9604 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9605 scroll_icon: IconName,
9606 visible_row_range: Range<DisplayRow>,
9607 line_layouts: &[LineWithInvisibles],
9608 newest_selection_head: Option<DisplayPoint>,
9609 scrolled_content_origin: gpui::Point<Pixels>,
9610 window: &mut Window,
9611 cx: &mut App,
9612 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9613 let mut element = self
9614 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9615 .into_any();
9616
9617 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9618
9619 let cursor = newest_selection_head?;
9620 let cursor_row_layout =
9621 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9622 let cursor_column = cursor.column() as usize;
9623
9624 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9625
9626 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9627
9628 element.prepaint_at(origin, window, cx);
9629 Some((element, origin))
9630 }
9631
9632 fn render_edit_prediction_eager_jump_popover(
9633 &mut self,
9634 text_bounds: &Bounds<Pixels>,
9635 content_origin: gpui::Point<Pixels>,
9636 editor_snapshot: &EditorSnapshot,
9637 visible_row_range: Range<DisplayRow>,
9638 scroll_top: ScrollOffset,
9639 scroll_bottom: ScrollOffset,
9640 line_height: Pixels,
9641 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9642 target_display_point: DisplayPoint,
9643 editor_width: Pixels,
9644 window: &mut Window,
9645 cx: &mut App,
9646 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9647 if target_display_point.row().as_f64() < scroll_top {
9648 let mut element = self
9649 .render_edit_prediction_line_popover(
9650 "Jump to Edit",
9651 Some(IconName::ArrowUp),
9652 window,
9653 cx,
9654 )
9655 .into_any();
9656
9657 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9658 let offset = point(
9659 (text_bounds.size.width - size.width) / 2.,
9660 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9661 );
9662
9663 let origin = text_bounds.origin + offset;
9664 element.prepaint_at(origin, window, cx);
9665 Some((element, origin))
9666 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9667 let mut element = self
9668 .render_edit_prediction_line_popover(
9669 "Jump to Edit",
9670 Some(IconName::ArrowDown),
9671 window,
9672 cx,
9673 )
9674 .into_any();
9675
9676 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9677 let offset = point(
9678 (text_bounds.size.width - size.width) / 2.,
9679 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9680 );
9681
9682 let origin = text_bounds.origin + offset;
9683 element.prepaint_at(origin, window, cx);
9684 Some((element, origin))
9685 } else {
9686 self.render_edit_prediction_end_of_line_popover(
9687 "Jump to Edit",
9688 editor_snapshot,
9689 visible_row_range,
9690 target_display_point,
9691 line_height,
9692 scroll_pixel_position,
9693 content_origin,
9694 editor_width,
9695 window,
9696 cx,
9697 )
9698 }
9699 }
9700
9701 fn render_edit_prediction_end_of_line_popover(
9702 self: &mut Editor,
9703 label: &'static str,
9704 editor_snapshot: &EditorSnapshot,
9705 visible_row_range: Range<DisplayRow>,
9706 target_display_point: DisplayPoint,
9707 line_height: Pixels,
9708 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9709 content_origin: gpui::Point<Pixels>,
9710 editor_width: Pixels,
9711 window: &mut Window,
9712 cx: &mut App,
9713 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9714 let target_line_end = DisplayPoint::new(
9715 target_display_point.row(),
9716 editor_snapshot.line_len(target_display_point.row()),
9717 );
9718
9719 let mut element = self
9720 .render_edit_prediction_line_popover(label, None, window, cx)
9721 .into_any();
9722
9723 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9724
9725 let line_origin =
9726 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9727
9728 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9729 let mut origin = start_point
9730 + line_origin
9731 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9732 origin.x = origin.x.max(content_origin.x);
9733
9734 let max_x = content_origin.x + editor_width - size.width;
9735
9736 if origin.x > max_x {
9737 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9738
9739 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9740 origin.y += offset;
9741 IconName::ArrowUp
9742 } else {
9743 origin.y -= offset;
9744 IconName::ArrowDown
9745 };
9746
9747 element = self
9748 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9749 .into_any();
9750
9751 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9752
9753 origin.x = content_origin.x + editor_width - size.width - px(2.);
9754 }
9755
9756 element.prepaint_at(origin, window, cx);
9757 Some((element, origin))
9758 }
9759
9760 fn render_edit_prediction_diff_popover(
9761 self: &Editor,
9762 text_bounds: &Bounds<Pixels>,
9763 content_origin: gpui::Point<Pixels>,
9764 right_margin: Pixels,
9765 editor_snapshot: &EditorSnapshot,
9766 visible_row_range: Range<DisplayRow>,
9767 line_layouts: &[LineWithInvisibles],
9768 line_height: Pixels,
9769 scroll_position: gpui::Point<ScrollOffset>,
9770 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9771 newest_selection_head: Option<DisplayPoint>,
9772 editor_width: Pixels,
9773 style: &EditorStyle,
9774 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9775 edit_preview: &Option<language::EditPreview>,
9776 snapshot: &language::BufferSnapshot,
9777 window: &mut Window,
9778 cx: &mut App,
9779 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9780 let edit_start = edits
9781 .first()
9782 .unwrap()
9783 .0
9784 .start
9785 .to_display_point(editor_snapshot);
9786 let edit_end = edits
9787 .last()
9788 .unwrap()
9789 .0
9790 .end
9791 .to_display_point(editor_snapshot);
9792
9793 let is_visible = visible_row_range.contains(&edit_start.row())
9794 || visible_row_range.contains(&edit_end.row());
9795 if !is_visible {
9796 return None;
9797 }
9798
9799 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9800 crate::edit_prediction_edit_text(
9801 snapshot,
9802 edits,
9803 edit_preview,
9804 false,
9805 editor_snapshot.buffer_snapshot(),
9806 cx,
9807 )
9808 } else {
9809 // Fallback for providers without edit_preview
9810 crate::edit_prediction_fallback_text(edits, cx)
9811 };
9812
9813 let styled_text = highlighted_edits.to_styled_text(&style.text);
9814 let line_count = highlighted_edits.text.lines().count();
9815
9816 const BORDER_WIDTH: Pixels = px(1.);
9817
9818 let keybind = self.render_edit_prediction_keybind(window, cx);
9819 let has_keybind = keybind.is_some();
9820
9821 let mut element = h_flex()
9822 .items_start()
9823 .child(
9824 h_flex()
9825 .bg(cx.theme().colors().editor_background)
9826 .border(BORDER_WIDTH)
9827 .shadow_xs()
9828 .border_color(cx.theme().colors().border)
9829 .rounded_l_lg()
9830 .when(line_count > 1, |el| el.rounded_br_lg())
9831 .pr_1()
9832 .child(styled_text),
9833 )
9834 .child(
9835 h_flex()
9836 .h(line_height + BORDER_WIDTH * 2.)
9837 .px_1p5()
9838 .gap_1()
9839 // Workaround: For some reason, there's a gap if we don't do this
9840 .ml(-BORDER_WIDTH)
9841 .shadow(vec![gpui::BoxShadow {
9842 color: gpui::black().opacity(0.05),
9843 offset: point(px(1.), px(1.)),
9844 blur_radius: px(2.),
9845 spread_radius: px(0.),
9846 }])
9847 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9848 .border(BORDER_WIDTH)
9849 .border_color(cx.theme().colors().border)
9850 .rounded_r_lg()
9851 .id("edit_prediction_diff_popover_keybind")
9852 .when(!has_keybind, |el| {
9853 let status_colors = cx.theme().status();
9854
9855 el.bg(status_colors.error_background)
9856 .border_color(status_colors.error.opacity(0.6))
9857 .child(Icon::new(IconName::Info).color(Color::Error))
9858 .cursor_default()
9859 .hoverable_tooltip(move |_window, cx| {
9860 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9861 })
9862 })
9863 .children(keybind),
9864 )
9865 .into_any();
9866
9867 let longest_row =
9868 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9869 let longest_line_width = if visible_row_range.contains(&longest_row) {
9870 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9871 } else {
9872 layout_line(
9873 longest_row,
9874 editor_snapshot,
9875 style,
9876 editor_width,
9877 |_| false,
9878 window,
9879 cx,
9880 )
9881 .width
9882 };
9883
9884 let viewport_bounds =
9885 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9886 right: -right_margin,
9887 ..Default::default()
9888 });
9889
9890 let x_after_longest = Pixels::from(
9891 ScrollPixelOffset::from(
9892 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9893 ) - scroll_pixel_position.x,
9894 );
9895
9896 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9897
9898 // Fully visible if it can be displayed within the window (allow overlapping other
9899 // panes). However, this is only allowed if the popover starts within text_bounds.
9900 let can_position_to_the_right = x_after_longest < text_bounds.right()
9901 && x_after_longest + element_bounds.width < viewport_bounds.right();
9902
9903 let mut origin = if can_position_to_the_right {
9904 point(
9905 x_after_longest,
9906 text_bounds.origin.y
9907 + Pixels::from(
9908 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9909 - scroll_pixel_position.y,
9910 ),
9911 )
9912 } else {
9913 let cursor_row = newest_selection_head.map(|head| head.row());
9914 let above_edit = edit_start
9915 .row()
9916 .0
9917 .checked_sub(line_count as u32)
9918 .map(DisplayRow);
9919 let below_edit = Some(edit_end.row() + 1);
9920 let above_cursor =
9921 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9922 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9923
9924 // Place the edit popover adjacent to the edit if there is a location
9925 // available that is onscreen and does not obscure the cursor. Otherwise,
9926 // place it adjacent to the cursor.
9927 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9928 .into_iter()
9929 .flatten()
9930 .find(|&start_row| {
9931 let end_row = start_row + line_count as u32;
9932 visible_row_range.contains(&start_row)
9933 && visible_row_range.contains(&end_row)
9934 && cursor_row
9935 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9936 })?;
9937
9938 content_origin
9939 + point(
9940 Pixels::from(-scroll_pixel_position.x),
9941 Pixels::from(
9942 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9943 ),
9944 )
9945 };
9946
9947 origin.x -= BORDER_WIDTH;
9948
9949 window.with_content_mask(
9950 Some(gpui::ContentMask {
9951 bounds: *text_bounds,
9952 }),
9953 |window| {
9954 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9955 },
9956 );
9957
9958 // Do not return an element, since it will already be drawn due to defer_draw.
9959 None
9960 }
9961
9962 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9963 px(30.)
9964 }
9965
9966 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9967 if self.read_only(cx) {
9968 cx.theme().players().read_only()
9969 } else {
9970 self.style.as_ref().unwrap().local_player
9971 }
9972 }
9973
9974 fn render_edit_prediction_inline_keystroke(
9975 &self,
9976 keystroke: &gpui::KeybindingKeystroke,
9977 modifiers_color: Color,
9978 cx: &App,
9979 ) -> AnyElement {
9980 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9981
9982 h_flex()
9983 .px_0p5()
9984 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9985 .font(
9986 theme_settings::ThemeSettings::get_global(cx)
9987 .buffer_font
9988 .clone(),
9989 )
9990 .text_size(TextSize::XSmall.rems(cx))
9991 .child(h_flex().children(ui::render_modifiers(
9992 keystroke.modifiers(),
9993 PlatformStyle::platform(),
9994 Some(modifiers_color),
9995 Some(IconSize::XSmall.rems().into()),
9996 true,
9997 )))
9998 .when(is_platform_style_mac, |parent| {
9999 parent.child(keystroke.key().to_string())
10000 })
10001 .when(!is_platform_style_mac, |parent| {
10002 parent.child(
10003 Key::new(ui::utils::capitalize(keystroke.key()), Some(Color::Default))
10004 .size(Some(IconSize::XSmall.rems().into())),
10005 )
10006 })
10007 .into_any()
10008 }
10009
10010 fn render_edit_prediction_popover_keystroke(
10011 &self,
10012 keystroke: &gpui::KeybindingKeystroke,
10013 color: Color,
10014 cx: &App,
10015 ) -> AnyElement {
10016 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
10017
10018 if keystroke.modifiers().modified() {
10019 h_flex()
10020 .font(
10021 theme_settings::ThemeSettings::get_global(cx)
10022 .buffer_font
10023 .clone(),
10024 )
10025 .when(is_platform_style_mac, |parent| parent.gap_1())
10026 .child(h_flex().children(ui::render_modifiers(
10027 keystroke.modifiers(),
10028 PlatformStyle::platform(),
10029 Some(color),
10030 None,
10031 false,
10032 )))
10033 .into_any()
10034 } else {
10035 Key::new(ui::utils::capitalize(keystroke.key()), Some(color))
10036 .size(Some(IconSize::XSmall.rems().into()))
10037 .into_any_element()
10038 }
10039 }
10040
10041 fn render_edit_prediction_keybind(
10042 &self,
10043 window: &mut Window,
10044 cx: &mut App,
10045 ) -> Option<AnyElement> {
10046 let keybind_display =
10047 self.edit_prediction_keybind_display(EditPredictionKeybindSurface::Inline, window, cx);
10048 let keystroke = keybind_display.displayed_keystroke.as_ref()?;
10049
10050 let modifiers_color = if *keystroke.modifiers() == window.modifiers() {
10051 Color::Accent
10052 } else {
10053 Color::Muted
10054 };
10055
10056 Some(self.render_edit_prediction_inline_keystroke(keystroke, modifiers_color, cx))
10057 }
10058
10059 fn render_edit_prediction_line_popover(
10060 &self,
10061 label: impl Into<SharedString>,
10062 icon: Option<IconName>,
10063 window: &mut Window,
10064 cx: &mut App,
10065 ) -> Stateful<Div> {
10066 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
10067
10068 let keybind = self.render_edit_prediction_keybind(window, cx);
10069 let has_keybind = keybind.is_some();
10070 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10071
10072 h_flex()
10073 .id("ep-line-popover")
10074 .py_0p5()
10075 .pl_1()
10076 .pr(padding_right)
10077 .gap_1()
10078 .rounded_md()
10079 .border_1()
10080 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10081 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10082 .shadow_xs()
10083 .when(!has_keybind, |el| {
10084 let status_colors = cx.theme().status();
10085
10086 el.bg(status_colors.error_background)
10087 .border_color(status_colors.error.opacity(0.6))
10088 .pl_2()
10089 .child(Icon::new(icons.error).color(Color::Error))
10090 .cursor_default()
10091 .hoverable_tooltip(move |_window, cx| {
10092 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10093 })
10094 })
10095 .children(keybind)
10096 .child(
10097 Label::new(label)
10098 .size(LabelSize::Small)
10099 .when(!has_keybind, |el| {
10100 el.color(cx.theme().status().error.into()).strikethrough()
10101 }),
10102 )
10103 .when(!has_keybind, |el| {
10104 el.child(
10105 h_flex().ml_1().child(
10106 Icon::new(IconName::Info)
10107 .size(IconSize::Small)
10108 .color(cx.theme().status().error.into()),
10109 ),
10110 )
10111 })
10112 .when_some(icon, |element, icon| {
10113 element.child(
10114 div()
10115 .mt(px(1.5))
10116 .child(Icon::new(icon).size(IconSize::Small)),
10117 )
10118 })
10119 }
10120
10121 fn render_edit_prediction_jump_outside_popover(
10122 &self,
10123 snapshot: &BufferSnapshot,
10124 window: &mut Window,
10125 cx: &mut App,
10126 ) -> Stateful<Div> {
10127 let keybind = self.render_edit_prediction_keybind(window, cx);
10128 let has_keybind = keybind.is_some();
10129 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10130
10131 let file_name = snapshot
10132 .file()
10133 .map(|file| SharedString::new(file.file_name(cx)))
10134 .unwrap_or(SharedString::new_static("untitled"));
10135
10136 h_flex()
10137 .id("ep-jump-outside-popover")
10138 .py_1()
10139 .px_2()
10140 .gap_1()
10141 .rounded_md()
10142 .border_1()
10143 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10144 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10145 .shadow_xs()
10146 .when(!has_keybind, |el| {
10147 let status_colors = cx.theme().status();
10148
10149 el.bg(status_colors.error_background)
10150 .border_color(status_colors.error.opacity(0.6))
10151 .pl_2()
10152 .child(Icon::new(icons.error).color(Color::Error))
10153 .cursor_default()
10154 .hoverable_tooltip(move |_window, cx| {
10155 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10156 })
10157 })
10158 .children(keybind)
10159 .child(
10160 Label::new(file_name)
10161 .size(LabelSize::Small)
10162 .buffer_font(cx)
10163 .when(!has_keybind, |el| {
10164 el.color(cx.theme().status().error.into()).strikethrough()
10165 }),
10166 )
10167 .when(!has_keybind, |el| {
10168 el.child(
10169 h_flex().ml_1().child(
10170 Icon::new(IconName::Info)
10171 .size(IconSize::Small)
10172 .color(cx.theme().status().error.into()),
10173 ),
10174 )
10175 })
10176 .child(
10177 div()
10178 .mt(px(1.5))
10179 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10180 )
10181 }
10182
10183 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10184 let accent_color = cx.theme().colors().text_accent;
10185 let editor_bg_color = cx.theme().colors().editor_background;
10186 editor_bg_color.blend(accent_color.opacity(0.1))
10187 }
10188
10189 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10190 let accent_color = cx.theme().colors().text_accent;
10191 let editor_bg_color = cx.theme().colors().editor_background;
10192 editor_bg_color.blend(accent_color.opacity(0.6))
10193 }
10194 fn get_prediction_provider_icons(
10195 provider: &Option<RegisteredEditPredictionDelegate>,
10196 cx: &App,
10197 ) -> edit_prediction_types::EditPredictionIconSet {
10198 match provider {
10199 Some(provider) => provider.provider.icons(cx),
10200 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10201 }
10202 }
10203
10204 fn render_edit_prediction_cursor_popover(
10205 &self,
10206 min_width: Pixels,
10207 max_width: Pixels,
10208 cursor_point: Point,
10209 style: &EditorStyle,
10210 window: &mut Window,
10211 cx: &mut Context<Editor>,
10212 ) -> Option<AnyElement> {
10213 let provider = self.edit_prediction_provider.as_ref()?;
10214 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10215
10216 let is_refreshing = provider.provider.is_refreshing(cx);
10217
10218 fn pending_completion_container(icon: IconName) -> Div {
10219 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10220 }
10221
10222 let completion = match &self.active_edit_prediction {
10223 Some(prediction) => {
10224 if !self.has_visible_completions_menu() {
10225 const RADIUS: Pixels = px(6.);
10226 const BORDER_WIDTH: Pixels = px(1.);
10227 let keybind_display = self.edit_prediction_keybind_display(
10228 EditPredictionKeybindSurface::CursorPopoverCompact,
10229 window,
10230 cx,
10231 );
10232
10233 return Some(
10234 h_flex()
10235 .elevation_2(cx)
10236 .border(BORDER_WIDTH)
10237 .border_color(cx.theme().colors().border)
10238 .when(keybind_display.missing_accept_keystroke, |el| {
10239 el.border_color(cx.theme().status().error)
10240 })
10241 .rounded(RADIUS)
10242 .rounded_tl(px(0.))
10243 .overflow_hidden()
10244 .child(div().px_1p5().child(match &prediction.completion {
10245 EditPrediction::MoveWithin { target, snapshot } => {
10246 use text::ToPoint as _;
10247 if target.text_anchor_in(&snapshot).to_point(snapshot).row
10248 > cursor_point.row
10249 {
10250 Icon::new(icons.down)
10251 } else {
10252 Icon::new(icons.up)
10253 }
10254 }
10255 EditPrediction::MoveOutside { .. } => {
10256 // TODO [zeta2] custom icon for external jump?
10257 Icon::new(icons.base)
10258 }
10259 EditPrediction::Edit { .. } => Icon::new(icons.base),
10260 }))
10261 .child(
10262 h_flex()
10263 .gap_1()
10264 .py_1()
10265 .px_2()
10266 .rounded_r(RADIUS - BORDER_WIDTH)
10267 .border_l_1()
10268 .border_color(cx.theme().colors().border)
10269 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10270 .when(keybind_display.show_hold_label, |el| {
10271 el.child(
10272 Label::new("Hold")
10273 .size(LabelSize::Small)
10274 .when(
10275 keybind_display.missing_accept_keystroke,
10276 |el| el.strikethrough(),
10277 )
10278 .line_height_style(LineHeightStyle::UiLabel),
10279 )
10280 })
10281 .id("edit_prediction_cursor_popover_keybind")
10282 .when(keybind_display.missing_accept_keystroke, |el| {
10283 let status_colors = cx.theme().status();
10284
10285 el.bg(status_colors.error_background)
10286 .border_color(status_colors.error.opacity(0.6))
10287 .child(Icon::new(IconName::Info).color(Color::Error))
10288 .cursor_default()
10289 .hoverable_tooltip(move |_window, cx| {
10290 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10291 .into()
10292 })
10293 })
10294 .when_some(
10295 keybind_display.displayed_keystroke.as_ref(),
10296 |el, compact_keystroke| {
10297 el.child(self.render_edit_prediction_popover_keystroke(
10298 compact_keystroke,
10299 Color::Default,
10300 cx,
10301 ))
10302 },
10303 ),
10304 )
10305 .into_any(),
10306 );
10307 }
10308
10309 self.render_edit_prediction_cursor_popover_preview(
10310 prediction,
10311 cursor_point,
10312 style,
10313 cx,
10314 )?
10315 }
10316
10317 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10318 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10319 stale_completion,
10320 cursor_point,
10321 style,
10322 cx,
10323 )?,
10324
10325 None => pending_completion_container(icons.base)
10326 .child(Label::new("...").size(LabelSize::Small)),
10327 },
10328
10329 None => pending_completion_container(icons.base)
10330 .child(Label::new("...").size(LabelSize::Small)),
10331 };
10332
10333 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10334 completion
10335 .with_animation(
10336 "loading-completion",
10337 Animation::new(Duration::from_secs(2))
10338 .repeat()
10339 .with_easing(pulsating_between(0.4, 0.8)),
10340 |label, delta| label.opacity(delta),
10341 )
10342 .into_any_element()
10343 } else {
10344 completion.into_any_element()
10345 };
10346
10347 let has_completion = self.active_edit_prediction.is_some();
10348 let keybind_display = self.edit_prediction_keybind_display(
10349 EditPredictionKeybindSurface::CursorPopoverExpanded,
10350 window,
10351 cx,
10352 );
10353
10354 Some(
10355 h_flex()
10356 .min_w(min_width)
10357 .max_w(max_width)
10358 .flex_1()
10359 .elevation_2(cx)
10360 .border_color(cx.theme().colors().border)
10361 .child(
10362 div()
10363 .flex_1()
10364 .py_1()
10365 .px_2()
10366 .overflow_hidden()
10367 .child(completion),
10368 )
10369 .when_some(
10370 keybind_display.displayed_keystroke.as_ref(),
10371 |el, keystroke| {
10372 let key_color = if !has_completion {
10373 Color::Muted
10374 } else {
10375 Color::Default
10376 };
10377
10378 if keybind_display.action == EditPredictionKeybindAction::Preview {
10379 el.child(
10380 h_flex()
10381 .h_full()
10382 .border_l_1()
10383 .rounded_r_lg()
10384 .border_color(cx.theme().colors().border)
10385 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10386 .gap_1()
10387 .py_1()
10388 .px_2()
10389 .child(self.render_edit_prediction_popover_keystroke(
10390 keystroke, key_color, cx,
10391 ))
10392 .child(Label::new("Preview").into_any_element())
10393 .opacity(if has_completion { 1.0 } else { 0.4 }),
10394 )
10395 } else {
10396 el.child(
10397 h_flex()
10398 .h_full()
10399 .border_l_1()
10400 .rounded_r_lg()
10401 .border_color(cx.theme().colors().border)
10402 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10403 .gap_1()
10404 .py_1()
10405 .px_2()
10406 .child(self.render_edit_prediction_popover_keystroke(
10407 keystroke, key_color, cx,
10408 ))
10409 .opacity(if has_completion { 1.0 } else { 0.4 }),
10410 )
10411 }
10412 },
10413 )
10414 .into_any(),
10415 )
10416 }
10417
10418 fn render_edit_prediction_cursor_popover_preview(
10419 &self,
10420 completion: &EditPredictionState,
10421 cursor_point: Point,
10422 style: &EditorStyle,
10423 cx: &mut Context<Editor>,
10424 ) -> Option<Div> {
10425 use text::ToPoint as _;
10426
10427 fn render_relative_row_jump(
10428 prefix: impl Into<String>,
10429 current_row: u32,
10430 target_row: u32,
10431 ) -> Div {
10432 let (row_diff, arrow) = if target_row < current_row {
10433 (current_row - target_row, IconName::ArrowUp)
10434 } else {
10435 (target_row - current_row, IconName::ArrowDown)
10436 };
10437
10438 h_flex()
10439 .child(
10440 Label::new(format!("{}{}", prefix.into(), row_diff))
10441 .color(Color::Muted)
10442 .size(LabelSize::Small),
10443 )
10444 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10445 }
10446
10447 let supports_jump = self
10448 .edit_prediction_provider
10449 .as_ref()
10450 .map(|provider| provider.provider.supports_jump_to_edit())
10451 .unwrap_or(true);
10452
10453 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10454
10455 match &completion.completion {
10456 EditPrediction::MoveWithin {
10457 target, snapshot, ..
10458 } => {
10459 if !supports_jump {
10460 return None;
10461 }
10462 let (target, _) = self.display_snapshot(cx).anchor_to_buffer_anchor(*target)?;
10463
10464 Some(
10465 h_flex()
10466 .px_2()
10467 .gap_2()
10468 .flex_1()
10469 .child(if target.to_point(snapshot).row > cursor_point.row {
10470 Icon::new(icons.down)
10471 } else {
10472 Icon::new(icons.up)
10473 })
10474 .child(Label::new("Jump to Edit")),
10475 )
10476 }
10477 EditPrediction::MoveOutside { snapshot, .. } => {
10478 let file_name = snapshot
10479 .file()
10480 .map(|file| file.file_name(cx))
10481 .unwrap_or("untitled");
10482 Some(
10483 h_flex()
10484 .px_2()
10485 .gap_2()
10486 .flex_1()
10487 .child(Icon::new(icons.base))
10488 .child(Label::new(format!("Jump to {file_name}"))),
10489 )
10490 }
10491 EditPrediction::Edit {
10492 edits,
10493 edit_preview,
10494 snapshot,
10495 ..
10496 } => {
10497 let first_edit_row = self
10498 .display_snapshot(cx)
10499 .anchor_to_buffer_anchor(edits.first()?.0.start)?
10500 .0
10501 .to_point(snapshot)
10502 .row;
10503
10504 let (highlighted_edits, has_more_lines) =
10505 if let Some(edit_preview) = edit_preview.as_ref() {
10506 crate::edit_prediction_edit_text(
10507 snapshot,
10508 edits,
10509 edit_preview,
10510 true,
10511 &self.display_snapshot(cx),
10512 cx,
10513 )
10514 .first_line_preview()
10515 } else {
10516 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10517 };
10518
10519 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10520 .with_default_highlights(&style.text, highlighted_edits.highlights);
10521
10522 let preview = h_flex()
10523 .gap_1()
10524 .min_w_16()
10525 .child(styled_text)
10526 .when(has_more_lines, |parent| parent.child("…"));
10527
10528 let left = if supports_jump && first_edit_row != cursor_point.row {
10529 render_relative_row_jump("", cursor_point.row, first_edit_row)
10530 .into_any_element()
10531 } else {
10532 Icon::new(icons.base).into_any_element()
10533 };
10534
10535 Some(
10536 h_flex()
10537 .h_full()
10538 .flex_1()
10539 .gap_2()
10540 .pr_1()
10541 .overflow_x_hidden()
10542 .font(
10543 theme_settings::ThemeSettings::get_global(cx)
10544 .buffer_font
10545 .clone(),
10546 )
10547 .child(left)
10548 .child(preview),
10549 )
10550 }
10551 }
10552 }
10553
10554 pub fn render_context_menu(
10555 &mut self,
10556 max_height_in_lines: u32,
10557 window: &mut Window,
10558 cx: &mut Context<Editor>,
10559 ) -> Option<AnyElement> {
10560 let menu = self.context_menu.borrow();
10561 let menu = menu.as_ref()?;
10562 if !menu.visible() {
10563 return None;
10564 };
10565 self.style
10566 .as_ref()
10567 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10568 }
10569
10570 fn render_context_menu_aside(
10571 &mut self,
10572 max_size: Size<Pixels>,
10573 window: &mut Window,
10574 cx: &mut Context<Editor>,
10575 ) -> Option<AnyElement> {
10576 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10577 if menu.visible() {
10578 menu.render_aside(max_size, window, cx)
10579 } else {
10580 None
10581 }
10582 })
10583 }
10584
10585 fn hide_context_menu(
10586 &mut self,
10587 window: &mut Window,
10588 cx: &mut Context<Self>,
10589 ) -> Option<CodeContextMenu> {
10590 cx.notify();
10591 self.completion_tasks.clear();
10592 let context_menu = self.context_menu.borrow_mut().take();
10593 self.stale_edit_prediction_in_menu.take();
10594 self.update_visible_edit_prediction(window, cx);
10595 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10596 && let Some(completion_provider) = &self.completion_provider
10597 {
10598 completion_provider.selection_changed(None, window, cx);
10599 }
10600 context_menu
10601 }
10602
10603 fn show_snippet_choices(
10604 &mut self,
10605 choices: &Vec<String>,
10606 selection: Range<Anchor>,
10607 cx: &mut Context<Self>,
10608 ) {
10609 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
10610 let Some((buffer_snapshot, range)) =
10611 buffer_snapshot.anchor_range_to_buffer_anchor_range(selection.clone())
10612 else {
10613 return;
10614 };
10615 let Some(buffer) = self.buffer.read(cx).buffer(buffer_snapshot.remote_id()) else {
10616 return;
10617 };
10618
10619 let id = post_inc(&mut self.next_completion_id);
10620 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10621 let mut context_menu = self.context_menu.borrow_mut();
10622 let old_menu = context_menu.take();
10623 *context_menu = Some(CodeContextMenu::Completions(
10624 CompletionsMenu::new_snippet_choices(
10625 id,
10626 true,
10627 choices,
10628 selection.start,
10629 range,
10630 buffer,
10631 old_menu.map(|menu| menu.primary_scroll_handle()),
10632 snippet_sort_order,
10633 ),
10634 ));
10635 }
10636
10637 pub fn insert_snippet(
10638 &mut self,
10639 insertion_ranges: &[Range<MultiBufferOffset>],
10640 snippet: Snippet,
10641 window: &mut Window,
10642 cx: &mut Context<Self>,
10643 ) -> Result<()> {
10644 struct Tabstop<T> {
10645 is_end_tabstop: bool,
10646 ranges: Vec<Range<T>>,
10647 choices: Option<Vec<String>>,
10648 }
10649
10650 let tabstops = self.buffer.update(cx, |buffer, cx| {
10651 let snippet_text: Arc<str> = snippet.text.clone().into();
10652 let edits = insertion_ranges
10653 .iter()
10654 .cloned()
10655 .map(|range| (range, snippet_text.clone()));
10656 let autoindent_mode = AutoindentMode::Block {
10657 original_indent_columns: Vec::new(),
10658 };
10659 buffer.edit(edits, Some(autoindent_mode), cx);
10660
10661 let snapshot = &*buffer.read(cx);
10662 let snippet = &snippet;
10663 snippet
10664 .tabstops
10665 .iter()
10666 .map(|tabstop| {
10667 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10668 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10669 });
10670 let mut tabstop_ranges = tabstop
10671 .ranges
10672 .iter()
10673 .flat_map(|tabstop_range| {
10674 let mut delta = 0_isize;
10675 insertion_ranges.iter().map(move |insertion_range| {
10676 let insertion_start = insertion_range.start + delta;
10677 delta += snippet.text.len() as isize
10678 - (insertion_range.end - insertion_range.start) as isize;
10679
10680 let start =
10681 (insertion_start + tabstop_range.start).min(snapshot.len());
10682 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10683 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10684 })
10685 })
10686 .collect::<Vec<_>>();
10687 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10688
10689 Tabstop {
10690 is_end_tabstop,
10691 ranges: tabstop_ranges,
10692 choices: tabstop.choices.clone(),
10693 }
10694 })
10695 .collect::<Vec<_>>()
10696 });
10697 if let Some(tabstop) = tabstops.first() {
10698 self.change_selections(Default::default(), window, cx, |s| {
10699 // Reverse order so that the first range is the newest created selection.
10700 // Completions will use it and autoscroll will prioritize it.
10701 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10702 });
10703
10704 if let Some(choices) = &tabstop.choices
10705 && let Some(selection) = tabstop.ranges.first()
10706 {
10707 self.show_snippet_choices(choices, selection.clone(), cx)
10708 }
10709
10710 // If we're already at the last tabstop and it's at the end of the snippet,
10711 // we're done, we don't need to keep the state around.
10712 if !tabstop.is_end_tabstop {
10713 let choices = tabstops
10714 .iter()
10715 .map(|tabstop| tabstop.choices.clone())
10716 .collect();
10717
10718 let ranges = tabstops
10719 .into_iter()
10720 .map(|tabstop| tabstop.ranges)
10721 .collect::<Vec<_>>();
10722
10723 self.snippet_stack.push(SnippetState {
10724 active_index: 0,
10725 ranges,
10726 choices,
10727 });
10728 }
10729
10730 // Check whether the just-entered snippet ends with an auto-closable bracket.
10731 if self.autoclose_regions.is_empty() {
10732 let snapshot = self.buffer.read(cx).snapshot(cx);
10733 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10734 let selection_head = selection.head();
10735 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10736 continue;
10737 };
10738
10739 let mut bracket_pair = None;
10740 let max_lookup_length = scope
10741 .brackets()
10742 .map(|(pair, _)| {
10743 pair.start
10744 .as_str()
10745 .chars()
10746 .count()
10747 .max(pair.end.as_str().chars().count())
10748 })
10749 .max();
10750 if let Some(max_lookup_length) = max_lookup_length {
10751 let next_text = snapshot
10752 .chars_at(selection_head)
10753 .take(max_lookup_length)
10754 .collect::<String>();
10755 let prev_text = snapshot
10756 .reversed_chars_at(selection_head)
10757 .take(max_lookup_length)
10758 .collect::<String>();
10759
10760 for (pair, enabled) in scope.brackets() {
10761 if enabled
10762 && pair.close
10763 && prev_text.starts_with(pair.start.as_str())
10764 && next_text.starts_with(pair.end.as_str())
10765 {
10766 bracket_pair = Some(pair.clone());
10767 break;
10768 }
10769 }
10770 }
10771
10772 if let Some(pair) = bracket_pair {
10773 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10774 let autoclose_enabled =
10775 self.use_autoclose && snapshot_settings.use_autoclose;
10776 if autoclose_enabled {
10777 let start = snapshot.anchor_after(selection_head);
10778 let end = snapshot.anchor_after(selection_head);
10779 self.autoclose_regions.push(AutocloseRegion {
10780 selection_id: selection.id,
10781 range: start..end,
10782 pair,
10783 });
10784 }
10785 }
10786 }
10787 }
10788 }
10789 Ok(())
10790 }
10791
10792 pub fn move_to_next_snippet_tabstop(
10793 &mut self,
10794 window: &mut Window,
10795 cx: &mut Context<Self>,
10796 ) -> bool {
10797 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10798 }
10799
10800 pub fn move_to_prev_snippet_tabstop(
10801 &mut self,
10802 window: &mut Window,
10803 cx: &mut Context<Self>,
10804 ) -> bool {
10805 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10806 }
10807
10808 pub fn move_to_snippet_tabstop(
10809 &mut self,
10810 bias: Bias,
10811 window: &mut Window,
10812 cx: &mut Context<Self>,
10813 ) -> bool {
10814 if let Some(mut snippet) = self.snippet_stack.pop() {
10815 match bias {
10816 Bias::Left => {
10817 if snippet.active_index > 0 {
10818 snippet.active_index -= 1;
10819 } else {
10820 self.snippet_stack.push(snippet);
10821 return false;
10822 }
10823 }
10824 Bias::Right => {
10825 if snippet.active_index + 1 < snippet.ranges.len() {
10826 snippet.active_index += 1;
10827 } else {
10828 self.snippet_stack.push(snippet);
10829 return false;
10830 }
10831 }
10832 }
10833 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10834 self.change_selections(Default::default(), window, cx, |s| {
10835 // Reverse order so that the first range is the newest created selection.
10836 // Completions will use it and autoscroll will prioritize it.
10837 s.select_ranges(current_ranges.iter().rev().cloned())
10838 });
10839
10840 if let Some(choices) = &snippet.choices[snippet.active_index]
10841 && let Some(selection) = current_ranges.first()
10842 {
10843 self.show_snippet_choices(choices, selection.clone(), cx);
10844 }
10845
10846 // If snippet state is not at the last tabstop, push it back on the stack
10847 if snippet.active_index + 1 < snippet.ranges.len() {
10848 self.snippet_stack.push(snippet);
10849 }
10850 return true;
10851 }
10852 }
10853
10854 false
10855 }
10856
10857 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10858 self.transact(window, cx, |this, window, cx| {
10859 this.select_all(&SelectAll, window, cx);
10860 this.insert("", window, cx);
10861 });
10862 }
10863
10864 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10865 if self.read_only(cx) {
10866 return;
10867 }
10868 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10869 self.transact(window, cx, |this, window, cx| {
10870 this.select_autoclose_pair(window, cx);
10871
10872 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10873
10874 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10875 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10876 for selection in &mut selections {
10877 if selection.is_empty() {
10878 let old_head = selection.head();
10879 let mut new_head =
10880 movement::left(&display_map, old_head.to_display_point(&display_map))
10881 .to_point(&display_map);
10882 if let Some((buffer, line_buffer_range)) = display_map
10883 .buffer_snapshot()
10884 .buffer_line_for_row(MultiBufferRow(old_head.row))
10885 {
10886 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10887 let indent_len = match indent_size.kind {
10888 IndentKind::Space => {
10889 buffer.settings_at(line_buffer_range.start, cx).tab_size
10890 }
10891 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10892 };
10893 if old_head.column <= indent_size.len && old_head.column > 0 {
10894 let indent_len = indent_len.get();
10895 new_head = cmp::min(
10896 new_head,
10897 MultiBufferPoint::new(
10898 old_head.row,
10899 ((old_head.column - 1) / indent_len) * indent_len,
10900 ),
10901 );
10902 }
10903 }
10904
10905 selection.set_head(new_head, SelectionGoal::None);
10906 }
10907 }
10908
10909 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10910 this.insert("", window, cx);
10911 linked_edits.apply_with_left_expansion(cx);
10912 this.refresh_edit_prediction(true, false, window, cx);
10913 refresh_linked_ranges(this, window, cx);
10914 });
10915 }
10916
10917 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10918 if self.read_only(cx) {
10919 return;
10920 }
10921 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10922 self.transact(window, cx, |this, window, cx| {
10923 this.change_selections(Default::default(), window, cx, |s| {
10924 s.move_with(&mut |map, selection| {
10925 if selection.is_empty() {
10926 let cursor = movement::right(map, selection.head());
10927 selection.end = cursor;
10928 selection.reversed = true;
10929 selection.goal = SelectionGoal::None;
10930 }
10931 })
10932 });
10933 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10934 this.insert("", window, cx);
10935 linked_edits.apply(cx);
10936 this.refresh_edit_prediction(true, false, window, cx);
10937 refresh_linked_ranges(this, window, cx);
10938 });
10939 }
10940
10941 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10942 if self.mode.is_single_line() {
10943 cx.propagate();
10944 return;
10945 }
10946
10947 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10948 if self.move_to_prev_snippet_tabstop(window, cx) {
10949 return;
10950 }
10951 self.outdent(&Outdent, window, cx);
10952 }
10953
10954 pub fn next_snippet_tabstop(
10955 &mut self,
10956 _: &NextSnippetTabstop,
10957 window: &mut Window,
10958 cx: &mut Context<Self>,
10959 ) {
10960 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10961 cx.propagate();
10962 return;
10963 }
10964
10965 if self.move_to_next_snippet_tabstop(window, cx) {
10966 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10967 return;
10968 }
10969 cx.propagate();
10970 }
10971
10972 pub fn previous_snippet_tabstop(
10973 &mut self,
10974 _: &PreviousSnippetTabstop,
10975 window: &mut Window,
10976 cx: &mut Context<Self>,
10977 ) {
10978 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10979 cx.propagate();
10980 return;
10981 }
10982
10983 if self.move_to_prev_snippet_tabstop(window, cx) {
10984 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10985 return;
10986 }
10987 cx.propagate();
10988 }
10989
10990 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10991 if self.mode.is_single_line() {
10992 cx.propagate();
10993 return;
10994 }
10995
10996 if self.move_to_next_snippet_tabstop(window, cx) {
10997 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10998 return;
10999 }
11000 if self.read_only(cx) {
11001 return;
11002 }
11003 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11004 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
11005 let buffer = self.buffer.read(cx);
11006 let snapshot = buffer.snapshot(cx);
11007 let rows_iter = selections.iter().map(|s| s.head().row);
11008 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
11009
11010 let has_some_cursor_in_whitespace = selections
11011 .iter()
11012 .filter(|selection| selection.is_empty())
11013 .any(|selection| {
11014 let cursor = selection.head();
11015 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
11016 cursor.column < current_indent.len
11017 });
11018
11019 let mut edits = Vec::new();
11020 let mut prev_edited_row = 0;
11021 let mut row_delta = 0;
11022 for selection in &mut selections {
11023 if selection.start.row != prev_edited_row {
11024 row_delta = 0;
11025 }
11026 prev_edited_row = selection.end.row;
11027
11028 // If cursor is after a list prefix, make selection non-empty to trigger line indent
11029 if selection.is_empty() {
11030 let cursor = selection.head();
11031 let settings = buffer.language_settings_at(cursor, cx);
11032 if settings.indent_list_on_tab {
11033 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
11034 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
11035 row_delta = Self::indent_selection(
11036 buffer, &snapshot, selection, &mut edits, row_delta, cx,
11037 );
11038 continue;
11039 }
11040 }
11041 }
11042 }
11043
11044 // If the selection is non-empty, then increase the indentation of the selected lines.
11045 if !selection.is_empty() {
11046 row_delta =
11047 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11048 continue;
11049 }
11050
11051 let cursor = selection.head();
11052 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
11053 if let Some(suggested_indent) =
11054 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
11055 {
11056 // Don't do anything if already at suggested indent
11057 // and there is any other cursor which is not
11058 if has_some_cursor_in_whitespace
11059 && cursor.column == current_indent.len
11060 && current_indent.len == suggested_indent.len
11061 {
11062 continue;
11063 }
11064
11065 // Adjust line and move cursor to suggested indent
11066 // if cursor is not at suggested indent
11067 if cursor.column < suggested_indent.len
11068 && cursor.column <= current_indent.len
11069 && current_indent.len <= suggested_indent.len
11070 {
11071 selection.start = Point::new(cursor.row, suggested_indent.len);
11072 selection.end = selection.start;
11073 if row_delta == 0 {
11074 edits.extend(Buffer::edit_for_indent_size_adjustment(
11075 cursor.row,
11076 current_indent,
11077 suggested_indent,
11078 ));
11079 row_delta = suggested_indent.len - current_indent.len;
11080 }
11081 continue;
11082 }
11083
11084 // If current indent is more than suggested indent
11085 // only move cursor to current indent and skip indent
11086 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
11087 selection.start = Point::new(cursor.row, current_indent.len);
11088 selection.end = selection.start;
11089 continue;
11090 }
11091 }
11092
11093 // Otherwise, insert a hard or soft tab.
11094 let settings = buffer.language_settings_at(cursor, cx);
11095 let tab_size = if settings.hard_tabs {
11096 IndentSize::tab()
11097 } else {
11098 let tab_size = settings.tab_size.get();
11099 let indent_remainder = snapshot
11100 .text_for_range(Point::new(cursor.row, 0)..cursor)
11101 .flat_map(str::chars)
11102 .fold(row_delta % tab_size, |counter: u32, c| {
11103 if c == '\t' {
11104 0
11105 } else {
11106 (counter + 1) % tab_size
11107 }
11108 });
11109
11110 let chars_to_next_tab_stop = tab_size - indent_remainder;
11111 IndentSize::spaces(chars_to_next_tab_stop)
11112 };
11113 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
11114 selection.end = selection.start;
11115 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11116 row_delta += tab_size.len;
11117 }
11118
11119 self.transact(window, cx, |this, window, cx| {
11120 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11121 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11122 this.refresh_edit_prediction(true, false, window, cx);
11123 });
11124 }
11125
11126 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11127 if self.read_only(cx) {
11128 return;
11129 }
11130 if self.mode.is_single_line() {
11131 cx.propagate();
11132 return;
11133 }
11134
11135 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11136 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11137 let mut prev_edited_row = 0;
11138 let mut row_delta = 0;
11139 let mut edits = Vec::new();
11140 let buffer = self.buffer.read(cx);
11141 let snapshot = buffer.snapshot(cx);
11142 for selection in &mut selections {
11143 if selection.start.row != prev_edited_row {
11144 row_delta = 0;
11145 }
11146 prev_edited_row = selection.end.row;
11147
11148 row_delta =
11149 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11150 }
11151
11152 self.transact(window, cx, |this, window, cx| {
11153 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11154 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11155 });
11156 }
11157
11158 fn indent_selection(
11159 buffer: &MultiBuffer,
11160 snapshot: &MultiBufferSnapshot,
11161 selection: &mut Selection<Point>,
11162 edits: &mut Vec<(Range<Point>, String)>,
11163 delta_for_start_row: u32,
11164 cx: &App,
11165 ) -> u32 {
11166 let settings = buffer.language_settings_at(selection.start, cx);
11167 let tab_size = settings.tab_size.get();
11168 let indent_kind = if settings.hard_tabs {
11169 IndentKind::Tab
11170 } else {
11171 IndentKind::Space
11172 };
11173 let mut start_row = selection.start.row;
11174 let mut end_row = selection.end.row + 1;
11175
11176 // If a selection ends at the beginning of a line, don't indent
11177 // that last line.
11178 if selection.end.column == 0 && selection.end.row > selection.start.row {
11179 end_row -= 1;
11180 }
11181
11182 // Avoid re-indenting a row that has already been indented by a
11183 // previous selection, but still update this selection's column
11184 // to reflect that indentation.
11185 if delta_for_start_row > 0 {
11186 start_row += 1;
11187 selection.start.column += delta_for_start_row;
11188 if selection.end.row == selection.start.row {
11189 selection.end.column += delta_for_start_row;
11190 }
11191 }
11192
11193 let mut delta_for_end_row = 0;
11194 let has_multiple_rows = start_row + 1 != end_row;
11195 for row in start_row..end_row {
11196 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11197 let indent_delta = match (current_indent.kind, indent_kind) {
11198 (IndentKind::Space, IndentKind::Space) => {
11199 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11200 IndentSize::spaces(columns_to_next_tab_stop)
11201 }
11202 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11203 (_, IndentKind::Tab) => IndentSize::tab(),
11204 };
11205
11206 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11207 0
11208 } else {
11209 selection.start.column
11210 };
11211 let row_start = Point::new(row, start);
11212 edits.push((
11213 row_start..row_start,
11214 indent_delta.chars().collect::<String>(),
11215 ));
11216
11217 // Update this selection's endpoints to reflect the indentation.
11218 if row == selection.start.row {
11219 selection.start.column += indent_delta.len;
11220 }
11221 if row == selection.end.row {
11222 selection.end.column += indent_delta.len;
11223 delta_for_end_row = indent_delta.len;
11224 }
11225 }
11226
11227 if selection.start.row == selection.end.row {
11228 delta_for_start_row + delta_for_end_row
11229 } else {
11230 delta_for_end_row
11231 }
11232 }
11233
11234 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11235 if self.read_only(cx) {
11236 return;
11237 }
11238 if self.mode.is_single_line() {
11239 cx.propagate();
11240 return;
11241 }
11242
11243 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11244 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11245 let selections = self.selections.all::<Point>(&display_map);
11246 let mut deletion_ranges = Vec::new();
11247 let mut last_outdent = None;
11248 {
11249 let buffer = self.buffer.read(cx);
11250 let snapshot = buffer.snapshot(cx);
11251 for selection in &selections {
11252 let settings = buffer.language_settings_at(selection.start, cx);
11253 let tab_size = settings.tab_size.get();
11254 let mut rows = selection.spanned_rows(false, &display_map);
11255
11256 // Avoid re-outdenting a row that has already been outdented by a
11257 // previous selection.
11258 if let Some(last_row) = last_outdent
11259 && last_row == rows.start
11260 {
11261 rows.start = rows.start.next_row();
11262 }
11263 let has_multiple_rows = rows.len() > 1;
11264 for row in rows.iter_rows() {
11265 let indent_size = snapshot.indent_size_for_line(row);
11266 if indent_size.len > 0 {
11267 let deletion_len = match indent_size.kind {
11268 IndentKind::Space => {
11269 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11270 if columns_to_prev_tab_stop == 0 {
11271 tab_size
11272 } else {
11273 columns_to_prev_tab_stop
11274 }
11275 }
11276 IndentKind::Tab => 1,
11277 };
11278 let start = if has_multiple_rows
11279 || deletion_len > selection.start.column
11280 || indent_size.len < selection.start.column
11281 {
11282 0
11283 } else {
11284 selection.start.column - deletion_len
11285 };
11286 deletion_ranges.push(
11287 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11288 );
11289 last_outdent = Some(row);
11290 }
11291 }
11292 }
11293 }
11294
11295 self.transact(window, cx, |this, window, cx| {
11296 this.buffer.update(cx, |buffer, cx| {
11297 let empty_str: Arc<str> = Arc::default();
11298 buffer.edit(
11299 deletion_ranges
11300 .into_iter()
11301 .map(|range| (range, empty_str.clone())),
11302 None,
11303 cx,
11304 );
11305 });
11306 let selections = this
11307 .selections
11308 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11309 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11310 });
11311 }
11312
11313 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11314 if self.read_only(cx) {
11315 return;
11316 }
11317 if self.mode.is_single_line() {
11318 cx.propagate();
11319 return;
11320 }
11321
11322 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11323 let selections = self
11324 .selections
11325 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11326 .into_iter()
11327 .map(|s| s.range());
11328
11329 self.transact(window, cx, |this, window, cx| {
11330 this.buffer.update(cx, |buffer, cx| {
11331 buffer.autoindent_ranges(selections, cx);
11332 });
11333 let selections = this
11334 .selections
11335 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11336 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11337 });
11338 }
11339
11340 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11341 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11342 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11343 let selections = self.selections.all::<Point>(&display_map);
11344
11345 let mut new_cursors = Vec::new();
11346 let mut edit_ranges = Vec::new();
11347 let mut selections = selections.iter().peekable();
11348 while let Some(selection) = selections.next() {
11349 let mut rows = selection.spanned_rows(false, &display_map);
11350
11351 // Accumulate contiguous regions of rows that we want to delete.
11352 while let Some(next_selection) = selections.peek() {
11353 let next_rows = next_selection.spanned_rows(false, &display_map);
11354 if next_rows.start <= rows.end {
11355 rows.end = next_rows.end;
11356 selections.next().unwrap();
11357 } else {
11358 break;
11359 }
11360 }
11361
11362 let buffer = display_map.buffer_snapshot();
11363 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11364 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11365 // If there's a line after the range, delete the \n from the end of the row range
11366 (
11367 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11368 rows.end,
11369 )
11370 } else {
11371 // If there isn't a line after the range, delete the \n from the line before the
11372 // start of the row range
11373 edit_start = edit_start.saturating_sub_usize(1);
11374 (buffer.len(), rows.start.previous_row())
11375 };
11376
11377 let text_layout_details = self.text_layout_details(window, cx);
11378 let x = display_map.x_for_display_point(
11379 selection.head().to_display_point(&display_map),
11380 &text_layout_details,
11381 );
11382 let row = Point::new(target_row.0, 0)
11383 .to_display_point(&display_map)
11384 .row();
11385 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11386
11387 new_cursors.push((
11388 selection.id,
11389 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11390 SelectionGoal::None,
11391 ));
11392 edit_ranges.push(edit_start..edit_end);
11393 }
11394
11395 self.transact(window, cx, |this, window, cx| {
11396 let buffer = this.buffer.update(cx, |buffer, cx| {
11397 let empty_str: Arc<str> = Arc::default();
11398 buffer.edit(
11399 edit_ranges
11400 .into_iter()
11401 .map(|range| (range, empty_str.clone())),
11402 None,
11403 cx,
11404 );
11405 buffer.snapshot(cx)
11406 });
11407 let new_selections = new_cursors
11408 .into_iter()
11409 .map(|(id, cursor, goal)| {
11410 let cursor = cursor.to_point(&buffer);
11411 Selection {
11412 id,
11413 start: cursor,
11414 end: cursor,
11415 reversed: false,
11416 goal,
11417 }
11418 })
11419 .collect();
11420
11421 this.change_selections(Default::default(), window, cx, |s| {
11422 s.select(new_selections);
11423 });
11424 });
11425 }
11426
11427 pub fn join_lines_impl(
11428 &mut self,
11429 insert_whitespace: bool,
11430 window: &mut Window,
11431 cx: &mut Context<Self>,
11432 ) {
11433 if self.read_only(cx) {
11434 return;
11435 }
11436 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11437 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11438 let start = MultiBufferRow(selection.start.row);
11439 // Treat single line selections as if they include the next line. Otherwise this action
11440 // would do nothing for single line selections individual cursors.
11441 let end = if selection.start.row == selection.end.row {
11442 MultiBufferRow(selection.start.row + 1)
11443 } else if selection.end.column == 0 {
11444 // If the selection ends at the start of a line, it's logically at the end of the
11445 // previous line (plus its newline).
11446 // Don't include the end line unless there's only one line selected.
11447 if selection.start.row + 1 == selection.end.row {
11448 MultiBufferRow(selection.end.row)
11449 } else {
11450 MultiBufferRow(selection.end.row - 1)
11451 }
11452 } else {
11453 MultiBufferRow(selection.end.row)
11454 };
11455
11456 if let Some(last_row_range) = row_ranges.last_mut()
11457 && start <= last_row_range.end
11458 {
11459 last_row_range.end = end;
11460 continue;
11461 }
11462 row_ranges.push(start..end);
11463 }
11464
11465 let snapshot = self.buffer.read(cx).snapshot(cx);
11466 let mut cursor_positions = Vec::new();
11467 for row_range in &row_ranges {
11468 let anchor = snapshot.anchor_before(Point::new(
11469 row_range.end.previous_row().0,
11470 snapshot.line_len(row_range.end.previous_row()),
11471 ));
11472 cursor_positions.push(anchor..anchor);
11473 }
11474
11475 self.transact(window, cx, |this, window, cx| {
11476 for row_range in row_ranges.into_iter().rev() {
11477 for row in row_range.iter_rows().rev() {
11478 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11479 let next_line_row = row.next_row();
11480 let indent = snapshot.indent_size_for_line(next_line_row);
11481 let mut join_start_column = indent.len;
11482
11483 if let Some(language_scope) =
11484 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11485 {
11486 let line_end =
11487 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11488 let line_text_after_indent = snapshot
11489 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11490 .collect::<String>();
11491
11492 if !line_text_after_indent.is_empty() {
11493 let block_prefix = language_scope
11494 .block_comment()
11495 .map(|c| c.prefix.as_ref())
11496 .filter(|p| !p.is_empty());
11497 let doc_prefix = language_scope
11498 .documentation_comment()
11499 .map(|c| c.prefix.as_ref())
11500 .filter(|p| !p.is_empty());
11501 let all_prefixes = language_scope
11502 .line_comment_prefixes()
11503 .iter()
11504 .map(|p| p.as_ref())
11505 .chain(block_prefix)
11506 .chain(doc_prefix)
11507 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11508
11509 let mut longest_prefix_len = None;
11510 for prefix in all_prefixes {
11511 let trimmed = prefix.trim_end();
11512 if line_text_after_indent.starts_with(trimmed) {
11513 let candidate_len =
11514 if line_text_after_indent.starts_with(prefix) {
11515 prefix.len()
11516 } else {
11517 trimmed.len()
11518 };
11519 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11520 longest_prefix_len = Some(candidate_len);
11521 }
11522 }
11523 }
11524
11525 if let Some(prefix_len) = longest_prefix_len {
11526 join_start_column =
11527 join_start_column.saturating_add(prefix_len as u32);
11528 }
11529 }
11530 }
11531
11532 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11533
11534 let replace = if snapshot.line_len(next_line_row) > join_start_column
11535 && insert_whitespace
11536 {
11537 " "
11538 } else {
11539 ""
11540 };
11541
11542 this.buffer.update(cx, |buffer, cx| {
11543 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11544 });
11545 }
11546 }
11547
11548 this.change_selections(Default::default(), window, cx, |s| {
11549 s.select_anchor_ranges(cursor_positions)
11550 });
11551 });
11552 }
11553
11554 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11556 self.join_lines_impl(true, window, cx);
11557 }
11558
11559 pub fn sort_lines_case_sensitive(
11560 &mut self,
11561 _: &SortLinesCaseSensitive,
11562 window: &mut Window,
11563 cx: &mut Context<Self>,
11564 ) {
11565 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11566 }
11567
11568 pub fn sort_lines_by_length(
11569 &mut self,
11570 _: &SortLinesByLength,
11571 window: &mut Window,
11572 cx: &mut Context<Self>,
11573 ) {
11574 self.manipulate_immutable_lines(window, cx, |lines| {
11575 lines.sort_by_key(|&line| line.chars().count())
11576 })
11577 }
11578
11579 pub fn sort_lines_case_insensitive(
11580 &mut self,
11581 _: &SortLinesCaseInsensitive,
11582 window: &mut Window,
11583 cx: &mut Context<Self>,
11584 ) {
11585 self.manipulate_immutable_lines(window, cx, |lines| {
11586 lines.sort_by_key(|line| line.to_lowercase())
11587 })
11588 }
11589
11590 pub fn unique_lines_case_insensitive(
11591 &mut self,
11592 _: &UniqueLinesCaseInsensitive,
11593 window: &mut Window,
11594 cx: &mut Context<Self>,
11595 ) {
11596 self.manipulate_immutable_lines(window, cx, |lines| {
11597 let mut seen = HashSet::default();
11598 lines.retain(|line| seen.insert(line.to_lowercase()));
11599 })
11600 }
11601
11602 pub fn unique_lines_case_sensitive(
11603 &mut self,
11604 _: &UniqueLinesCaseSensitive,
11605 window: &mut Window,
11606 cx: &mut Context<Self>,
11607 ) {
11608 self.manipulate_immutable_lines(window, cx, |lines| {
11609 let mut seen = HashSet::default();
11610 lines.retain(|line| seen.insert(*line));
11611 })
11612 }
11613
11614 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11615 let snapshot = self.buffer.read(cx).snapshot(cx);
11616 for selection in self.selections.disjoint_anchors_arc().iter() {
11617 if snapshot
11618 .language_at(selection.start)
11619 .and_then(|lang| lang.config().wrap_characters.as_ref())
11620 .is_some()
11621 {
11622 return true;
11623 }
11624 }
11625 false
11626 }
11627
11628 fn wrap_selections_in_tag(
11629 &mut self,
11630 _: &WrapSelectionsInTag,
11631 window: &mut Window,
11632 cx: &mut Context<Self>,
11633 ) {
11634 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11635
11636 let snapshot = self.buffer.read(cx).snapshot(cx);
11637
11638 let mut edits = Vec::new();
11639 let mut boundaries = Vec::new();
11640
11641 for selection in self
11642 .selections
11643 .all_adjusted(&self.display_snapshot(cx))
11644 .iter()
11645 {
11646 let Some(wrap_config) = snapshot
11647 .language_at(selection.start)
11648 .and_then(|lang| lang.config().wrap_characters.clone())
11649 else {
11650 continue;
11651 };
11652
11653 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11654 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11655
11656 let start_before = snapshot.anchor_before(selection.start);
11657 let end_after = snapshot.anchor_after(selection.end);
11658
11659 edits.push((start_before..start_before, open_tag));
11660 edits.push((end_after..end_after, close_tag));
11661
11662 boundaries.push((
11663 start_before,
11664 end_after,
11665 wrap_config.start_prefix.len(),
11666 wrap_config.end_suffix.len(),
11667 ));
11668 }
11669
11670 if edits.is_empty() {
11671 return;
11672 }
11673
11674 self.transact(window, cx, |this, window, cx| {
11675 let buffer = this.buffer.update(cx, |buffer, cx| {
11676 buffer.edit(edits, None, cx);
11677 buffer.snapshot(cx)
11678 });
11679
11680 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11681 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11682 boundaries.into_iter()
11683 {
11684 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11685 let close_offset = end_after
11686 .to_offset(&buffer)
11687 .saturating_sub_usize(end_suffix_len);
11688 new_selections.push(open_offset..open_offset);
11689 new_selections.push(close_offset..close_offset);
11690 }
11691
11692 this.change_selections(Default::default(), window, cx, |s| {
11693 s.select_ranges(new_selections);
11694 });
11695
11696 this.request_autoscroll(Autoscroll::fit(), cx);
11697 });
11698 }
11699
11700 pub fn toggle_read_only(
11701 &mut self,
11702 _: &workspace::ToggleReadOnlyFile,
11703 _: &mut Window,
11704 cx: &mut Context<Self>,
11705 ) {
11706 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11707 buffer.update(cx, |buffer, cx| {
11708 buffer.set_capability(
11709 match buffer.capability() {
11710 Capability::ReadWrite => Capability::Read,
11711 Capability::Read => Capability::ReadWrite,
11712 Capability::ReadOnly => Capability::ReadOnly,
11713 },
11714 cx,
11715 );
11716 })
11717 }
11718 }
11719
11720 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11721 let Some(project) = self.project.clone() else {
11722 return;
11723 };
11724 let task = self.reload(project, window, cx);
11725 self.detach_and_notify_err(task, window, cx);
11726 }
11727
11728 pub fn restore_file(
11729 &mut self,
11730 _: &::git::RestoreFile,
11731 window: &mut Window,
11732 cx: &mut Context<Self>,
11733 ) {
11734 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11735 let mut buffer_ids = HashSet::default();
11736 let snapshot = self.buffer().read(cx).snapshot(cx);
11737 for selection in self
11738 .selections
11739 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11740 {
11741 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11742 }
11743
11744 let buffer = self.buffer().read(cx);
11745 let ranges = buffer_ids
11746 .into_iter()
11747 .flat_map(|buffer_id| buffer.range_for_buffer(buffer_id, cx))
11748 .collect::<Vec<_>>();
11749
11750 self.restore_hunks_in_ranges(ranges, window, cx);
11751 }
11752
11753 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11755 let selections = self
11756 .selections
11757 .all(&self.display_snapshot(cx))
11758 .into_iter()
11759 .map(|s| s.range())
11760 .collect();
11761 self.restore_hunks_in_ranges(selections, window, cx);
11762 }
11763
11764 /// Restores the diff hunks in the editor's selections and moves the cursor
11765 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11766 /// not all diff hunks are expanded.
11767 pub fn restore_and_next(
11768 &mut self,
11769 _: &::git::RestoreAndNext,
11770 window: &mut Window,
11771 cx: &mut Context<Self>,
11772 ) {
11773 let selections = self
11774 .selections
11775 .all(&self.display_snapshot(cx))
11776 .into_iter()
11777 .map(|selection| selection.range())
11778 .collect();
11779
11780 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11781 self.restore_hunks_in_ranges(selections, window, cx);
11782
11783 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11784 let wrap_around = !all_diff_hunks_expanded;
11785 let snapshot = self.snapshot(window, cx);
11786 let position = self
11787 .selections
11788 .newest::<Point>(&snapshot.display_snapshot)
11789 .head();
11790
11791 self.go_to_hunk_before_or_after_position(
11792 &snapshot,
11793 position,
11794 Direction::Next,
11795 wrap_around,
11796 window,
11797 cx,
11798 );
11799 }
11800
11801 pub fn restore_hunks_in_ranges(
11802 &mut self,
11803 ranges: Vec<Range<Point>>,
11804 window: &mut Window,
11805 cx: &mut Context<Editor>,
11806 ) {
11807 if self.delegate_stage_and_restore {
11808 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11809 if !hunks.is_empty() {
11810 cx.emit(EditorEvent::RestoreRequested { hunks });
11811 }
11812 return;
11813 }
11814 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11815 self.transact(window, cx, |editor, window, cx| {
11816 editor.restore_diff_hunks(hunks, cx);
11817 let selections = editor
11818 .selections
11819 .all::<MultiBufferOffset>(&editor.display_snapshot(cx));
11820 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11821 s.select(selections);
11822 });
11823 });
11824 }
11825
11826 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11827 let mut revert_changes = HashMap::default();
11828 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11829 for (buffer_id, hunks) in &chunk_by {
11830 let hunks = hunks.collect::<Vec<_>>();
11831 for hunk in &hunks {
11832 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11833 }
11834 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11835 }
11836 if !revert_changes.is_empty() {
11837 self.buffer().update(cx, |multi_buffer, cx| {
11838 for (buffer_id, changes) in revert_changes {
11839 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11840 buffer.update(cx, |buffer, cx| {
11841 buffer.edit(
11842 changes
11843 .into_iter()
11844 .map(|(range, text)| (range, text.to_string())),
11845 None,
11846 cx,
11847 );
11848 });
11849 }
11850 }
11851 });
11852 }
11853 }
11854
11855 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11856 if let Some(status) = self
11857 .addons
11858 .iter()
11859 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11860 {
11861 return Some(status);
11862 }
11863 self.project
11864 .as_ref()?
11865 .read(cx)
11866 .status_for_buffer_id(buffer_id, cx)
11867 }
11868
11869 pub fn open_active_item_in_terminal(
11870 &mut self,
11871 _: &OpenInTerminal,
11872 window: &mut Window,
11873 cx: &mut Context<Self>,
11874 ) {
11875 if let Some(working_directory) = self.active_buffer(cx).and_then(|buffer| {
11876 let project_path = buffer.read(cx).project_path(cx)?;
11877 let project = self.project()?.read(cx);
11878 let entry = project.entry_for_path(&project_path, cx)?;
11879 let parent = match &entry.canonical_path {
11880 Some(canonical_path) => canonical_path.to_path_buf(),
11881 None => project.absolute_path(&project_path, cx)?,
11882 }
11883 .parent()?
11884 .to_path_buf();
11885 Some(parent)
11886 }) {
11887 window.dispatch_action(
11888 OpenTerminal {
11889 working_directory,
11890 local: false,
11891 }
11892 .boxed_clone(),
11893 cx,
11894 );
11895 }
11896 }
11897
11898 fn set_breakpoint_context_menu(
11899 &mut self,
11900 display_row: DisplayRow,
11901 position: Option<Anchor>,
11902 clicked_point: gpui::Point<Pixels>,
11903 window: &mut Window,
11904 cx: &mut Context<Self>,
11905 ) {
11906 let source = self
11907 .buffer
11908 .read(cx)
11909 .snapshot(cx)
11910 .anchor_before(Point::new(display_row.0, 0u32));
11911
11912 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11913
11914 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11915 self,
11916 source,
11917 clicked_point,
11918 context_menu,
11919 window,
11920 cx,
11921 );
11922 }
11923
11924 fn add_edit_breakpoint_block(
11925 &mut self,
11926 anchor: Anchor,
11927 breakpoint: &Breakpoint,
11928 edit_action: BreakpointPromptEditAction,
11929 window: &mut Window,
11930 cx: &mut Context<Self>,
11931 ) {
11932 let weak_editor = cx.weak_entity();
11933 let bp_prompt = cx.new(|cx| {
11934 BreakpointPromptEditor::new(
11935 weak_editor,
11936 anchor,
11937 breakpoint.clone(),
11938 edit_action,
11939 window,
11940 cx,
11941 )
11942 });
11943
11944 let height = bp_prompt.update(cx, |this, cx| {
11945 this.prompt
11946 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11947 });
11948 let cloned_prompt = bp_prompt.clone();
11949 let blocks = vec![BlockProperties {
11950 style: BlockStyle::Sticky,
11951 placement: BlockPlacement::Above(anchor),
11952 height: Some(height),
11953 render: Arc::new(move |cx| {
11954 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11955 cloned_prompt.clone().into_any_element()
11956 }),
11957 priority: 0,
11958 }];
11959
11960 let focus_handle = bp_prompt.focus_handle(cx);
11961 window.focus(&focus_handle, cx);
11962
11963 let block_ids = self.insert_blocks(blocks, None, cx);
11964 bp_prompt.update(cx, |prompt, _| {
11965 prompt.add_block_ids(block_ids);
11966 });
11967 }
11968
11969 pub(crate) fn breakpoint_at_row(
11970 &self,
11971 row: u32,
11972 window: &mut Window,
11973 cx: &mut Context<Self>,
11974 ) -> Option<(Anchor, Breakpoint)> {
11975 let snapshot = self.snapshot(window, cx);
11976 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11977
11978 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11979 }
11980
11981 pub(crate) fn breakpoint_at_anchor(
11982 &self,
11983 breakpoint_position: Anchor,
11984 snapshot: &EditorSnapshot,
11985 cx: &mut Context<Self>,
11986 ) -> Option<(Anchor, Breakpoint)> {
11987 let (breakpoint_position, _) = snapshot
11988 .buffer_snapshot()
11989 .anchor_to_buffer_anchor(breakpoint_position)?;
11990 let buffer = self.buffer.read(cx).buffer(breakpoint_position.buffer_id)?;
11991
11992 let buffer_snapshot = buffer.read(cx).snapshot();
11993
11994 let row = buffer_snapshot
11995 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position)
11996 .row;
11997
11998 let line_len = buffer_snapshot.line_len(row);
11999 let anchor_end = buffer_snapshot.anchor_after(Point::new(row, line_len));
12000
12001 self.breakpoint_store
12002 .as_ref()?
12003 .read_with(cx, |breakpoint_store, cx| {
12004 breakpoint_store
12005 .breakpoints(
12006 &buffer,
12007 Some(breakpoint_position..anchor_end),
12008 &buffer_snapshot,
12009 cx,
12010 )
12011 .next()
12012 .and_then(|(bp, _)| {
12013 let breakpoint_row = buffer_snapshot
12014 .summary_for_anchor::<text::PointUtf16>(&bp.position)
12015 .row;
12016
12017 if breakpoint_row == row {
12018 snapshot
12019 .buffer_snapshot()
12020 .anchor_in_excerpt(bp.position)
12021 .map(|position| (position, bp.bp.clone()))
12022 } else {
12023 None
12024 }
12025 })
12026 })
12027 }
12028
12029 pub fn edit_log_breakpoint(
12030 &mut self,
12031 _: &EditLogBreakpoint,
12032 window: &mut Window,
12033 cx: &mut Context<Self>,
12034 ) {
12035 if self.breakpoint_store.is_none() {
12036 return;
12037 }
12038
12039 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12040 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
12041 message: None,
12042 state: BreakpointState::Enabled,
12043 condition: None,
12044 hit_condition: None,
12045 });
12046
12047 self.add_edit_breakpoint_block(
12048 anchor,
12049 &breakpoint,
12050 BreakpointPromptEditAction::Log,
12051 window,
12052 cx,
12053 );
12054 }
12055 }
12056
12057 fn breakpoints_at_cursors(
12058 &self,
12059 window: &mut Window,
12060 cx: &mut Context<Self>,
12061 ) -> Vec<(Anchor, Option<Breakpoint>)> {
12062 let snapshot = self.snapshot(window, cx);
12063 let cursors = self
12064 .selections
12065 .disjoint_anchors_arc()
12066 .iter()
12067 .map(|selection| {
12068 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
12069
12070 let breakpoint_position = self
12071 .breakpoint_at_row(cursor_position.row, window, cx)
12072 .map(|bp| bp.0)
12073 .unwrap_or_else(|| {
12074 snapshot
12075 .display_snapshot
12076 .buffer_snapshot()
12077 .anchor_after(Point::new(cursor_position.row, 0))
12078 });
12079
12080 let breakpoint = self
12081 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
12082 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
12083
12084 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
12085 })
12086 // 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.
12087 .collect::<HashMap<Anchor, _>>();
12088
12089 cursors.into_iter().collect()
12090 }
12091
12092 pub fn enable_breakpoint(
12093 &mut self,
12094 _: &crate::actions::EnableBreakpoint,
12095 window: &mut Window,
12096 cx: &mut Context<Self>,
12097 ) {
12098 if self.breakpoint_store.is_none() {
12099 return;
12100 }
12101
12102 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12103 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
12104 continue;
12105 };
12106 self.edit_breakpoint_at_anchor(
12107 anchor,
12108 breakpoint,
12109 BreakpointEditAction::InvertState,
12110 cx,
12111 );
12112 }
12113 }
12114
12115 pub fn align_selections(
12116 &mut self,
12117 _: &crate::actions::AlignSelections,
12118 window: &mut Window,
12119 cx: &mut Context<Self>,
12120 ) {
12121 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12122
12123 let display_snapshot = self.display_snapshot(cx);
12124
12125 struct CursorData {
12126 anchor: Anchor,
12127 point: Point,
12128 }
12129 let cursor_data: Vec<CursorData> = self
12130 .selections
12131 .disjoint_anchors()
12132 .iter()
12133 .map(|selection| {
12134 let anchor = if selection.reversed {
12135 selection.head()
12136 } else {
12137 selection.tail()
12138 };
12139 CursorData {
12140 anchor: anchor,
12141 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
12142 }
12143 })
12144 .collect();
12145
12146 let rows_anchors_count: Vec<usize> = cursor_data
12147 .iter()
12148 .map(|cursor| cursor.point.row)
12149 .chunk_by(|&row| row)
12150 .into_iter()
12151 .map(|(_, group)| group.count())
12152 .collect();
12153 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
12154 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
12155 let mut edits = Vec::new();
12156
12157 for column_idx in 0..max_columns {
12158 let mut cursor_index = 0;
12159
12160 // Calculate target_column => position that the selections will go
12161 let mut target_column = 0;
12162 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12163 // Skip rows that don't have this column
12164 if column_idx >= *cursor_count {
12165 cursor_index += cursor_count;
12166 continue;
12167 }
12168
12169 let point = &cursor_data[cursor_index + column_idx].point;
12170 let adjusted_column = point.column + rows_column_offset[row_idx];
12171 if adjusted_column > target_column {
12172 target_column = adjusted_column;
12173 }
12174 cursor_index += cursor_count;
12175 }
12176
12177 // Collect edits for this column
12178 cursor_index = 0;
12179 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12180 // Skip rows that don't have this column
12181 if column_idx >= *cursor_count {
12182 cursor_index += *cursor_count;
12183 continue;
12184 }
12185
12186 let point = &cursor_data[cursor_index + column_idx].point;
12187 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
12188 if spaces_needed > 0 {
12189 let anchor = cursor_data[cursor_index + column_idx]
12190 .anchor
12191 .bias_left(&display_snapshot);
12192 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
12193 }
12194 rows_column_offset[row_idx] += spaces_needed;
12195
12196 cursor_index += *cursor_count;
12197 }
12198 }
12199
12200 if !edits.is_empty() {
12201 self.transact(window, cx, |editor, _window, cx| {
12202 editor.edit(edits, cx);
12203 });
12204 }
12205 }
12206
12207 pub fn disable_breakpoint(
12208 &mut self,
12209 _: &crate::actions::DisableBreakpoint,
12210 window: &mut Window,
12211 cx: &mut Context<Self>,
12212 ) {
12213 if self.breakpoint_store.is_none() {
12214 return;
12215 }
12216
12217 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12218 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12219 continue;
12220 };
12221 self.edit_breakpoint_at_anchor(
12222 anchor,
12223 breakpoint,
12224 BreakpointEditAction::InvertState,
12225 cx,
12226 );
12227 }
12228 }
12229
12230 pub fn toggle_breakpoint(
12231 &mut self,
12232 _: &crate::actions::ToggleBreakpoint,
12233 window: &mut Window,
12234 cx: &mut Context<Self>,
12235 ) {
12236 if self.breakpoint_store.is_none() {
12237 return;
12238 }
12239
12240 let snapshot = self.snapshot(window, cx);
12241 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12242 if self.gutter_breakpoint_indicator.0.is_some() {
12243 let display_row = anchor
12244 .to_point(snapshot.buffer_snapshot())
12245 .to_display_point(&snapshot.display_snapshot)
12246 .row();
12247 self.update_breakpoint_collision_on_toggle(
12248 display_row,
12249 &BreakpointEditAction::Toggle,
12250 );
12251 }
12252
12253 if let Some(breakpoint) = breakpoint {
12254 self.edit_breakpoint_at_anchor(
12255 anchor,
12256 breakpoint,
12257 BreakpointEditAction::Toggle,
12258 cx,
12259 );
12260 } else {
12261 self.edit_breakpoint_at_anchor(
12262 anchor,
12263 Breakpoint::new_standard(),
12264 BreakpointEditAction::Toggle,
12265 cx,
12266 );
12267 }
12268 }
12269 }
12270
12271 fn update_breakpoint_collision_on_toggle(
12272 &mut self,
12273 display_row: DisplayRow,
12274 edit_action: &BreakpointEditAction,
12275 ) {
12276 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12277 if breakpoint_indicator.display_row == display_row
12278 && matches!(edit_action, BreakpointEditAction::Toggle)
12279 {
12280 breakpoint_indicator.collides_with_existing_breakpoint =
12281 !breakpoint_indicator.collides_with_existing_breakpoint;
12282 }
12283 }
12284 }
12285
12286 pub fn edit_breakpoint_at_anchor(
12287 &mut self,
12288 breakpoint_position: Anchor,
12289 breakpoint: Breakpoint,
12290 edit_action: BreakpointEditAction,
12291 cx: &mut Context<Self>,
12292 ) {
12293 let Some(breakpoint_store) = &self.breakpoint_store else {
12294 return;
12295 };
12296 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
12297 let Some((position, _)) = buffer_snapshot.anchor_to_buffer_anchor(breakpoint_position)
12298 else {
12299 return;
12300 };
12301 let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
12302 return;
12303 };
12304
12305 breakpoint_store.update(cx, |breakpoint_store, cx| {
12306 breakpoint_store.toggle_breakpoint(
12307 buffer,
12308 BreakpointWithPosition {
12309 position,
12310 bp: breakpoint,
12311 },
12312 edit_action,
12313 cx,
12314 );
12315 });
12316
12317 cx.notify();
12318 }
12319
12320 #[cfg(any(test, feature = "test-support"))]
12321 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12322 self.breakpoint_store.clone()
12323 }
12324
12325 pub fn prepare_restore_change(
12326 &self,
12327 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12328 hunk: &MultiBufferDiffHunk,
12329 cx: &mut App,
12330 ) -> Option<()> {
12331 if hunk.is_created_file() {
12332 return None;
12333 }
12334 let multi_buffer = self.buffer.read(cx);
12335 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
12336 let diff_snapshot = multi_buffer_snapshot.diff_for_buffer_id(hunk.buffer_id)?;
12337 let original_text = diff_snapshot
12338 .base_text()
12339 .as_rope()
12340 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12341 let buffer = multi_buffer.buffer(hunk.buffer_id)?;
12342 let buffer = buffer.read(cx);
12343 let buffer_snapshot = buffer.snapshot();
12344 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12345 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12346 probe
12347 .0
12348 .start
12349 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12350 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12351 }) {
12352 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12353 Some(())
12354 } else {
12355 None
12356 }
12357 }
12358
12359 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12360 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12361 }
12362
12363 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12364 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12365 }
12366
12367 pub fn rotate_selections_forward(
12368 &mut self,
12369 _: &RotateSelectionsForward,
12370 window: &mut Window,
12371 cx: &mut Context<Self>,
12372 ) {
12373 self.rotate_selections(window, cx, false)
12374 }
12375
12376 pub fn rotate_selections_backward(
12377 &mut self,
12378 _: &RotateSelectionsBackward,
12379 window: &mut Window,
12380 cx: &mut Context<Self>,
12381 ) {
12382 self.rotate_selections(window, cx, true)
12383 }
12384
12385 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12386 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12387 let display_snapshot = self.display_snapshot(cx);
12388 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12389
12390 if selections.len() < 2 {
12391 return;
12392 }
12393
12394 let (edits, new_selections) = {
12395 let buffer = self.buffer.read(cx).read(cx);
12396 let has_selections = selections.iter().any(|s| !s.is_empty());
12397 if has_selections {
12398 let mut selected_texts: Vec<String> = selections
12399 .iter()
12400 .map(|selection| {
12401 buffer
12402 .text_for_range(selection.start..selection.end)
12403 .collect()
12404 })
12405 .collect();
12406
12407 if reverse {
12408 selected_texts.rotate_left(1);
12409 } else {
12410 selected_texts.rotate_right(1);
12411 }
12412
12413 let mut offset_delta: i64 = 0;
12414 let mut new_selections = Vec::new();
12415 let edits: Vec<_> = selections
12416 .iter()
12417 .zip(selected_texts.iter())
12418 .map(|(selection, new_text)| {
12419 let old_len = (selection.end.0 - selection.start.0) as i64;
12420 let new_len = new_text.len() as i64;
12421 let adjusted_start =
12422 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12423 let adjusted_end =
12424 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12425
12426 new_selections.push(Selection {
12427 id: selection.id,
12428 start: adjusted_start,
12429 end: adjusted_end,
12430 reversed: selection.reversed,
12431 goal: selection.goal,
12432 });
12433
12434 offset_delta += new_len - old_len;
12435 (selection.start..selection.end, new_text.clone())
12436 })
12437 .collect();
12438 (edits, new_selections)
12439 } else {
12440 let mut all_rows: Vec<u32> = selections
12441 .iter()
12442 .map(|selection| buffer.offset_to_point(selection.start).row)
12443 .collect();
12444 all_rows.sort_unstable();
12445 all_rows.dedup();
12446
12447 if all_rows.len() < 2 {
12448 return;
12449 }
12450
12451 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12452 .iter()
12453 .map(|&row| {
12454 let start = Point::new(row, 0);
12455 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12456 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12457 })
12458 .collect();
12459
12460 let mut line_texts: Vec<String> = line_ranges
12461 .iter()
12462 .map(|range| buffer.text_for_range(range.clone()).collect())
12463 .collect();
12464
12465 if reverse {
12466 line_texts.rotate_left(1);
12467 } else {
12468 line_texts.rotate_right(1);
12469 }
12470
12471 let edits = line_ranges
12472 .iter()
12473 .zip(line_texts.iter())
12474 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12475 .collect();
12476
12477 let num_rows = all_rows.len();
12478 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12479 .iter()
12480 .enumerate()
12481 .map(|(i, &row)| (row, i))
12482 .collect();
12483
12484 // Compute new line start offsets after rotation (handles CRLF)
12485 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12486 let first_line_start = line_ranges[0].start.0;
12487 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12488 for text in line_texts.iter().take(num_rows - 1) {
12489 let prev_start = *new_line_starts.last().unwrap();
12490 new_line_starts.push(prev_start + text.len() + newline_len);
12491 }
12492
12493 let new_selections = selections
12494 .iter()
12495 .map(|selection| {
12496 let point = buffer.offset_to_point(selection.start);
12497 let old_index = row_to_index[&point.row];
12498 let new_index = if reverse {
12499 (old_index + num_rows - 1) % num_rows
12500 } else {
12501 (old_index + 1) % num_rows
12502 };
12503 let new_offset =
12504 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12505 Selection {
12506 id: selection.id,
12507 start: new_offset,
12508 end: new_offset,
12509 reversed: selection.reversed,
12510 goal: selection.goal,
12511 }
12512 })
12513 .collect();
12514
12515 (edits, new_selections)
12516 }
12517 };
12518
12519 self.transact(window, cx, |this, window, cx| {
12520 this.buffer.update(cx, |buffer, cx| {
12521 buffer.edit(edits, None, cx);
12522 });
12523 this.change_selections(Default::default(), window, cx, |s| {
12524 s.select(new_selections);
12525 });
12526 });
12527 }
12528
12529 fn manipulate_lines<M>(
12530 &mut self,
12531 window: &mut Window,
12532 cx: &mut Context<Self>,
12533 mut manipulate: M,
12534 ) where
12535 M: FnMut(&str) -> LineManipulationResult,
12536 {
12537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12538
12539 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12540 let buffer = self.buffer.read(cx).snapshot(cx);
12541
12542 let mut edits = Vec::new();
12543
12544 let selections = self.selections.all::<Point>(&display_map);
12545 let mut selections = selections.iter().peekable();
12546 let mut contiguous_row_selections = Vec::new();
12547 let mut new_selections = Vec::new();
12548 let mut added_lines = 0;
12549 let mut removed_lines = 0;
12550
12551 while let Some(selection) = selections.next() {
12552 let (start_row, end_row) = consume_contiguous_rows(
12553 &mut contiguous_row_selections,
12554 selection,
12555 &display_map,
12556 &mut selections,
12557 );
12558
12559 let start_point = Point::new(start_row.0, 0);
12560 let end_point = Point::new(
12561 end_row.previous_row().0,
12562 buffer.line_len(end_row.previous_row()),
12563 );
12564 let text = buffer
12565 .text_for_range(start_point..end_point)
12566 .collect::<String>();
12567
12568 let LineManipulationResult {
12569 new_text,
12570 line_count_before,
12571 line_count_after,
12572 } = manipulate(&text);
12573
12574 edits.push((start_point..end_point, new_text));
12575
12576 // Selections must change based on added and removed line count
12577 let start_row =
12578 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12579 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12580 new_selections.push(Selection {
12581 id: selection.id,
12582 start: start_row,
12583 end: end_row,
12584 goal: SelectionGoal::None,
12585 reversed: selection.reversed,
12586 });
12587
12588 if line_count_after > line_count_before {
12589 added_lines += line_count_after - line_count_before;
12590 } else if line_count_before > line_count_after {
12591 removed_lines += line_count_before - line_count_after;
12592 }
12593 }
12594
12595 self.transact(window, cx, |this, window, cx| {
12596 let buffer = this.buffer.update(cx, |buffer, cx| {
12597 buffer.edit(edits, None, cx);
12598 buffer.snapshot(cx)
12599 });
12600
12601 // Recalculate offsets on newly edited buffer
12602 let new_selections = new_selections
12603 .iter()
12604 .map(|s| {
12605 let start_point = Point::new(s.start.0, 0);
12606 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12607 Selection {
12608 id: s.id,
12609 start: buffer.point_to_offset(start_point),
12610 end: buffer.point_to_offset(end_point),
12611 goal: s.goal,
12612 reversed: s.reversed,
12613 }
12614 })
12615 .collect();
12616
12617 this.change_selections(Default::default(), window, cx, |s| {
12618 s.select(new_selections);
12619 });
12620
12621 this.request_autoscroll(Autoscroll::fit(), cx);
12622 });
12623 }
12624
12625 fn manipulate_immutable_lines<Fn>(
12626 &mut self,
12627 window: &mut Window,
12628 cx: &mut Context<Self>,
12629 mut callback: Fn,
12630 ) where
12631 Fn: FnMut(&mut Vec<&str>),
12632 {
12633 self.manipulate_lines(window, cx, |text| {
12634 let mut lines: Vec<&str> = text.split('\n').collect();
12635 let line_count_before = lines.len();
12636
12637 callback(&mut lines);
12638
12639 LineManipulationResult {
12640 new_text: lines.join("\n"),
12641 line_count_before,
12642 line_count_after: lines.len(),
12643 }
12644 });
12645 }
12646
12647 fn manipulate_mutable_lines<Fn>(
12648 &mut self,
12649 window: &mut Window,
12650 cx: &mut Context<Self>,
12651 mut callback: Fn,
12652 ) where
12653 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12654 {
12655 self.manipulate_lines(window, cx, |text| {
12656 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12657 let line_count_before = lines.len();
12658
12659 callback(&mut lines);
12660
12661 LineManipulationResult {
12662 new_text: lines.join("\n"),
12663 line_count_before,
12664 line_count_after: lines.len(),
12665 }
12666 });
12667 }
12668
12669 pub fn convert_indentation_to_spaces(
12670 &mut self,
12671 _: &ConvertIndentationToSpaces,
12672 window: &mut Window,
12673 cx: &mut Context<Self>,
12674 ) {
12675 let settings = self.buffer.read(cx).language_settings(cx);
12676 let tab_size = settings.tab_size.get() as usize;
12677
12678 self.manipulate_mutable_lines(window, cx, |lines| {
12679 // Allocates a reasonably sized scratch buffer once for the whole loop
12680 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12681 // Avoids recomputing spaces that could be inserted many times
12682 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12683 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12684 .collect();
12685
12686 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12687 let mut chars = line.as_ref().chars();
12688 let mut col = 0;
12689 let mut changed = false;
12690
12691 for ch in chars.by_ref() {
12692 match ch {
12693 ' ' => {
12694 reindented_line.push(' ');
12695 col += 1;
12696 }
12697 '\t' => {
12698 // \t are converted to spaces depending on the current column
12699 let spaces_len = tab_size - (col % tab_size);
12700 reindented_line.extend(&space_cache[spaces_len - 1]);
12701 col += spaces_len;
12702 changed = true;
12703 }
12704 _ => {
12705 // If we dont append before break, the character is consumed
12706 reindented_line.push(ch);
12707 break;
12708 }
12709 }
12710 }
12711
12712 if !changed {
12713 reindented_line.clear();
12714 continue;
12715 }
12716 // Append the rest of the line and replace old reference with new one
12717 reindented_line.extend(chars);
12718 *line = Cow::Owned(reindented_line.clone());
12719 reindented_line.clear();
12720 }
12721 });
12722 }
12723
12724 pub fn convert_indentation_to_tabs(
12725 &mut self,
12726 _: &ConvertIndentationToTabs,
12727 window: &mut Window,
12728 cx: &mut Context<Self>,
12729 ) {
12730 let settings = self.buffer.read(cx).language_settings(cx);
12731 let tab_size = settings.tab_size.get() as usize;
12732
12733 self.manipulate_mutable_lines(window, cx, |lines| {
12734 // Allocates a reasonably sized buffer once for the whole loop
12735 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12736 // Avoids recomputing spaces that could be inserted many times
12737 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12738 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12739 .collect();
12740
12741 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12742 let mut chars = line.chars();
12743 let mut spaces_count = 0;
12744 let mut first_non_indent_char = None;
12745 let mut changed = false;
12746
12747 for ch in chars.by_ref() {
12748 match ch {
12749 ' ' => {
12750 // Keep track of spaces. Append \t when we reach tab_size
12751 spaces_count += 1;
12752 changed = true;
12753 if spaces_count == tab_size {
12754 reindented_line.push('\t');
12755 spaces_count = 0;
12756 }
12757 }
12758 '\t' => {
12759 reindented_line.push('\t');
12760 spaces_count = 0;
12761 }
12762 _ => {
12763 // Dont append it yet, we might have remaining spaces
12764 first_non_indent_char = Some(ch);
12765 break;
12766 }
12767 }
12768 }
12769
12770 if !changed {
12771 reindented_line.clear();
12772 continue;
12773 }
12774 // Remaining spaces that didn't make a full tab stop
12775 if spaces_count > 0 {
12776 reindented_line.extend(&space_cache[spaces_count - 1]);
12777 }
12778 // If we consume an extra character that was not indentation, add it back
12779 if let Some(extra_char) = first_non_indent_char {
12780 reindented_line.push(extra_char);
12781 }
12782 // Append the rest of the line and replace old reference with new one
12783 reindented_line.extend(chars);
12784 *line = Cow::Owned(reindented_line.clone());
12785 reindented_line.clear();
12786 }
12787 });
12788 }
12789
12790 pub fn convert_to_upper_case(
12791 &mut self,
12792 _: &ConvertToUpperCase,
12793 window: &mut Window,
12794 cx: &mut Context<Self>,
12795 ) {
12796 self.manipulate_text(window, cx, |text| text.to_uppercase())
12797 }
12798
12799 pub fn convert_to_lower_case(
12800 &mut self,
12801 _: &ConvertToLowerCase,
12802 window: &mut Window,
12803 cx: &mut Context<Self>,
12804 ) {
12805 self.manipulate_text(window, cx, |text| text.to_lowercase())
12806 }
12807
12808 pub fn convert_to_title_case(
12809 &mut self,
12810 _: &ConvertToTitleCase,
12811 window: &mut Window,
12812 cx: &mut Context<Self>,
12813 ) {
12814 self.manipulate_text(window, cx, |text| {
12815 Self::convert_text_case(text, Case::Title)
12816 })
12817 }
12818
12819 pub fn convert_to_snake_case(
12820 &mut self,
12821 _: &ConvertToSnakeCase,
12822 window: &mut Window,
12823 cx: &mut Context<Self>,
12824 ) {
12825 self.manipulate_text(window, cx, |text| {
12826 Self::convert_text_case(text, Case::Snake)
12827 })
12828 }
12829
12830 pub fn convert_to_kebab_case(
12831 &mut self,
12832 _: &ConvertToKebabCase,
12833 window: &mut Window,
12834 cx: &mut Context<Self>,
12835 ) {
12836 self.manipulate_text(window, cx, |text| {
12837 Self::convert_text_case(text, Case::Kebab)
12838 })
12839 }
12840
12841 pub fn convert_to_upper_camel_case(
12842 &mut self,
12843 _: &ConvertToUpperCamelCase,
12844 window: &mut Window,
12845 cx: &mut Context<Self>,
12846 ) {
12847 self.manipulate_text(window, cx, |text| {
12848 Self::convert_text_case(text, Case::UpperCamel)
12849 })
12850 }
12851
12852 pub fn convert_to_lower_camel_case(
12853 &mut self,
12854 _: &ConvertToLowerCamelCase,
12855 window: &mut Window,
12856 cx: &mut Context<Self>,
12857 ) {
12858 self.manipulate_text(window, cx, |text| {
12859 Self::convert_text_case(text, Case::Camel)
12860 })
12861 }
12862
12863 pub fn convert_to_opposite_case(
12864 &mut self,
12865 _: &ConvertToOppositeCase,
12866 window: &mut Window,
12867 cx: &mut Context<Self>,
12868 ) {
12869 self.manipulate_text(window, cx, |text| {
12870 text.chars()
12871 .fold(String::with_capacity(text.len()), |mut t, c| {
12872 if c.is_uppercase() {
12873 t.extend(c.to_lowercase());
12874 } else {
12875 t.extend(c.to_uppercase());
12876 }
12877 t
12878 })
12879 })
12880 }
12881
12882 pub fn convert_to_sentence_case(
12883 &mut self,
12884 _: &ConvertToSentenceCase,
12885 window: &mut Window,
12886 cx: &mut Context<Self>,
12887 ) {
12888 self.manipulate_text(window, cx, |text| {
12889 Self::convert_text_case(text, Case::Sentence)
12890 })
12891 }
12892
12893 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12894 self.manipulate_text(window, cx, |text| {
12895 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12896 if has_upper_case_characters {
12897 text.to_lowercase()
12898 } else {
12899 text.to_uppercase()
12900 }
12901 })
12902 }
12903
12904 pub fn convert_to_rot13(
12905 &mut self,
12906 _: &ConvertToRot13,
12907 window: &mut Window,
12908 cx: &mut Context<Self>,
12909 ) {
12910 self.manipulate_text(window, cx, |text| {
12911 text.chars()
12912 .map(|c| match c {
12913 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12914 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12915 _ => c,
12916 })
12917 .collect()
12918 })
12919 }
12920
12921 fn convert_text_case(text: &str, case: Case) -> String {
12922 text.lines()
12923 .map(|line| {
12924 let trimmed_start = line.trim_start();
12925 let leading = &line[..line.len() - trimmed_start.len()];
12926 let trimmed = trimmed_start.trim_end();
12927 let trailing = &trimmed_start[trimmed.len()..];
12928 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12929 })
12930 .join("\n")
12931 }
12932
12933 pub fn convert_to_rot47(
12934 &mut self,
12935 _: &ConvertToRot47,
12936 window: &mut Window,
12937 cx: &mut Context<Self>,
12938 ) {
12939 self.manipulate_text(window, cx, |text| {
12940 text.chars()
12941 .map(|c| {
12942 let code_point = c as u32;
12943 if code_point >= 33 && code_point <= 126 {
12944 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12945 }
12946 c
12947 })
12948 .collect()
12949 })
12950 }
12951
12952 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12953 where
12954 Fn: FnMut(&str) -> String,
12955 {
12956 let buffer = self.buffer.read(cx).snapshot(cx);
12957
12958 let mut new_selections = Vec::new();
12959 let mut edits = Vec::new();
12960 let mut selection_adjustment = 0isize;
12961
12962 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12963 let selection_is_empty = selection.is_empty();
12964
12965 let (start, end) = if selection_is_empty {
12966 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12967 (word_range.start, word_range.end)
12968 } else {
12969 (
12970 buffer.point_to_offset(selection.start),
12971 buffer.point_to_offset(selection.end),
12972 )
12973 };
12974
12975 let text = buffer.text_for_range(start..end).collect::<String>();
12976 let old_length = text.len() as isize;
12977 let text = callback(&text);
12978
12979 new_selections.push(Selection {
12980 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12981 end: MultiBufferOffset(
12982 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12983 ),
12984 goal: SelectionGoal::None,
12985 id: selection.id,
12986 reversed: selection.reversed,
12987 });
12988
12989 selection_adjustment += old_length - text.len() as isize;
12990
12991 edits.push((start..end, text));
12992 }
12993
12994 self.transact(window, cx, |this, window, cx| {
12995 this.buffer.update(cx, |buffer, cx| {
12996 buffer.edit(edits, None, cx);
12997 });
12998
12999 this.change_selections(Default::default(), window, cx, |s| {
13000 s.select(new_selections);
13001 });
13002
13003 this.request_autoscroll(Autoscroll::fit(), cx);
13004 });
13005 }
13006
13007 pub fn move_selection_on_drop(
13008 &mut self,
13009 selection: &Selection<Anchor>,
13010 target: DisplayPoint,
13011 is_cut: bool,
13012 window: &mut Window,
13013 cx: &mut Context<Self>,
13014 ) {
13015 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13016 let buffer = display_map.buffer_snapshot();
13017 let mut edits = Vec::new();
13018 let insert_point = display_map
13019 .clip_point(target, Bias::Left)
13020 .to_point(&display_map);
13021 let text = buffer
13022 .text_for_range(selection.start..selection.end)
13023 .collect::<String>();
13024 if is_cut {
13025 edits.push(((selection.start..selection.end), String::new()));
13026 }
13027 let insert_anchor = buffer.anchor_before(insert_point);
13028 edits.push(((insert_anchor..insert_anchor), text));
13029 let last_edit_start = insert_anchor.bias_left(buffer);
13030 let last_edit_end = insert_anchor.bias_right(buffer);
13031 self.transact(window, cx, |this, window, cx| {
13032 this.buffer.update(cx, |buffer, cx| {
13033 buffer.edit(edits, None, cx);
13034 });
13035 this.change_selections(Default::default(), window, cx, |s| {
13036 s.select_anchor_ranges([last_edit_start..last_edit_end]);
13037 });
13038 });
13039 }
13040
13041 pub fn clear_selection_drag_state(&mut self) {
13042 self.selection_drag_state = SelectionDragState::None;
13043 }
13044
13045 pub fn duplicate(
13046 &mut self,
13047 upwards: bool,
13048 whole_lines: bool,
13049 window: &mut Window,
13050 cx: &mut Context<Self>,
13051 ) {
13052 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13053
13054 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13055 let buffer = display_map.buffer_snapshot();
13056 let selections = self.selections.all::<Point>(&display_map);
13057
13058 let mut edits = Vec::new();
13059 let mut selections_iter = selections.iter().peekable();
13060 while let Some(selection) = selections_iter.next() {
13061 let mut rows = selection.spanned_rows(false, &display_map);
13062 // duplicate line-wise
13063 if whole_lines || selection.start == selection.end {
13064 // Avoid duplicating the same lines twice.
13065 while let Some(next_selection) = selections_iter.peek() {
13066 let next_rows = next_selection.spanned_rows(false, &display_map);
13067 if next_rows.start < rows.end {
13068 rows.end = next_rows.end;
13069 selections_iter.next().unwrap();
13070 } else {
13071 break;
13072 }
13073 }
13074
13075 // Copy the text from the selected row region and splice it either at the start
13076 // or end of the region.
13077 let start = Point::new(rows.start.0, 0);
13078 let end = Point::new(
13079 rows.end.previous_row().0,
13080 buffer.line_len(rows.end.previous_row()),
13081 );
13082
13083 let mut text = buffer.text_for_range(start..end).collect::<String>();
13084
13085 let insert_location = if upwards {
13086 // When duplicating upward, we need to insert before the current line.
13087 // If we're on the last line and it doesn't end with a newline,
13088 // we need to add a newline before the duplicated content.
13089 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
13090 && buffer.max_point().column > 0
13091 && !text.ends_with('\n');
13092
13093 if needs_leading_newline {
13094 text.insert(0, '\n');
13095 end
13096 } else {
13097 text.push('\n');
13098 Point::new(rows.start.0, 0)
13099 }
13100 } else {
13101 text.push('\n');
13102 start
13103 };
13104 edits.push((insert_location..insert_location, text));
13105 } else {
13106 // duplicate character-wise
13107 let start = selection.start;
13108 let end = selection.end;
13109 let text = buffer.text_for_range(start..end).collect::<String>();
13110 edits.push((selection.end..selection.end, text));
13111 }
13112 }
13113
13114 self.transact(window, cx, |this, window, cx| {
13115 this.buffer.update(cx, |buffer, cx| {
13116 buffer.edit(edits, None, cx);
13117 });
13118
13119 // When duplicating upward with whole lines, move the cursor to the duplicated line
13120 if upwards && whole_lines {
13121 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
13122
13123 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13124 let mut new_ranges = Vec::new();
13125 let selections = s.all::<Point>(&display_map);
13126 let mut selections_iter = selections.iter().peekable();
13127
13128 while let Some(first_selection) = selections_iter.next() {
13129 // Group contiguous selections together to find the total row span
13130 let mut group_selections = vec![first_selection];
13131 let mut rows = first_selection.spanned_rows(false, &display_map);
13132
13133 while let Some(next_selection) = selections_iter.peek() {
13134 let next_rows = next_selection.spanned_rows(false, &display_map);
13135 if next_rows.start < rows.end {
13136 rows.end = next_rows.end;
13137 group_selections.push(selections_iter.next().unwrap());
13138 } else {
13139 break;
13140 }
13141 }
13142
13143 let row_count = rows.end.0 - rows.start.0;
13144
13145 // Move all selections in this group up by the total number of duplicated rows
13146 for selection in group_selections {
13147 let new_start = Point::new(
13148 selection.start.row.saturating_sub(row_count),
13149 selection.start.column,
13150 );
13151
13152 let new_end = Point::new(
13153 selection.end.row.saturating_sub(row_count),
13154 selection.end.column,
13155 );
13156
13157 new_ranges.push(new_start..new_end);
13158 }
13159 }
13160
13161 s.select_ranges(new_ranges);
13162 });
13163 }
13164
13165 this.request_autoscroll(Autoscroll::fit(), cx);
13166 });
13167 }
13168
13169 pub fn duplicate_line_up(
13170 &mut self,
13171 _: &DuplicateLineUp,
13172 window: &mut Window,
13173 cx: &mut Context<Self>,
13174 ) {
13175 self.duplicate(true, true, window, cx);
13176 }
13177
13178 pub fn duplicate_line_down(
13179 &mut self,
13180 _: &DuplicateLineDown,
13181 window: &mut Window,
13182 cx: &mut Context<Self>,
13183 ) {
13184 self.duplicate(false, true, window, cx);
13185 }
13186
13187 pub fn duplicate_selection(
13188 &mut self,
13189 _: &DuplicateSelection,
13190 window: &mut Window,
13191 cx: &mut Context<Self>,
13192 ) {
13193 self.duplicate(false, false, window, cx);
13194 }
13195
13196 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13197 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13198 if self.mode.is_single_line() {
13199 cx.propagate();
13200 return;
13201 }
13202
13203 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13204 let buffer = self.buffer.read(cx).snapshot(cx);
13205
13206 let mut edits = Vec::new();
13207 let mut unfold_ranges = Vec::new();
13208 let mut refold_creases = Vec::new();
13209
13210 let selections = self.selections.all::<Point>(&display_map);
13211 let mut selections = selections.iter().peekable();
13212 let mut contiguous_row_selections = Vec::new();
13213 let mut new_selections = Vec::new();
13214
13215 while let Some(selection) = selections.next() {
13216 // Find all the selections that span a contiguous row range
13217 let (start_row, end_row) = consume_contiguous_rows(
13218 &mut contiguous_row_selections,
13219 selection,
13220 &display_map,
13221 &mut selections,
13222 );
13223
13224 // Move the text spanned by the row range to be before the line preceding the row range
13225 if start_row.0 > 0 {
13226 let range_to_move = Point::new(
13227 start_row.previous_row().0,
13228 buffer.line_len(start_row.previous_row()),
13229 )
13230 ..Point::new(
13231 end_row.previous_row().0,
13232 buffer.line_len(end_row.previous_row()),
13233 );
13234 let insertion_point = display_map
13235 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13236 .0;
13237
13238 // Don't move lines across excerpts
13239 if buffer
13240 .excerpt_containing(insertion_point..range_to_move.end)
13241 .is_some()
13242 {
13243 let text = buffer
13244 .text_for_range(range_to_move.clone())
13245 .flat_map(|s| s.chars())
13246 .skip(1)
13247 .chain(['\n'])
13248 .collect::<String>();
13249
13250 edits.push((
13251 buffer.anchor_after(range_to_move.start)
13252 ..buffer.anchor_before(range_to_move.end),
13253 String::new(),
13254 ));
13255 let insertion_anchor = buffer.anchor_after(insertion_point);
13256 edits.push((insertion_anchor..insertion_anchor, text));
13257
13258 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13259
13260 // Move selections up
13261 new_selections.extend(contiguous_row_selections.drain(..).map(
13262 |mut selection| {
13263 selection.start.row -= row_delta;
13264 selection.end.row -= row_delta;
13265 selection
13266 },
13267 ));
13268
13269 // Move folds up
13270 unfold_ranges.push(range_to_move.clone());
13271 for fold in display_map.folds_in_range(
13272 buffer.anchor_before(range_to_move.start)
13273 ..buffer.anchor_after(range_to_move.end),
13274 ) {
13275 let mut start = fold.range.start.to_point(&buffer);
13276 let mut end = fold.range.end.to_point(&buffer);
13277 start.row -= row_delta;
13278 end.row -= row_delta;
13279 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13280 }
13281 }
13282 }
13283
13284 // If we didn't move line(s), preserve the existing selections
13285 new_selections.append(&mut contiguous_row_selections);
13286 }
13287
13288 self.transact(window, cx, |this, window, cx| {
13289 this.unfold_ranges(&unfold_ranges, true, true, cx);
13290 this.buffer.update(cx, |buffer, cx| {
13291 for (range, text) in edits {
13292 buffer.edit([(range, text)], None, cx);
13293 }
13294 });
13295 this.fold_creases(refold_creases, true, window, cx);
13296 this.change_selections(Default::default(), window, cx, |s| {
13297 s.select(new_selections);
13298 })
13299 });
13300 }
13301
13302 pub fn move_line_down(
13303 &mut self,
13304 _: &MoveLineDown,
13305 window: &mut Window,
13306 cx: &mut Context<Self>,
13307 ) {
13308 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13309 if self.mode.is_single_line() {
13310 cx.propagate();
13311 return;
13312 }
13313
13314 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13315 let buffer = self.buffer.read(cx).snapshot(cx);
13316
13317 let mut edits = Vec::new();
13318 let mut unfold_ranges = Vec::new();
13319 let mut refold_creases = Vec::new();
13320
13321 let selections = self.selections.all::<Point>(&display_map);
13322 let mut selections = selections.iter().peekable();
13323 let mut contiguous_row_selections = Vec::new();
13324 let mut new_selections = Vec::new();
13325
13326 while let Some(selection) = selections.next() {
13327 // Find all the selections that span a contiguous row range
13328 let (start_row, end_row) = consume_contiguous_rows(
13329 &mut contiguous_row_selections,
13330 selection,
13331 &display_map,
13332 &mut selections,
13333 );
13334
13335 // Move the text spanned by the row range to be after the last line of the row range
13336 if end_row.0 <= buffer.max_point().row {
13337 let range_to_move =
13338 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13339 let insertion_point = display_map
13340 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13341 .0;
13342
13343 // Don't move lines across excerpt boundaries
13344 if buffer
13345 .excerpt_containing(range_to_move.start..insertion_point)
13346 .is_some()
13347 {
13348 let mut text = String::from("\n");
13349 text.extend(buffer.text_for_range(range_to_move.clone()));
13350 text.pop(); // Drop trailing newline
13351 edits.push((
13352 buffer.anchor_after(range_to_move.start)
13353 ..buffer.anchor_before(range_to_move.end),
13354 String::new(),
13355 ));
13356 let insertion_anchor = buffer.anchor_after(insertion_point);
13357 edits.push((insertion_anchor..insertion_anchor, text));
13358
13359 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13360
13361 // Move selections down
13362 new_selections.extend(contiguous_row_selections.drain(..).map(
13363 |mut selection| {
13364 selection.start.row += row_delta;
13365 selection.end.row += row_delta;
13366 selection
13367 },
13368 ));
13369
13370 // Move folds down
13371 unfold_ranges.push(range_to_move.clone());
13372 for fold in display_map.folds_in_range(
13373 buffer.anchor_before(range_to_move.start)
13374 ..buffer.anchor_after(range_to_move.end),
13375 ) {
13376 let mut start = fold.range.start.to_point(&buffer);
13377 let mut end = fold.range.end.to_point(&buffer);
13378 start.row += row_delta;
13379 end.row += row_delta;
13380 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13381 }
13382 }
13383 }
13384
13385 // If we didn't move line(s), preserve the existing selections
13386 new_selections.append(&mut contiguous_row_selections);
13387 }
13388
13389 self.transact(window, cx, |this, window, cx| {
13390 this.unfold_ranges(&unfold_ranges, true, true, cx);
13391 this.buffer.update(cx, |buffer, cx| {
13392 for (range, text) in edits {
13393 buffer.edit([(range, text)], None, cx);
13394 }
13395 });
13396 this.fold_creases(refold_creases, true, window, cx);
13397 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13398 });
13399 }
13400
13401 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13403 let text_layout_details = &self.text_layout_details(window, cx);
13404 self.transact(window, cx, |this, window, cx| {
13405 let edits = this.change_selections(Default::default(), window, cx, |s| {
13406 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13407 s.move_with(&mut |display_map, selection| {
13408 if !selection.is_empty() {
13409 return;
13410 }
13411
13412 let mut head = selection.head();
13413 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13414 if head.column() == display_map.line_len(head.row()) {
13415 transpose_offset = display_map
13416 .buffer_snapshot()
13417 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13418 }
13419
13420 if transpose_offset == MultiBufferOffset(0) {
13421 return;
13422 }
13423
13424 *head.column_mut() += 1;
13425 head = display_map.clip_point(head, Bias::Right);
13426 let goal = SelectionGoal::HorizontalPosition(
13427 display_map
13428 .x_for_display_point(head, text_layout_details)
13429 .into(),
13430 );
13431 selection.collapse_to(head, goal);
13432
13433 let transpose_start = display_map
13434 .buffer_snapshot()
13435 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13436 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13437 let transpose_end = display_map
13438 .buffer_snapshot()
13439 .clip_offset(transpose_offset + 1usize, Bias::Right);
13440 if let Some(ch) = display_map
13441 .buffer_snapshot()
13442 .chars_at(transpose_start)
13443 .next()
13444 {
13445 edits.push((transpose_start..transpose_offset, String::new()));
13446 edits.push((transpose_end..transpose_end, ch.to_string()));
13447 }
13448 }
13449 });
13450 edits
13451 });
13452 this.buffer
13453 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13454 let selections = this
13455 .selections
13456 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13457 this.change_selections(Default::default(), window, cx, |s| {
13458 s.select(selections);
13459 });
13460 });
13461 }
13462
13463 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13465 if self.mode.is_single_line() {
13466 cx.propagate();
13467 return;
13468 }
13469
13470 self.rewrap_impl(RewrapOptions::default(), cx)
13471 }
13472
13473 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13474 let buffer = self.buffer.read(cx).snapshot(cx);
13475 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13476
13477 #[derive(Clone, Debug, PartialEq)]
13478 enum CommentFormat {
13479 /// single line comment, with prefix for line
13480 Line(String),
13481 /// single line within a block comment, with prefix for line
13482 BlockLine(String),
13483 /// a single line of a block comment that includes the initial delimiter
13484 BlockCommentWithStart(BlockCommentConfig),
13485 /// a single line of a block comment that includes the ending delimiter
13486 BlockCommentWithEnd(BlockCommentConfig),
13487 }
13488
13489 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13490 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13491 let language_settings = buffer.language_settings_at(selection.head(), cx);
13492 let language_scope = buffer.language_scope_at(selection.head());
13493
13494 let indent_and_prefix_for_row =
13495 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13496 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13497 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13498 &language_scope
13499 {
13500 let indent_end = Point::new(row, indent.len);
13501 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13502 let line_text_after_indent = buffer
13503 .text_for_range(indent_end..line_end)
13504 .collect::<String>();
13505
13506 let is_within_comment_override = buffer
13507 .language_scope_at(indent_end)
13508 .is_some_and(|scope| scope.override_name() == Some("comment"));
13509 let comment_delimiters = if is_within_comment_override {
13510 // we are within a comment syntax node, but we don't
13511 // yet know what kind of comment: block, doc or line
13512 match (
13513 language_scope.documentation_comment(),
13514 language_scope.block_comment(),
13515 ) {
13516 (Some(config), _) | (_, Some(config))
13517 if buffer.contains_str_at(indent_end, &config.start) =>
13518 {
13519 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13520 }
13521 (Some(config), _) | (_, Some(config))
13522 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13523 {
13524 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13525 }
13526 (Some(config), _) | (_, Some(config))
13527 if buffer.contains_str_at(indent_end, &config.prefix) =>
13528 {
13529 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13530 }
13531 (_, _) => language_scope
13532 .line_comment_prefixes()
13533 .iter()
13534 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13535 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13536 }
13537 } else {
13538 // we not in an overridden comment node, but we may
13539 // be within a non-overridden line comment node
13540 language_scope
13541 .line_comment_prefixes()
13542 .iter()
13543 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13544 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13545 };
13546
13547 let rewrap_prefix = language_scope
13548 .rewrap_prefixes()
13549 .iter()
13550 .find_map(|prefix_regex| {
13551 prefix_regex.find(&line_text_after_indent).map(|mat| {
13552 if mat.start() == 0 {
13553 Some(mat.as_str().to_string())
13554 } else {
13555 None
13556 }
13557 })
13558 })
13559 .flatten();
13560 (comment_delimiters, rewrap_prefix)
13561 } else {
13562 (None, None)
13563 };
13564 (indent, comment_prefix, rewrap_prefix)
13565 };
13566
13567 let mut start_row = selection.start.row;
13568 let mut end_row = selection.end.row;
13569
13570 if selection.is_empty() {
13571 let cursor_row = selection.start.row;
13572
13573 let (mut indent_size, comment_prefix, _) = indent_and_prefix_for_row(cursor_row);
13574 let line_prefix = match &comment_prefix {
13575 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13576 Some(prefix.as_str())
13577 }
13578 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13579 prefix, ..
13580 })) => Some(prefix.as_ref()),
13581 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13582 start: _,
13583 end: _,
13584 prefix,
13585 tab_size,
13586 })) => {
13587 indent_size.len += tab_size;
13588 Some(prefix.as_ref())
13589 }
13590 None => None,
13591 };
13592 let indent_prefix = indent_size.chars().collect::<String>();
13593 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13594
13595 'expand_upwards: while start_row > 0 {
13596 let prev_row = start_row - 1;
13597 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13598 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13599 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13600 {
13601 start_row = prev_row;
13602 } else {
13603 break 'expand_upwards;
13604 }
13605 }
13606
13607 'expand_downwards: while end_row < buffer.max_point().row {
13608 let next_row = end_row + 1;
13609 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13610 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13611 && !buffer.is_line_blank(MultiBufferRow(next_row))
13612 {
13613 end_row = next_row;
13614 } else {
13615 break 'expand_downwards;
13616 }
13617 }
13618 }
13619
13620 let mut non_blank_rows_iter = (start_row..=end_row)
13621 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13622 .peekable();
13623
13624 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13625 row
13626 } else {
13627 return Vec::new();
13628 };
13629
13630 let mut ranges = Vec::new();
13631
13632 let mut current_range_start = first_row;
13633 let mut prev_row = first_row;
13634 let (
13635 mut current_range_indent,
13636 mut current_range_comment_delimiters,
13637 mut current_range_rewrap_prefix,
13638 ) = indent_and_prefix_for_row(first_row);
13639
13640 for row in non_blank_rows_iter.skip(1) {
13641 let has_paragraph_break = row > prev_row + 1;
13642
13643 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13644 indent_and_prefix_for_row(row);
13645
13646 let has_indent_change = row_indent != current_range_indent;
13647 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13648
13649 let has_boundary_change = has_comment_change
13650 || row_rewrap_prefix.is_some()
13651 || (has_indent_change && current_range_comment_delimiters.is_some());
13652
13653 if has_paragraph_break || has_boundary_change {
13654 ranges.push((
13655 language_settings.clone(),
13656 Point::new(current_range_start, 0)
13657 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13658 current_range_indent,
13659 current_range_comment_delimiters.clone(),
13660 current_range_rewrap_prefix.clone(),
13661 ));
13662 current_range_start = row;
13663 current_range_indent = row_indent;
13664 current_range_comment_delimiters = row_comment_delimiters;
13665 current_range_rewrap_prefix = row_rewrap_prefix;
13666 }
13667 prev_row = row;
13668 }
13669
13670 ranges.push((
13671 language_settings.clone(),
13672 Point::new(current_range_start, 0)
13673 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13674 current_range_indent,
13675 current_range_comment_delimiters,
13676 current_range_rewrap_prefix,
13677 ));
13678
13679 ranges
13680 });
13681
13682 let mut edits = Vec::new();
13683 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13684
13685 for (language_settings, wrap_range, mut indent_size, comment_prefix, rewrap_prefix) in
13686 wrap_ranges
13687 {
13688 let start_row = wrap_range.start.row;
13689 let end_row = wrap_range.end.row;
13690
13691 // Skip selections that overlap with a range that has already been rewrapped.
13692 let selection_range = start_row..end_row;
13693 if rewrapped_row_ranges
13694 .iter()
13695 .any(|range| range.overlaps(&selection_range))
13696 {
13697 continue;
13698 }
13699
13700 let tab_size = language_settings.tab_size;
13701
13702 let (line_prefix, inside_comment) = match &comment_prefix {
13703 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13704 (Some(prefix.as_str()), true)
13705 }
13706 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13707 (Some(prefix.as_ref()), true)
13708 }
13709 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13710 start: _,
13711 end: _,
13712 prefix,
13713 tab_size,
13714 })) => {
13715 indent_size.len += tab_size;
13716 (Some(prefix.as_ref()), true)
13717 }
13718 None => (None, false),
13719 };
13720 let indent_prefix = indent_size.chars().collect::<String>();
13721 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13722
13723 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13724 RewrapBehavior::InComments => inside_comment,
13725 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13726 RewrapBehavior::Anywhere => true,
13727 };
13728
13729 let should_rewrap = options.override_language_settings
13730 || allow_rewrap_based_on_language
13731 || self.hard_wrap.is_some();
13732 if !should_rewrap {
13733 continue;
13734 }
13735
13736 let start = Point::new(start_row, 0);
13737 let start_offset = ToOffset::to_offset(&start, &buffer);
13738 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13739 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13740 let mut first_line_delimiter = None;
13741 let mut last_line_delimiter = None;
13742 let Some(lines_without_prefixes) = selection_text
13743 .lines()
13744 .enumerate()
13745 .map(|(ix, line)| {
13746 let line_trimmed = line.trim_start();
13747 if rewrap_prefix.is_some() && ix > 0 {
13748 Ok(line_trimmed)
13749 } else if let Some(
13750 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13751 start,
13752 prefix,
13753 end,
13754 tab_size,
13755 })
13756 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13757 start,
13758 prefix,
13759 end,
13760 tab_size,
13761 }),
13762 ) = &comment_prefix
13763 {
13764 let line_trimmed = line_trimmed
13765 .strip_prefix(start.as_ref())
13766 .map(|s| {
13767 let mut indent_size = indent_size;
13768 indent_size.len -= tab_size;
13769 let indent_prefix: String = indent_size.chars().collect();
13770 first_line_delimiter = Some((indent_prefix, start));
13771 s.trim_start()
13772 })
13773 .unwrap_or(line_trimmed);
13774 let line_trimmed = line_trimmed
13775 .strip_suffix(end.as_ref())
13776 .map(|s| {
13777 last_line_delimiter = Some(end);
13778 s.trim_end()
13779 })
13780 .unwrap_or(line_trimmed);
13781 let line_trimmed = line_trimmed
13782 .strip_prefix(prefix.as_ref())
13783 .unwrap_or(line_trimmed);
13784 Ok(line_trimmed)
13785 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13786 line_trimmed.strip_prefix(prefix).with_context(|| {
13787 format!("line did not start with prefix {prefix:?}: {line:?}")
13788 })
13789 } else {
13790 line_trimmed
13791 .strip_prefix(&line_prefix.trim_start())
13792 .with_context(|| {
13793 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13794 })
13795 }
13796 })
13797 .collect::<Result<Vec<_>, _>>()
13798 .log_err()
13799 else {
13800 continue;
13801 };
13802
13803 let wrap_column = options.line_length.or(self.hard_wrap).unwrap_or_else(|| {
13804 buffer
13805 .language_settings_at(Point::new(start_row, 0), cx)
13806 .preferred_line_length as usize
13807 });
13808
13809 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13810 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13811 } else {
13812 line_prefix.clone()
13813 };
13814
13815 let wrapped_text = {
13816 let mut wrapped_text = wrap_with_prefix(
13817 line_prefix,
13818 subsequent_lines_prefix,
13819 lines_without_prefixes.join("\n"),
13820 wrap_column,
13821 tab_size,
13822 options.preserve_existing_whitespace,
13823 );
13824
13825 if let Some((indent, delimiter)) = first_line_delimiter {
13826 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13827 }
13828 if let Some(last_line) = last_line_delimiter {
13829 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13830 }
13831
13832 wrapped_text
13833 };
13834
13835 // TODO: should always use char-based diff while still supporting cursor behavior that
13836 // matches vim.
13837 let mut diff_options = DiffOptions::default();
13838 if options.override_language_settings {
13839 diff_options.max_word_diff_len = 0;
13840 diff_options.max_word_diff_line_count = 0;
13841 } else {
13842 diff_options.max_word_diff_len = usize::MAX;
13843 diff_options.max_word_diff_line_count = usize::MAX;
13844 }
13845
13846 for (old_range, new_text) in
13847 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13848 {
13849 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13850 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13851 edits.push((edit_start..edit_end, new_text));
13852 }
13853
13854 rewrapped_row_ranges.push(start_row..=end_row);
13855 }
13856
13857 self.buffer
13858 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13859 }
13860
13861 pub fn cut_common(
13862 &mut self,
13863 cut_no_selection_line: bool,
13864 window: &mut Window,
13865 cx: &mut Context<Self>,
13866 ) -> ClipboardItem {
13867 let mut text = String::new();
13868 let buffer = self.buffer.read(cx).snapshot(cx);
13869 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13870 let mut clipboard_selections = Vec::with_capacity(selections.len());
13871 {
13872 let max_point = buffer.max_point();
13873 let mut is_first = true;
13874 let mut prev_selection_was_entire_line = false;
13875 for selection in &mut selections {
13876 let is_entire_line =
13877 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13878 if is_entire_line {
13879 selection.start = Point::new(selection.start.row, 0);
13880 if !selection.is_empty() && selection.end.column == 0 {
13881 selection.end = cmp::min(max_point, selection.end);
13882 } else {
13883 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13884 }
13885 selection.goal = SelectionGoal::None;
13886 }
13887 if is_first {
13888 is_first = false;
13889 } else if !prev_selection_was_entire_line {
13890 text += "\n";
13891 }
13892 prev_selection_was_entire_line = is_entire_line;
13893 let mut len = 0;
13894 for chunk in buffer.text_for_range(selection.start..selection.end) {
13895 text.push_str(chunk);
13896 len += chunk.len();
13897 }
13898
13899 clipboard_selections.push(ClipboardSelection::for_buffer(
13900 len,
13901 is_entire_line,
13902 selection.range(),
13903 &buffer,
13904 self.project.as_ref(),
13905 cx,
13906 ));
13907 }
13908 }
13909
13910 self.transact(window, cx, |this, window, cx| {
13911 this.change_selections(Default::default(), window, cx, |s| {
13912 s.select(selections);
13913 });
13914 this.insert("", window, cx);
13915 });
13916 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13917 }
13918
13919 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13921 let item = self.cut_common(true, window, cx);
13922 cx.write_to_clipboard(item);
13923 }
13924
13925 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13927 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13928 s.move_with(&mut |snapshot, sel| {
13929 if sel.is_empty() {
13930 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13931 }
13932 if sel.is_empty() {
13933 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13934 }
13935 });
13936 });
13937 let item = self.cut_common(false, window, cx);
13938 cx.set_global(KillRing(item))
13939 }
13940
13941 pub fn kill_ring_yank(
13942 &mut self,
13943 _: &KillRingYank,
13944 window: &mut Window,
13945 cx: &mut Context<Self>,
13946 ) {
13947 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13948 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13949 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13950 (kill_ring.text().to_string(), kill_ring.metadata_json())
13951 } else {
13952 return;
13953 }
13954 } else {
13955 return;
13956 };
13957 self.do_paste(&text, metadata, false, window, cx);
13958 }
13959
13960 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13961 self.do_copy(true, cx);
13962 }
13963
13964 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13965 self.do_copy(false, cx);
13966 }
13967
13968 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13969 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13970 let buffer = self.buffer.read(cx).read(cx);
13971 let mut text = String::new();
13972 let mut clipboard_selections = Vec::with_capacity(selections.len());
13973
13974 let max_point = buffer.max_point();
13975 let mut is_first = true;
13976 for selection in &selections {
13977 let mut start = selection.start;
13978 let mut end = selection.end;
13979 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13980 let mut add_trailing_newline = false;
13981 if is_entire_line {
13982 start = Point::new(start.row, 0);
13983 let next_line_start = Point::new(end.row + 1, 0);
13984 if next_line_start <= max_point {
13985 end = next_line_start;
13986 } else {
13987 // We're on the last line without a trailing newline.
13988 // Copy to the end of the line and add a newline afterwards.
13989 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13990 add_trailing_newline = true;
13991 }
13992 }
13993
13994 let mut trimmed_selections = Vec::new();
13995 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13996 let row = MultiBufferRow(start.row);
13997 let first_indent = buffer.indent_size_for_line(row);
13998 if first_indent.len == 0 || start.column > first_indent.len {
13999 trimmed_selections.push(start..end);
14000 } else {
14001 trimmed_selections.push(
14002 Point::new(row.0, first_indent.len)
14003 ..Point::new(row.0, buffer.line_len(row)),
14004 );
14005 for row in start.row + 1..=end.row {
14006 let mut line_len = buffer.line_len(MultiBufferRow(row));
14007 if row == end.row {
14008 line_len = end.column;
14009 }
14010 if line_len == 0 {
14011 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
14012 continue;
14013 }
14014 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
14015 if row_indent_size.len >= first_indent.len {
14016 trimmed_selections
14017 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
14018 } else {
14019 trimmed_selections.clear();
14020 trimmed_selections.push(start..end);
14021 break;
14022 }
14023 }
14024 }
14025 } else {
14026 trimmed_selections.push(start..end);
14027 }
14028
14029 let is_multiline_trim = trimmed_selections.len() > 1;
14030 let mut selection_len: usize = 0;
14031 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
14032
14033 for trimmed_range in trimmed_selections {
14034 if is_first {
14035 is_first = false;
14036 } else if is_multiline_trim || !prev_selection_was_entire_line {
14037 text.push('\n');
14038 if is_multiline_trim {
14039 selection_len += 1;
14040 }
14041 }
14042 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
14043 text.push_str(chunk);
14044 selection_len += chunk.len();
14045 }
14046 if add_trailing_newline {
14047 text.push('\n');
14048 selection_len += 1;
14049 }
14050 }
14051
14052 clipboard_selections.push(ClipboardSelection::for_buffer(
14053 selection_len,
14054 is_entire_line,
14055 start..end,
14056 &buffer,
14057 self.project.as_ref(),
14058 cx,
14059 ));
14060 }
14061
14062 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
14063 text,
14064 clipboard_selections,
14065 ));
14066 }
14067
14068 pub fn do_paste(
14069 &mut self,
14070 text: &String,
14071 clipboard_selections: Option<Vec<ClipboardSelection>>,
14072 handle_entire_lines: bool,
14073 window: &mut Window,
14074 cx: &mut Context<Self>,
14075 ) {
14076 if self.read_only(cx) {
14077 return;
14078 }
14079
14080 self.finalize_last_transaction(cx);
14081
14082 let clipboard_text = Cow::Borrowed(text.as_str());
14083
14084 self.transact(window, cx, |this, window, cx| {
14085 let had_active_edit_prediction = this.has_active_edit_prediction();
14086 let display_map = this.display_snapshot(cx);
14087 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
14088 let cursor_offset = this
14089 .selections
14090 .last::<MultiBufferOffset>(&display_map)
14091 .head();
14092
14093 if let Some(mut clipboard_selections) = clipboard_selections {
14094 let all_selections_were_entire_line =
14095 clipboard_selections.iter().all(|s| s.is_entire_line);
14096 let first_selection_indent_column =
14097 clipboard_selections.first().map(|s| s.first_line_indent);
14098 if clipboard_selections.len() != old_selections.len() {
14099 clipboard_selections.drain(..);
14100 }
14101 let mut auto_indent_on_paste = true;
14102
14103 this.buffer.update(cx, |buffer, cx| {
14104 let snapshot = buffer.read(cx);
14105 auto_indent_on_paste = snapshot
14106 .language_settings_at(cursor_offset, cx)
14107 .auto_indent_on_paste;
14108
14109 let mut start_offset = 0;
14110 let mut edits = Vec::new();
14111 let mut original_indent_columns = Vec::new();
14112 for (ix, selection) in old_selections.iter().enumerate() {
14113 let to_insert;
14114 let entire_line;
14115 let original_indent_column;
14116 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
14117 let end_offset = start_offset + clipboard_selection.len;
14118 to_insert = &clipboard_text[start_offset..end_offset];
14119 entire_line = clipboard_selection.is_entire_line;
14120 start_offset = if entire_line {
14121 end_offset
14122 } else {
14123 end_offset + 1
14124 };
14125 original_indent_column = Some(clipboard_selection.first_line_indent);
14126 } else {
14127 to_insert = &*clipboard_text;
14128 entire_line = all_selections_were_entire_line;
14129 original_indent_column = first_selection_indent_column
14130 }
14131
14132 let (range, to_insert) =
14133 if selection.is_empty() && handle_entire_lines && entire_line {
14134 // If the corresponding selection was empty when this slice of the
14135 // clipboard text was written, then the entire line containing the
14136 // selection was copied. If this selection is also currently empty,
14137 // then paste the line before the current line of the buffer.
14138 let column = selection.start.to_point(&snapshot).column as usize;
14139 let line_start = selection.start - column;
14140 (line_start..line_start, Cow::Borrowed(to_insert))
14141 } else {
14142 let language = snapshot.language_at(selection.head());
14143 let range = selection.range();
14144 if let Some(language) = language
14145 && language.name() == "Markdown"
14146 {
14147 edit_for_markdown_paste(
14148 &snapshot,
14149 range,
14150 to_insert,
14151 url::Url::parse(to_insert).ok(),
14152 )
14153 } else {
14154 (range, Cow::Borrowed(to_insert))
14155 }
14156 };
14157
14158 edits.push((range, to_insert));
14159 original_indent_columns.push(original_indent_column);
14160 }
14161 drop(snapshot);
14162
14163 buffer.edit(
14164 edits,
14165 if auto_indent_on_paste {
14166 Some(AutoindentMode::Block {
14167 original_indent_columns,
14168 })
14169 } else {
14170 None
14171 },
14172 cx,
14173 );
14174 });
14175
14176 let selections = this
14177 .selections
14178 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
14179 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14180 } else {
14181 let url = url::Url::parse(&clipboard_text).ok();
14182
14183 let auto_indent_mode = if !clipboard_text.is_empty() {
14184 Some(AutoindentMode::Block {
14185 original_indent_columns: Vec::new(),
14186 })
14187 } else {
14188 None
14189 };
14190
14191 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
14192 let snapshot = buffer.snapshot(cx);
14193
14194 let anchors = old_selections
14195 .iter()
14196 .map(|s| {
14197 let anchor = snapshot.anchor_after(s.head());
14198 s.map(|_| anchor)
14199 })
14200 .collect::<Vec<_>>();
14201
14202 let mut edits = Vec::new();
14203
14204 // When pasting text without metadata (e.g. copied from an
14205 // external editor using multiple cursors) and the number of
14206 // lines matches the number of selections, distribute one
14207 // line per cursor instead of pasting the whole text at each.
14208 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14209 let distribute_lines =
14210 old_selections.len() > 1 && lines.len() == old_selections.len();
14211
14212 for (ix, selection) in old_selections.iter().enumerate() {
14213 let language = snapshot.language_at(selection.head());
14214 let range = selection.range();
14215
14216 let text_for_cursor: &str = if distribute_lines {
14217 lines[ix]
14218 } else {
14219 &clipboard_text
14220 };
14221
14222 let (edit_range, edit_text) = if let Some(language) = language
14223 && language.name() == "Markdown"
14224 {
14225 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14226 } else {
14227 (range, Cow::Borrowed(text_for_cursor))
14228 };
14229
14230 edits.push((edit_range, edit_text));
14231 }
14232
14233 drop(snapshot);
14234 buffer.edit(edits, auto_indent_mode, cx);
14235
14236 anchors
14237 });
14238
14239 this.change_selections(Default::default(), window, cx, |s| {
14240 s.select_anchors(selection_anchors);
14241 });
14242 }
14243
14244 // 🤔 | .. | show_in_menu |
14245 // | .. | true true
14246 // | had_edit_prediction | false true
14247
14248 let trigger_in_words =
14249 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14250
14251 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14252 });
14253 }
14254
14255 pub fn diff_clipboard_with_selection(
14256 &mut self,
14257 _: &DiffClipboardWithSelection,
14258 window: &mut Window,
14259 cx: &mut Context<Self>,
14260 ) {
14261 let selections = self
14262 .selections
14263 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14264
14265 if selections.is_empty() {
14266 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14267 return;
14268 };
14269
14270 let clipboard_text = cx.read_from_clipboard().and_then(|item| {
14271 item.entries().iter().find_map(|entry| match entry {
14272 ClipboardEntry::String(text) => Some(text.text().to_string()),
14273 _ => None,
14274 })
14275 });
14276
14277 let Some(clipboard_text) = clipboard_text else {
14278 log::warn!("Clipboard doesn't contain text.");
14279 return;
14280 };
14281
14282 window.dispatch_action(
14283 Box::new(DiffClipboardWithSelectionData {
14284 clipboard_text,
14285 editor: cx.entity(),
14286 }),
14287 cx,
14288 );
14289 }
14290
14291 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14292 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14293 if let Some(item) = cx.read_from_clipboard() {
14294 let clipboard_string = item.entries().iter().find_map(|entry| match entry {
14295 ClipboardEntry::String(s) => Some(s),
14296 _ => None,
14297 });
14298 match clipboard_string {
14299 Some(clipboard_string) => self.do_paste(
14300 clipboard_string.text(),
14301 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14302 true,
14303 window,
14304 cx,
14305 ),
14306 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14307 }
14308 }
14309 }
14310
14311 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14312 if self.read_only(cx) {
14313 return;
14314 }
14315
14316 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14317
14318 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14319 if let Some((selections, _)) =
14320 self.selection_history.transaction(transaction_id).cloned()
14321 {
14322 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14323 s.select_anchors(selections.to_vec());
14324 });
14325 } else {
14326 log::error!(
14327 "No entry in selection_history found for undo. \
14328 This may correspond to a bug where undo does not update the selection. \
14329 If this is occurring, please add details to \
14330 https://github.com/zed-industries/zed/issues/22692"
14331 );
14332 }
14333 self.request_autoscroll(Autoscroll::fit(), cx);
14334 self.unmark_text(window, cx);
14335 self.refresh_edit_prediction(true, false, window, cx);
14336 cx.emit(EditorEvent::Edited { transaction_id });
14337 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14338 }
14339 }
14340
14341 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14342 if self.read_only(cx) {
14343 return;
14344 }
14345
14346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14347
14348 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14349 if let Some((_, Some(selections))) =
14350 self.selection_history.transaction(transaction_id).cloned()
14351 {
14352 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14353 s.select_anchors(selections.to_vec());
14354 });
14355 } else {
14356 log::error!(
14357 "No entry in selection_history found for redo. \
14358 This may correspond to a bug where undo does not update the selection. \
14359 If this is occurring, please add details to \
14360 https://github.com/zed-industries/zed/issues/22692"
14361 );
14362 }
14363 self.request_autoscroll(Autoscroll::fit(), cx);
14364 self.unmark_text(window, cx);
14365 self.refresh_edit_prediction(true, false, window, cx);
14366 cx.emit(EditorEvent::Edited { transaction_id });
14367 }
14368 }
14369
14370 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14371 self.buffer
14372 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14373 }
14374
14375 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14376 self.buffer
14377 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14378 }
14379
14380 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14381 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14382 self.change_selections(Default::default(), window, cx, |s| {
14383 s.move_with(&mut |map, selection| {
14384 let cursor = if selection.is_empty() {
14385 movement::left(map, selection.start)
14386 } else {
14387 selection.start
14388 };
14389 selection.collapse_to(cursor, SelectionGoal::None);
14390 });
14391 })
14392 }
14393
14394 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14395 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14396 self.change_selections(Default::default(), window, cx, |s| {
14397 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14398 })
14399 }
14400
14401 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14402 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14403 self.change_selections(Default::default(), window, cx, |s| {
14404 s.move_with(&mut |map, selection| {
14405 let cursor = if selection.is_empty() {
14406 movement::right(map, selection.end)
14407 } else {
14408 selection.end
14409 };
14410 selection.collapse_to(cursor, SelectionGoal::None)
14411 });
14412 })
14413 }
14414
14415 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14416 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14417 self.change_selections(Default::default(), window, cx, |s| {
14418 s.move_heads_with(&mut |map, head, _| {
14419 (movement::right(map, head), SelectionGoal::None)
14420 });
14421 });
14422 }
14423
14424 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14425 if self.take_rename(true, window, cx).is_some() {
14426 return;
14427 }
14428
14429 if self.mode.is_single_line() {
14430 cx.propagate();
14431 return;
14432 }
14433
14434 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14435
14436 let text_layout_details = &self.text_layout_details(window, cx);
14437 let selection_count = self.selections.count();
14438 let first_selection = self.selections.first_anchor();
14439
14440 self.change_selections(Default::default(), window, cx, |s| {
14441 s.move_with(&mut |map, selection| {
14442 if !selection.is_empty() {
14443 selection.goal = SelectionGoal::None;
14444 }
14445 let (cursor, goal) = movement::up(
14446 map,
14447 selection.start,
14448 selection.goal,
14449 false,
14450 text_layout_details,
14451 );
14452 selection.collapse_to(cursor, goal);
14453 });
14454 });
14455
14456 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14457 {
14458 cx.propagate();
14459 }
14460 }
14461
14462 pub fn move_up_by_lines(
14463 &mut self,
14464 action: &MoveUpByLines,
14465 window: &mut Window,
14466 cx: &mut Context<Self>,
14467 ) {
14468 if self.take_rename(true, window, cx).is_some() {
14469 return;
14470 }
14471
14472 if self.mode.is_single_line() {
14473 cx.propagate();
14474 return;
14475 }
14476
14477 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14478
14479 let text_layout_details = &self.text_layout_details(window, cx);
14480
14481 self.change_selections(Default::default(), window, cx, |s| {
14482 s.move_with(&mut |map, selection| {
14483 if !selection.is_empty() {
14484 selection.goal = SelectionGoal::None;
14485 }
14486 let (cursor, goal) = movement::up_by_rows(
14487 map,
14488 selection.start,
14489 action.lines,
14490 selection.goal,
14491 false,
14492 text_layout_details,
14493 );
14494 selection.collapse_to(cursor, goal);
14495 });
14496 })
14497 }
14498
14499 pub fn move_down_by_lines(
14500 &mut self,
14501 action: &MoveDownByLines,
14502 window: &mut Window,
14503 cx: &mut Context<Self>,
14504 ) {
14505 if self.take_rename(true, window, cx).is_some() {
14506 return;
14507 }
14508
14509 if self.mode.is_single_line() {
14510 cx.propagate();
14511 return;
14512 }
14513
14514 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14515
14516 let text_layout_details = &self.text_layout_details(window, cx);
14517
14518 self.change_selections(Default::default(), window, cx, |s| {
14519 s.move_with(&mut |map, selection| {
14520 if !selection.is_empty() {
14521 selection.goal = SelectionGoal::None;
14522 }
14523 let (cursor, goal) = movement::down_by_rows(
14524 map,
14525 selection.start,
14526 action.lines,
14527 selection.goal,
14528 false,
14529 text_layout_details,
14530 );
14531 selection.collapse_to(cursor, goal);
14532 });
14533 })
14534 }
14535
14536 pub fn select_down_by_lines(
14537 &mut self,
14538 action: &SelectDownByLines,
14539 window: &mut Window,
14540 cx: &mut Context<Self>,
14541 ) {
14542 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14543 let text_layout_details = &self.text_layout_details(window, cx);
14544 self.change_selections(Default::default(), window, cx, |s| {
14545 s.move_heads_with(&mut |map, head, goal| {
14546 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14547 })
14548 })
14549 }
14550
14551 pub fn select_up_by_lines(
14552 &mut self,
14553 action: &SelectUpByLines,
14554 window: &mut Window,
14555 cx: &mut Context<Self>,
14556 ) {
14557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14558 let text_layout_details = &self.text_layout_details(window, cx);
14559 self.change_selections(Default::default(), window, cx, |s| {
14560 s.move_heads_with(&mut |map, head, goal| {
14561 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14562 })
14563 })
14564 }
14565
14566 pub fn select_page_up(
14567 &mut self,
14568 _: &SelectPageUp,
14569 window: &mut Window,
14570 cx: &mut Context<Self>,
14571 ) {
14572 let Some(row_count) = self.visible_row_count() else {
14573 return;
14574 };
14575
14576 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14577
14578 let text_layout_details = &self.text_layout_details(window, cx);
14579
14580 self.change_selections(Default::default(), window, cx, |s| {
14581 s.move_heads_with(&mut |map, head, goal| {
14582 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14583 })
14584 })
14585 }
14586
14587 pub fn move_page_up(
14588 &mut self,
14589 action: &MovePageUp,
14590 window: &mut Window,
14591 cx: &mut Context<Self>,
14592 ) {
14593 if self.take_rename(true, window, cx).is_some() {
14594 return;
14595 }
14596
14597 if self
14598 .context_menu
14599 .borrow_mut()
14600 .as_mut()
14601 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14602 .unwrap_or(false)
14603 {
14604 return;
14605 }
14606
14607 if matches!(self.mode, EditorMode::SingleLine) {
14608 cx.propagate();
14609 return;
14610 }
14611
14612 let Some(row_count) = self.visible_row_count() else {
14613 return;
14614 };
14615
14616 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14617
14618 let effects = if action.center_cursor {
14619 SelectionEffects::scroll(Autoscroll::center())
14620 } else {
14621 SelectionEffects::default()
14622 };
14623
14624 let text_layout_details = &self.text_layout_details(window, cx);
14625
14626 self.change_selections(effects, window, cx, |s| {
14627 s.move_with(&mut |map, selection| {
14628 if !selection.is_empty() {
14629 selection.goal = SelectionGoal::None;
14630 }
14631 let (cursor, goal) = movement::up_by_rows(
14632 map,
14633 selection.end,
14634 row_count,
14635 selection.goal,
14636 false,
14637 text_layout_details,
14638 );
14639 selection.collapse_to(cursor, goal);
14640 });
14641 });
14642 }
14643
14644 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14645 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14646 let text_layout_details = &self.text_layout_details(window, cx);
14647 self.change_selections(Default::default(), window, cx, |s| {
14648 s.move_heads_with(&mut |map, head, goal| {
14649 movement::up(map, head, goal, false, text_layout_details)
14650 })
14651 })
14652 }
14653
14654 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14655 self.take_rename(true, window, cx);
14656
14657 if self.mode.is_single_line() {
14658 cx.propagate();
14659 return;
14660 }
14661
14662 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14663
14664 let text_layout_details = &self.text_layout_details(window, cx);
14665 let selection_count = self.selections.count();
14666 let first_selection = self.selections.first_anchor();
14667
14668 self.change_selections(Default::default(), window, cx, |s| {
14669 s.move_with(&mut |map, selection| {
14670 if !selection.is_empty() {
14671 selection.goal = SelectionGoal::None;
14672 }
14673 let (cursor, goal) = movement::down(
14674 map,
14675 selection.end,
14676 selection.goal,
14677 false,
14678 text_layout_details,
14679 );
14680 selection.collapse_to(cursor, goal);
14681 });
14682 });
14683
14684 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14685 {
14686 cx.propagate();
14687 }
14688 }
14689
14690 pub fn select_page_down(
14691 &mut self,
14692 _: &SelectPageDown,
14693 window: &mut Window,
14694 cx: &mut Context<Self>,
14695 ) {
14696 let Some(row_count) = self.visible_row_count() else {
14697 return;
14698 };
14699
14700 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14701
14702 let text_layout_details = &self.text_layout_details(window, cx);
14703
14704 self.change_selections(Default::default(), window, cx, |s| {
14705 s.move_heads_with(&mut |map, head, goal| {
14706 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14707 })
14708 })
14709 }
14710
14711 pub fn move_page_down(
14712 &mut self,
14713 action: &MovePageDown,
14714 window: &mut Window,
14715 cx: &mut Context<Self>,
14716 ) {
14717 if self.take_rename(true, window, cx).is_some() {
14718 return;
14719 }
14720
14721 if self
14722 .context_menu
14723 .borrow_mut()
14724 .as_mut()
14725 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14726 .unwrap_or(false)
14727 {
14728 return;
14729 }
14730
14731 if matches!(self.mode, EditorMode::SingleLine) {
14732 cx.propagate();
14733 return;
14734 }
14735
14736 let Some(row_count) = self.visible_row_count() else {
14737 return;
14738 };
14739
14740 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14741
14742 let effects = if action.center_cursor {
14743 SelectionEffects::scroll(Autoscroll::center())
14744 } else {
14745 SelectionEffects::default()
14746 };
14747
14748 let text_layout_details = &self.text_layout_details(window, cx);
14749 self.change_selections(effects, window, cx, |s| {
14750 s.move_with(&mut |map, selection| {
14751 if !selection.is_empty() {
14752 selection.goal = SelectionGoal::None;
14753 }
14754 let (cursor, goal) = movement::down_by_rows(
14755 map,
14756 selection.end,
14757 row_count,
14758 selection.goal,
14759 false,
14760 text_layout_details,
14761 );
14762 selection.collapse_to(cursor, goal);
14763 });
14764 });
14765 }
14766
14767 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14768 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14769 let text_layout_details = &self.text_layout_details(window, cx);
14770 self.change_selections(Default::default(), window, cx, |s| {
14771 s.move_heads_with(&mut |map, head, goal| {
14772 movement::down(map, head, goal, false, text_layout_details)
14773 })
14774 });
14775 }
14776
14777 pub fn context_menu_first(
14778 &mut self,
14779 _: &ContextMenuFirst,
14780 window: &mut Window,
14781 cx: &mut Context<Self>,
14782 ) {
14783 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14784 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14785 }
14786 }
14787
14788 pub fn context_menu_prev(
14789 &mut self,
14790 _: &ContextMenuPrevious,
14791 window: &mut Window,
14792 cx: &mut Context<Self>,
14793 ) {
14794 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14795 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14796 }
14797 }
14798
14799 pub fn context_menu_next(
14800 &mut self,
14801 _: &ContextMenuNext,
14802 window: &mut Window,
14803 cx: &mut Context<Self>,
14804 ) {
14805 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14806 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14807 }
14808 }
14809
14810 pub fn context_menu_last(
14811 &mut self,
14812 _: &ContextMenuLast,
14813 window: &mut Window,
14814 cx: &mut Context<Self>,
14815 ) {
14816 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14817 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14818 }
14819 }
14820
14821 pub fn signature_help_prev(
14822 &mut self,
14823 _: &SignatureHelpPrevious,
14824 _: &mut Window,
14825 cx: &mut Context<Self>,
14826 ) {
14827 if let Some(popover) = self.signature_help_state.popover_mut() {
14828 if popover.current_signature == 0 {
14829 popover.current_signature = popover.signatures.len() - 1;
14830 } else {
14831 popover.current_signature -= 1;
14832 }
14833 cx.notify();
14834 }
14835 }
14836
14837 pub fn signature_help_next(
14838 &mut self,
14839 _: &SignatureHelpNext,
14840 _: &mut Window,
14841 cx: &mut Context<Self>,
14842 ) {
14843 if let Some(popover) = self.signature_help_state.popover_mut() {
14844 if popover.current_signature + 1 == popover.signatures.len() {
14845 popover.current_signature = 0;
14846 } else {
14847 popover.current_signature += 1;
14848 }
14849 cx.notify();
14850 }
14851 }
14852
14853 pub fn move_to_previous_word_start(
14854 &mut self,
14855 _: &MoveToPreviousWordStart,
14856 window: &mut Window,
14857 cx: &mut Context<Self>,
14858 ) {
14859 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14860 self.change_selections(Default::default(), window, cx, |s| {
14861 s.move_cursors_with(&mut |map, head, _| {
14862 (
14863 movement::previous_word_start(map, head),
14864 SelectionGoal::None,
14865 )
14866 });
14867 })
14868 }
14869
14870 pub fn move_to_previous_subword_start(
14871 &mut self,
14872 _: &MoveToPreviousSubwordStart,
14873 window: &mut Window,
14874 cx: &mut Context<Self>,
14875 ) {
14876 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14877 self.change_selections(Default::default(), window, cx, |s| {
14878 s.move_cursors_with(&mut |map, head, _| {
14879 (
14880 movement::previous_subword_start(map, head),
14881 SelectionGoal::None,
14882 )
14883 });
14884 })
14885 }
14886
14887 pub fn select_to_previous_word_start(
14888 &mut self,
14889 _: &SelectToPreviousWordStart,
14890 window: &mut Window,
14891 cx: &mut Context<Self>,
14892 ) {
14893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14894 self.change_selections(Default::default(), window, cx, |s| {
14895 s.move_heads_with(&mut |map, head, _| {
14896 (
14897 movement::previous_word_start(map, head),
14898 SelectionGoal::None,
14899 )
14900 });
14901 })
14902 }
14903
14904 pub fn select_to_previous_subword_start(
14905 &mut self,
14906 _: &SelectToPreviousSubwordStart,
14907 window: &mut Window,
14908 cx: &mut Context<Self>,
14909 ) {
14910 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14911 self.change_selections(Default::default(), window, cx, |s| {
14912 s.move_heads_with(&mut |map, head, _| {
14913 (
14914 movement::previous_subword_start(map, head),
14915 SelectionGoal::None,
14916 )
14917 });
14918 })
14919 }
14920
14921 pub fn delete_to_previous_word_start(
14922 &mut self,
14923 action: &DeleteToPreviousWordStart,
14924 window: &mut Window,
14925 cx: &mut Context<Self>,
14926 ) {
14927 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14928 self.transact(window, cx, |this, window, cx| {
14929 this.select_autoclose_pair(window, cx);
14930 this.change_selections(Default::default(), window, cx, |s| {
14931 s.move_with(&mut |map, selection| {
14932 if selection.is_empty() {
14933 let mut cursor = if action.ignore_newlines {
14934 movement::previous_word_start(map, selection.head())
14935 } else {
14936 movement::previous_word_start_or_newline(map, selection.head())
14937 };
14938 cursor = movement::adjust_greedy_deletion(
14939 map,
14940 selection.head(),
14941 cursor,
14942 action.ignore_brackets,
14943 );
14944 selection.set_head(cursor, SelectionGoal::None);
14945 }
14946 });
14947 });
14948 this.insert("", window, cx);
14949 });
14950 }
14951
14952 pub fn delete_to_previous_subword_start(
14953 &mut self,
14954 action: &DeleteToPreviousSubwordStart,
14955 window: &mut Window,
14956 cx: &mut Context<Self>,
14957 ) {
14958 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14959 self.transact(window, cx, |this, window, cx| {
14960 this.select_autoclose_pair(window, cx);
14961 this.change_selections(Default::default(), window, cx, |s| {
14962 s.move_with(&mut |map, selection| {
14963 if selection.is_empty() {
14964 let mut cursor = if action.ignore_newlines {
14965 movement::previous_subword_start(map, selection.head())
14966 } else {
14967 movement::previous_subword_start_or_newline(map, selection.head())
14968 };
14969 cursor = movement::adjust_greedy_deletion(
14970 map,
14971 selection.head(),
14972 cursor,
14973 action.ignore_brackets,
14974 );
14975 selection.set_head(cursor, SelectionGoal::None);
14976 }
14977 });
14978 });
14979 this.insert("", window, cx);
14980 });
14981 }
14982
14983 pub fn move_to_next_word_end(
14984 &mut self,
14985 _: &MoveToNextWordEnd,
14986 window: &mut Window,
14987 cx: &mut Context<Self>,
14988 ) {
14989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14990 self.change_selections(Default::default(), window, cx, |s| {
14991 s.move_cursors_with(&mut |map, head, _| {
14992 (movement::next_word_end(map, head), SelectionGoal::None)
14993 });
14994 })
14995 }
14996
14997 pub fn move_to_next_subword_end(
14998 &mut self,
14999 _: &MoveToNextSubwordEnd,
15000 window: &mut Window,
15001 cx: &mut Context<Self>,
15002 ) {
15003 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15004 self.change_selections(Default::default(), window, cx, |s| {
15005 s.move_cursors_with(&mut |map, head, _| {
15006 (movement::next_subword_end(map, head), SelectionGoal::None)
15007 });
15008 })
15009 }
15010
15011 pub fn select_to_next_word_end(
15012 &mut self,
15013 _: &SelectToNextWordEnd,
15014 window: &mut Window,
15015 cx: &mut Context<Self>,
15016 ) {
15017 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15018 self.change_selections(Default::default(), window, cx, |s| {
15019 s.move_heads_with(&mut |map, head, _| {
15020 (movement::next_word_end(map, head), SelectionGoal::None)
15021 });
15022 })
15023 }
15024
15025 pub fn select_to_next_subword_end(
15026 &mut self,
15027 _: &SelectToNextSubwordEnd,
15028 window: &mut Window,
15029 cx: &mut Context<Self>,
15030 ) {
15031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15032 self.change_selections(Default::default(), window, cx, |s| {
15033 s.move_heads_with(&mut |map, head, _| {
15034 (movement::next_subword_end(map, head), SelectionGoal::None)
15035 });
15036 })
15037 }
15038
15039 pub fn delete_to_next_word_end(
15040 &mut self,
15041 action: &DeleteToNextWordEnd,
15042 window: &mut Window,
15043 cx: &mut Context<Self>,
15044 ) {
15045 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15046 self.transact(window, cx, |this, window, cx| {
15047 this.change_selections(Default::default(), window, cx, |s| {
15048 s.move_with(&mut |map, selection| {
15049 if selection.is_empty() {
15050 let mut cursor = if action.ignore_newlines {
15051 movement::next_word_end(map, selection.head())
15052 } else {
15053 movement::next_word_end_or_newline(map, selection.head())
15054 };
15055 cursor = movement::adjust_greedy_deletion(
15056 map,
15057 selection.head(),
15058 cursor,
15059 action.ignore_brackets,
15060 );
15061 selection.set_head(cursor, SelectionGoal::None);
15062 }
15063 });
15064 });
15065 this.insert("", window, cx);
15066 });
15067 }
15068
15069 pub fn delete_to_next_subword_end(
15070 &mut self,
15071 action: &DeleteToNextSubwordEnd,
15072 window: &mut Window,
15073 cx: &mut Context<Self>,
15074 ) {
15075 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15076 self.transact(window, cx, |this, window, cx| {
15077 this.change_selections(Default::default(), window, cx, |s| {
15078 s.move_with(&mut |map, selection| {
15079 if selection.is_empty() {
15080 let mut cursor = if action.ignore_newlines {
15081 movement::next_subword_end(map, selection.head())
15082 } else {
15083 movement::next_subword_end_or_newline(map, selection.head())
15084 };
15085 cursor = movement::adjust_greedy_deletion(
15086 map,
15087 selection.head(),
15088 cursor,
15089 action.ignore_brackets,
15090 );
15091 selection.set_head(cursor, SelectionGoal::None);
15092 }
15093 });
15094 });
15095 this.insert("", window, cx);
15096 });
15097 }
15098
15099 pub fn move_to_beginning_of_line(
15100 &mut self,
15101 action: &MoveToBeginningOfLine,
15102 window: &mut Window,
15103 cx: &mut Context<Self>,
15104 ) {
15105 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15106 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15107 self.change_selections(Default::default(), window, cx, |s| {
15108 s.move_cursors_with(&mut |map, head, _| {
15109 (
15110 movement::indented_line_beginning(
15111 map,
15112 head,
15113 action.stop_at_soft_wraps,
15114 stop_at_indent,
15115 ),
15116 SelectionGoal::None,
15117 )
15118 });
15119 })
15120 }
15121
15122 pub fn select_to_beginning_of_line(
15123 &mut self,
15124 action: &SelectToBeginningOfLine,
15125 window: &mut Window,
15126 cx: &mut Context<Self>,
15127 ) {
15128 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15129 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15130 self.change_selections(Default::default(), window, cx, |s| {
15131 s.move_heads_with(&mut |map, head, _| {
15132 (
15133 movement::indented_line_beginning(
15134 map,
15135 head,
15136 action.stop_at_soft_wraps,
15137 stop_at_indent,
15138 ),
15139 SelectionGoal::None,
15140 )
15141 });
15142 });
15143 }
15144
15145 pub fn delete_to_beginning_of_line(
15146 &mut self,
15147 action: &DeleteToBeginningOfLine,
15148 window: &mut Window,
15149 cx: &mut Context<Self>,
15150 ) {
15151 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15152 self.transact(window, cx, |this, window, cx| {
15153 this.change_selections(Default::default(), window, cx, |s| {
15154 s.move_with(&mut |_, selection| {
15155 selection.reversed = true;
15156 });
15157 });
15158
15159 this.select_to_beginning_of_line(
15160 &SelectToBeginningOfLine {
15161 stop_at_soft_wraps: false,
15162 stop_at_indent: action.stop_at_indent,
15163 },
15164 window,
15165 cx,
15166 );
15167 this.backspace(&Backspace, window, cx);
15168 });
15169 }
15170
15171 pub fn move_to_end_of_line(
15172 &mut self,
15173 action: &MoveToEndOfLine,
15174 window: &mut Window,
15175 cx: &mut Context<Self>,
15176 ) {
15177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15178 self.change_selections(Default::default(), window, cx, |s| {
15179 s.move_cursors_with(&mut |map, head, _| {
15180 (
15181 movement::line_end(map, head, action.stop_at_soft_wraps),
15182 SelectionGoal::None,
15183 )
15184 });
15185 })
15186 }
15187
15188 pub fn select_to_end_of_line(
15189 &mut self,
15190 action: &SelectToEndOfLine,
15191 window: &mut Window,
15192 cx: &mut Context<Self>,
15193 ) {
15194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15195 self.change_selections(Default::default(), window, cx, |s| {
15196 s.move_heads_with(&mut |map, head, _| {
15197 (
15198 movement::line_end(map, head, action.stop_at_soft_wraps),
15199 SelectionGoal::None,
15200 )
15201 });
15202 })
15203 }
15204
15205 pub fn delete_to_end_of_line(
15206 &mut self,
15207 _: &DeleteToEndOfLine,
15208 window: &mut Window,
15209 cx: &mut Context<Self>,
15210 ) {
15211 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15212 self.transact(window, cx, |this, window, cx| {
15213 this.select_to_end_of_line(
15214 &SelectToEndOfLine {
15215 stop_at_soft_wraps: false,
15216 },
15217 window,
15218 cx,
15219 );
15220 this.delete(&Delete, window, cx);
15221 });
15222 }
15223
15224 pub fn cut_to_end_of_line(
15225 &mut self,
15226 action: &CutToEndOfLine,
15227 window: &mut Window,
15228 cx: &mut Context<Self>,
15229 ) {
15230 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15231 self.transact(window, cx, |this, window, cx| {
15232 this.select_to_end_of_line(
15233 &SelectToEndOfLine {
15234 stop_at_soft_wraps: false,
15235 },
15236 window,
15237 cx,
15238 );
15239 if !action.stop_at_newlines {
15240 this.change_selections(Default::default(), window, cx, |s| {
15241 s.move_with(&mut |_, sel| {
15242 if sel.is_empty() {
15243 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15244 }
15245 });
15246 });
15247 }
15248 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15249 let item = this.cut_common(false, window, cx);
15250 cx.write_to_clipboard(item);
15251 });
15252 }
15253
15254 pub fn move_to_start_of_paragraph(
15255 &mut self,
15256 _: &MoveToStartOfParagraph,
15257 window: &mut Window,
15258 cx: &mut Context<Self>,
15259 ) {
15260 if matches!(self.mode, EditorMode::SingleLine) {
15261 cx.propagate();
15262 return;
15263 }
15264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15265 self.change_selections(Default::default(), window, cx, |s| {
15266 s.move_with(&mut |map, selection| {
15267 selection.collapse_to(
15268 movement::start_of_paragraph(map, selection.head(), 1),
15269 SelectionGoal::None,
15270 )
15271 });
15272 })
15273 }
15274
15275 pub fn move_to_end_of_paragraph(
15276 &mut self,
15277 _: &MoveToEndOfParagraph,
15278 window: &mut Window,
15279 cx: &mut Context<Self>,
15280 ) {
15281 if matches!(self.mode, EditorMode::SingleLine) {
15282 cx.propagate();
15283 return;
15284 }
15285 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15286 self.change_selections(Default::default(), window, cx, |s| {
15287 s.move_with(&mut |map, selection| {
15288 selection.collapse_to(
15289 movement::end_of_paragraph(map, selection.head(), 1),
15290 SelectionGoal::None,
15291 )
15292 });
15293 })
15294 }
15295
15296 pub fn select_to_start_of_paragraph(
15297 &mut self,
15298 _: &SelectToStartOfParagraph,
15299 window: &mut Window,
15300 cx: &mut Context<Self>,
15301 ) {
15302 if matches!(self.mode, EditorMode::SingleLine) {
15303 cx.propagate();
15304 return;
15305 }
15306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15307 self.change_selections(Default::default(), window, cx, |s| {
15308 s.move_heads_with(&mut |map, head, _| {
15309 (
15310 movement::start_of_paragraph(map, head, 1),
15311 SelectionGoal::None,
15312 )
15313 });
15314 })
15315 }
15316
15317 pub fn select_to_end_of_paragraph(
15318 &mut self,
15319 _: &SelectToEndOfParagraph,
15320 window: &mut Window,
15321 cx: &mut Context<Self>,
15322 ) {
15323 if matches!(self.mode, EditorMode::SingleLine) {
15324 cx.propagate();
15325 return;
15326 }
15327 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15328 self.change_selections(Default::default(), window, cx, |s| {
15329 s.move_heads_with(&mut |map, head, _| {
15330 (
15331 movement::end_of_paragraph(map, head, 1),
15332 SelectionGoal::None,
15333 )
15334 });
15335 })
15336 }
15337
15338 pub fn move_to_start_of_excerpt(
15339 &mut self,
15340 _: &MoveToStartOfExcerpt,
15341 window: &mut Window,
15342 cx: &mut Context<Self>,
15343 ) {
15344 if matches!(self.mode, EditorMode::SingleLine) {
15345 cx.propagate();
15346 return;
15347 }
15348 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15349 self.change_selections(Default::default(), window, cx, |s| {
15350 s.move_with(&mut |map, selection| {
15351 selection.collapse_to(
15352 movement::start_of_excerpt(
15353 map,
15354 selection.head(),
15355 workspace::searchable::Direction::Prev,
15356 ),
15357 SelectionGoal::None,
15358 )
15359 });
15360 })
15361 }
15362
15363 pub fn move_to_start_of_next_excerpt(
15364 &mut self,
15365 _: &MoveToStartOfNextExcerpt,
15366 window: &mut Window,
15367 cx: &mut Context<Self>,
15368 ) {
15369 if matches!(self.mode, EditorMode::SingleLine) {
15370 cx.propagate();
15371 return;
15372 }
15373
15374 self.change_selections(Default::default(), window, cx, |s| {
15375 s.move_with(&mut |map, selection| {
15376 selection.collapse_to(
15377 movement::start_of_excerpt(
15378 map,
15379 selection.head(),
15380 workspace::searchable::Direction::Next,
15381 ),
15382 SelectionGoal::None,
15383 )
15384 });
15385 })
15386 }
15387
15388 pub fn move_to_end_of_excerpt(
15389 &mut self,
15390 _: &MoveToEndOfExcerpt,
15391 window: &mut Window,
15392 cx: &mut Context<Self>,
15393 ) {
15394 if matches!(self.mode, EditorMode::SingleLine) {
15395 cx.propagate();
15396 return;
15397 }
15398 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15399 self.change_selections(Default::default(), window, cx, |s| {
15400 s.move_with(&mut |map, selection| {
15401 selection.collapse_to(
15402 movement::end_of_excerpt(
15403 map,
15404 selection.head(),
15405 workspace::searchable::Direction::Next,
15406 ),
15407 SelectionGoal::None,
15408 )
15409 });
15410 })
15411 }
15412
15413 pub fn move_to_end_of_previous_excerpt(
15414 &mut self,
15415 _: &MoveToEndOfPreviousExcerpt,
15416 window: &mut Window,
15417 cx: &mut Context<Self>,
15418 ) {
15419 if matches!(self.mode, EditorMode::SingleLine) {
15420 cx.propagate();
15421 return;
15422 }
15423 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15424 self.change_selections(Default::default(), window, cx, |s| {
15425 s.move_with(&mut |map, selection| {
15426 selection.collapse_to(
15427 movement::end_of_excerpt(
15428 map,
15429 selection.head(),
15430 workspace::searchable::Direction::Prev,
15431 ),
15432 SelectionGoal::None,
15433 )
15434 });
15435 })
15436 }
15437
15438 pub fn select_to_start_of_excerpt(
15439 &mut self,
15440 _: &SelectToStartOfExcerpt,
15441 window: &mut Window,
15442 cx: &mut Context<Self>,
15443 ) {
15444 if matches!(self.mode, EditorMode::SingleLine) {
15445 cx.propagate();
15446 return;
15447 }
15448 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15449 self.change_selections(Default::default(), window, cx, |s| {
15450 s.move_heads_with(&mut |map, head, _| {
15451 (
15452 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15453 SelectionGoal::None,
15454 )
15455 });
15456 })
15457 }
15458
15459 pub fn select_to_start_of_next_excerpt(
15460 &mut self,
15461 _: &SelectToStartOfNextExcerpt,
15462 window: &mut Window,
15463 cx: &mut Context<Self>,
15464 ) {
15465 if matches!(self.mode, EditorMode::SingleLine) {
15466 cx.propagate();
15467 return;
15468 }
15469 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15470 self.change_selections(Default::default(), window, cx, |s| {
15471 s.move_heads_with(&mut |map, head, _| {
15472 (
15473 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15474 SelectionGoal::None,
15475 )
15476 });
15477 })
15478 }
15479
15480 pub fn select_to_end_of_excerpt(
15481 &mut self,
15482 _: &SelectToEndOfExcerpt,
15483 window: &mut Window,
15484 cx: &mut Context<Self>,
15485 ) {
15486 if matches!(self.mode, EditorMode::SingleLine) {
15487 cx.propagate();
15488 return;
15489 }
15490 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15491 self.change_selections(Default::default(), window, cx, |s| {
15492 s.move_heads_with(&mut |map, head, _| {
15493 (
15494 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15495 SelectionGoal::None,
15496 )
15497 });
15498 })
15499 }
15500
15501 pub fn select_to_end_of_previous_excerpt(
15502 &mut self,
15503 _: &SelectToEndOfPreviousExcerpt,
15504 window: &mut Window,
15505 cx: &mut Context<Self>,
15506 ) {
15507 if matches!(self.mode, EditorMode::SingleLine) {
15508 cx.propagate();
15509 return;
15510 }
15511 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15512 self.change_selections(Default::default(), window, cx, |s| {
15513 s.move_heads_with(&mut |map, head, _| {
15514 (
15515 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15516 SelectionGoal::None,
15517 )
15518 });
15519 })
15520 }
15521
15522 pub fn move_to_beginning(
15523 &mut self,
15524 _: &MoveToBeginning,
15525 window: &mut Window,
15526 cx: &mut Context<Self>,
15527 ) {
15528 if matches!(self.mode, EditorMode::SingleLine) {
15529 cx.propagate();
15530 return;
15531 }
15532 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15533 self.change_selections(Default::default(), window, cx, |s| {
15534 s.select_ranges(vec![Anchor::Min..Anchor::Min]);
15535 });
15536 }
15537
15538 pub fn select_to_beginning(
15539 &mut self,
15540 _: &SelectToBeginning,
15541 window: &mut Window,
15542 cx: &mut Context<Self>,
15543 ) {
15544 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15545 selection.set_head(Point::zero(), SelectionGoal::None);
15546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15547 self.change_selections(Default::default(), window, cx, |s| {
15548 s.select(vec![selection]);
15549 });
15550 }
15551
15552 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15553 if matches!(self.mode, EditorMode::SingleLine) {
15554 cx.propagate();
15555 return;
15556 }
15557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15558 let cursor = self.buffer.read(cx).read(cx).len();
15559 self.change_selections(Default::default(), window, cx, |s| {
15560 s.select_ranges(vec![cursor..cursor])
15561 });
15562 }
15563
15564 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15565 self.nav_history = nav_history;
15566 }
15567
15568 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15569 self.nav_history.as_ref()
15570 }
15571
15572 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15573 self.push_to_nav_history(
15574 self.selections.newest_anchor().head(),
15575 None,
15576 false,
15577 true,
15578 cx,
15579 );
15580 }
15581
15582 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15583 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15584 let buffer = self.buffer.read(cx).read(cx);
15585 let cursor_position = cursor_anchor.to_point(&buffer);
15586 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15587 let scroll_top_row = scroll_anchor.top_row(&buffer);
15588 drop(buffer);
15589
15590 NavigationData {
15591 cursor_anchor,
15592 cursor_position,
15593 scroll_anchor,
15594 scroll_top_row,
15595 }
15596 }
15597
15598 fn navigation_entry(
15599 &self,
15600 cursor_anchor: Anchor,
15601 cx: &mut Context<Self>,
15602 ) -> Option<NavigationEntry> {
15603 let Some(history) = self.nav_history.clone() else {
15604 return None;
15605 };
15606 let data = self.navigation_data(cursor_anchor, cx);
15607 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15608 }
15609
15610 fn push_to_nav_history(
15611 &mut self,
15612 cursor_anchor: Anchor,
15613 new_position: Option<Point>,
15614 is_deactivate: bool,
15615 always: bool,
15616 cx: &mut Context<Self>,
15617 ) {
15618 let data = self.navigation_data(cursor_anchor, cx);
15619 if let Some(nav_history) = self.nav_history.as_mut() {
15620 if let Some(new_position) = new_position {
15621 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15622 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15623 return;
15624 }
15625 }
15626
15627 let cursor_row = data.cursor_position.row;
15628 nav_history.push(Some(data), Some(cursor_row), cx);
15629 cx.emit(EditorEvent::PushedToNavHistory {
15630 anchor: cursor_anchor,
15631 is_deactivate,
15632 })
15633 }
15634 }
15635
15636 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15637 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15638 let buffer = self.buffer.read(cx).snapshot(cx);
15639 let mut selection = self
15640 .selections
15641 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15642 selection.set_head(buffer.len(), SelectionGoal::None);
15643 self.change_selections(Default::default(), window, cx, |s| {
15644 s.select(vec![selection]);
15645 });
15646 }
15647
15648 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15649 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15650 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15651 s.select_ranges(vec![Anchor::Min..Anchor::Max]);
15652 });
15653 }
15654
15655 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15656 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15657 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15658 let mut selections = self.selections.all::<Point>(&display_map);
15659 let max_point = display_map.buffer_snapshot().max_point();
15660 for selection in &mut selections {
15661 let rows = selection.spanned_rows(true, &display_map);
15662 selection.start = Point::new(rows.start.0, 0);
15663 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15664 selection.reversed = false;
15665 }
15666 self.change_selections(Default::default(), window, cx, |s| {
15667 s.select(selections);
15668 });
15669 }
15670
15671 pub fn split_selection_into_lines(
15672 &mut self,
15673 action: &SplitSelectionIntoLines,
15674 window: &mut Window,
15675 cx: &mut Context<Self>,
15676 ) {
15677 let selections = self
15678 .selections
15679 .all::<Point>(&self.display_snapshot(cx))
15680 .into_iter()
15681 .map(|selection| selection.start..selection.end)
15682 .collect::<Vec<_>>();
15683 self.unfold_ranges(&selections, true, false, cx);
15684
15685 let mut new_selection_ranges = Vec::new();
15686 {
15687 let buffer = self.buffer.read(cx).read(cx);
15688 for selection in selections {
15689 for row in selection.start.row..selection.end.row {
15690 let line_start = Point::new(row, 0);
15691 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15692
15693 if action.keep_selections {
15694 // Keep the selection range for each line
15695 let selection_start = if row == selection.start.row {
15696 selection.start
15697 } else {
15698 line_start
15699 };
15700 new_selection_ranges.push(selection_start..line_end);
15701 } else {
15702 // Collapse to cursor at end of line
15703 new_selection_ranges.push(line_end..line_end);
15704 }
15705 }
15706
15707 let is_multiline_selection = selection.start.row != selection.end.row;
15708 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15709 // so this action feels more ergonomic when paired with other selection operations
15710 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15711 if !should_skip_last {
15712 if action.keep_selections {
15713 if is_multiline_selection {
15714 let line_start = Point::new(selection.end.row, 0);
15715 new_selection_ranges.push(line_start..selection.end);
15716 } else {
15717 new_selection_ranges.push(selection.start..selection.end);
15718 }
15719 } else {
15720 new_selection_ranges.push(selection.end..selection.end);
15721 }
15722 }
15723 }
15724 }
15725 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15726 s.select_ranges(new_selection_ranges);
15727 });
15728 }
15729
15730 pub fn add_selection_above(
15731 &mut self,
15732 action: &AddSelectionAbove,
15733 window: &mut Window,
15734 cx: &mut Context<Self>,
15735 ) {
15736 self.add_selection(true, action.skip_soft_wrap, window, cx);
15737 }
15738
15739 pub fn add_selection_below(
15740 &mut self,
15741 action: &AddSelectionBelow,
15742 window: &mut Window,
15743 cx: &mut Context<Self>,
15744 ) {
15745 self.add_selection(false, action.skip_soft_wrap, window, cx);
15746 }
15747
15748 fn add_selection(
15749 &mut self,
15750 above: bool,
15751 skip_soft_wrap: bool,
15752 window: &mut Window,
15753 cx: &mut Context<Self>,
15754 ) {
15755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15756
15757 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15758 let all_selections = self.selections.all::<Point>(&display_map);
15759 let text_layout_details = self.text_layout_details(window, cx);
15760
15761 let (mut columnar_selections, new_selections_to_columnarize) = {
15762 if let Some(state) = self.add_selections_state.as_ref() {
15763 let columnar_selection_ids: HashSet<_> = state
15764 .groups
15765 .iter()
15766 .flat_map(|group| group.stack.iter())
15767 .copied()
15768 .collect();
15769
15770 all_selections
15771 .into_iter()
15772 .partition(|s| columnar_selection_ids.contains(&s.id))
15773 } else {
15774 (Vec::new(), all_selections)
15775 }
15776 };
15777
15778 let mut state = self
15779 .add_selections_state
15780 .take()
15781 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15782
15783 for selection in new_selections_to_columnarize {
15784 let range = selection.display_range(&display_map).sorted();
15785 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15786 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15787 let positions = start_x.min(end_x)..start_x.max(end_x);
15788 let mut stack = Vec::new();
15789 for row in range.start.row().0..=range.end.row().0 {
15790 if let Some(selection) = self.selections.build_columnar_selection(
15791 &display_map,
15792 DisplayRow(row),
15793 &positions,
15794 selection.reversed,
15795 &text_layout_details,
15796 ) {
15797 stack.push(selection.id);
15798 columnar_selections.push(selection);
15799 }
15800 }
15801 if !stack.is_empty() {
15802 if above {
15803 stack.reverse();
15804 }
15805 state.groups.push(AddSelectionsGroup { above, stack });
15806 }
15807 }
15808
15809 let mut final_selections = Vec::new();
15810 let end_row = if above {
15811 DisplayRow(0)
15812 } else {
15813 display_map.max_point().row()
15814 };
15815
15816 // When `skip_soft_wrap` is true, we use UTF-16 columns instead of pixel
15817 // positions to place new selections, so we need to keep track of the
15818 // column range of the oldest selection in each group, because
15819 // intermediate selections may have been clamped to shorter lines.
15820 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15821 let mut map = HashMap::default();
15822 for group in state.groups.iter() {
15823 if let Some(oldest_id) = group.stack.first() {
15824 if let Some(oldest_selection) =
15825 columnar_selections.iter().find(|s| s.id == *oldest_id)
15826 {
15827 let snapshot = display_map.buffer_snapshot();
15828 let start_col =
15829 snapshot.point_to_point_utf16(oldest_selection.start).column;
15830 let end_col = snapshot.point_to_point_utf16(oldest_selection.end).column;
15831 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15832 for id in &group.stack {
15833 map.insert(*id, goal_columns.clone());
15834 }
15835 }
15836 }
15837 }
15838 map
15839 } else {
15840 HashMap::default()
15841 };
15842
15843 let mut last_added_item_per_group = HashMap::default();
15844 for group in state.groups.iter_mut() {
15845 if let Some(last_id) = group.stack.last() {
15846 last_added_item_per_group.insert(*last_id, group);
15847 }
15848 }
15849
15850 for selection in columnar_selections {
15851 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15852 if above == group.above {
15853 let range = selection.display_range(&display_map).sorted();
15854 debug_assert_eq!(range.start.row(), range.end.row());
15855 let row = range.start.row();
15856 let positions =
15857 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15858 Pixels::from(start)..Pixels::from(end)
15859 } else {
15860 let start_x =
15861 display_map.x_for_display_point(range.start, &text_layout_details);
15862 let end_x =
15863 display_map.x_for_display_point(range.end, &text_layout_details);
15864 start_x.min(end_x)..start_x.max(end_x)
15865 };
15866
15867 let maybe_new_selection = if skip_soft_wrap {
15868 let goal_columns = goal_columns_by_selection_id
15869 .remove(&selection.id)
15870 .unwrap_or_else(|| {
15871 let snapshot = display_map.buffer_snapshot();
15872 let start_col =
15873 snapshot.point_to_point_utf16(selection.start).column;
15874 let end_col = snapshot.point_to_point_utf16(selection.end).column;
15875 start_col.min(end_col)..start_col.max(end_col)
15876 });
15877 self.selections.find_next_columnar_selection_by_buffer_row(
15878 &display_map,
15879 row,
15880 end_row,
15881 above,
15882 &goal_columns,
15883 selection.reversed,
15884 &text_layout_details,
15885 )
15886 } else {
15887 self.selections.find_next_columnar_selection_by_display_row(
15888 &display_map,
15889 row,
15890 end_row,
15891 above,
15892 &positions,
15893 selection.reversed,
15894 &text_layout_details,
15895 )
15896 };
15897
15898 if let Some(new_selection) = maybe_new_selection {
15899 group.stack.push(new_selection.id);
15900 if above {
15901 final_selections.push(new_selection);
15902 final_selections.push(selection);
15903 } else {
15904 final_selections.push(selection);
15905 final_selections.push(new_selection);
15906 }
15907 } else {
15908 final_selections.push(selection);
15909 }
15910 } else {
15911 group.stack.pop();
15912 }
15913 } else {
15914 final_selections.push(selection);
15915 }
15916 }
15917
15918 self.change_selections(Default::default(), window, cx, |s| {
15919 s.select(final_selections);
15920 });
15921
15922 let final_selection_ids: HashSet<_> = self
15923 .selections
15924 .all::<Point>(&display_map)
15925 .iter()
15926 .map(|s| s.id)
15927 .collect();
15928 state.groups.retain_mut(|group| {
15929 // selections might get merged above so we remove invalid items from stacks
15930 group.stack.retain(|id| final_selection_ids.contains(id));
15931
15932 // single selection in stack can be treated as initial state
15933 group.stack.len() > 1
15934 });
15935
15936 if !state.groups.is_empty() {
15937 self.add_selections_state = Some(state);
15938 }
15939 }
15940
15941 pub fn insert_snippet_at_selections(
15942 &mut self,
15943 action: &InsertSnippet,
15944 window: &mut Window,
15945 cx: &mut Context<Self>,
15946 ) {
15947 self.try_insert_snippet_at_selections(action, window, cx)
15948 .log_err();
15949 }
15950
15951 fn try_insert_snippet_at_selections(
15952 &mut self,
15953 action: &InsertSnippet,
15954 window: &mut Window,
15955 cx: &mut Context<Self>,
15956 ) -> Result<()> {
15957 let insertion_ranges = self
15958 .selections
15959 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15960 .into_iter()
15961 .map(|selection| selection.range())
15962 .collect_vec();
15963
15964 let snippet = if let Some(snippet_body) = &action.snippet {
15965 if action.language.is_none() && action.name.is_none() {
15966 Snippet::parse(snippet_body)?
15967 } else {
15968 bail!("`snippet` is mutually exclusive with `language` and `name`")
15969 }
15970 } else if let Some(name) = &action.name {
15971 let project = self.project().context("no project")?;
15972 let snippet_store = project.read(cx).snippets().read(cx);
15973 let snippet = snippet_store
15974 .snippets_for(action.language.clone(), cx)
15975 .into_iter()
15976 .find(|snippet| snippet.name == *name)
15977 .context("snippet not found")?;
15978 Snippet::parse(&snippet.body)?
15979 } else {
15980 // todo(andrew): open modal to select snippet
15981 bail!("`name` or `snippet` is required")
15982 };
15983
15984 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15985 }
15986
15987 fn select_match_ranges(
15988 &mut self,
15989 range: Range<MultiBufferOffset>,
15990 reversed: bool,
15991 replace_newest: bool,
15992 auto_scroll: Option<Autoscroll>,
15993 window: &mut Window,
15994 cx: &mut Context<Editor>,
15995 ) {
15996 self.unfold_ranges(
15997 std::slice::from_ref(&range),
15998 false,
15999 auto_scroll.is_some(),
16000 cx,
16001 );
16002 let effects = if let Some(scroll) = auto_scroll {
16003 SelectionEffects::scroll(scroll)
16004 } else {
16005 SelectionEffects::no_scroll()
16006 };
16007 self.change_selections(effects, window, cx, |s| {
16008 if replace_newest {
16009 s.delete(s.newest_anchor().id);
16010 }
16011 if reversed {
16012 s.insert_range(range.end..range.start);
16013 } else {
16014 s.insert_range(range);
16015 }
16016 });
16017 }
16018
16019 pub fn select_next_match_internal(
16020 &mut self,
16021 display_map: &DisplaySnapshot,
16022 replace_newest: bool,
16023 autoscroll: Option<Autoscroll>,
16024 window: &mut Window,
16025 cx: &mut Context<Self>,
16026 ) -> Result<()> {
16027 let buffer = display_map.buffer_snapshot();
16028 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16029 if let Some(mut select_next_state) = self.select_next_state.take() {
16030 let query = &select_next_state.query;
16031 if !select_next_state.done {
16032 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16033 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16034 let mut next_selected_range = None;
16035
16036 let bytes_after_last_selection =
16037 buffer.bytes_in_range(last_selection.end..buffer.len());
16038 let bytes_before_first_selection =
16039 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
16040 let query_matches = query
16041 .stream_find_iter(bytes_after_last_selection)
16042 .map(|result| (last_selection.end, result))
16043 .chain(
16044 query
16045 .stream_find_iter(bytes_before_first_selection)
16046 .map(|result| (MultiBufferOffset(0), result)),
16047 );
16048
16049 for (start_offset, query_match) in query_matches {
16050 let query_match = query_match.unwrap(); // can only fail due to I/O
16051 let offset_range =
16052 start_offset + query_match.start()..start_offset + query_match.end();
16053
16054 if !select_next_state.wordwise
16055 || (!buffer.is_inside_word(offset_range.start, None)
16056 && !buffer.is_inside_word(offset_range.end, None))
16057 {
16058 let idx = selections
16059 .partition_point(|selection| selection.end <= offset_range.start);
16060 let overlaps = selections
16061 .get(idx)
16062 .map_or(false, |selection| selection.start < offset_range.end);
16063
16064 if !overlaps {
16065 next_selected_range = Some(offset_range);
16066 break;
16067 }
16068 }
16069 }
16070
16071 if let Some(next_selected_range) = next_selected_range {
16072 self.select_match_ranges(
16073 next_selected_range,
16074 last_selection.reversed,
16075 replace_newest,
16076 autoscroll,
16077 window,
16078 cx,
16079 );
16080 } else {
16081 select_next_state.done = true;
16082 }
16083 }
16084
16085 self.select_next_state = Some(select_next_state);
16086 } else {
16087 let mut only_carets = true;
16088 let mut same_text_selected = true;
16089 let mut selected_text = None;
16090
16091 let mut selections_iter = selections.iter().peekable();
16092 while let Some(selection) = selections_iter.next() {
16093 if selection.start != selection.end {
16094 only_carets = false;
16095 }
16096
16097 if same_text_selected {
16098 if selected_text.is_none() {
16099 selected_text =
16100 Some(buffer.text_for_range(selection.range()).collect::<String>());
16101 }
16102
16103 if let Some(next_selection) = selections_iter.peek() {
16104 if next_selection.len() == selection.len() {
16105 let next_selected_text = buffer
16106 .text_for_range(next_selection.range())
16107 .collect::<String>();
16108 if Some(next_selected_text) != selected_text {
16109 same_text_selected = false;
16110 selected_text = None;
16111 }
16112 } else {
16113 same_text_selected = false;
16114 selected_text = None;
16115 }
16116 }
16117 }
16118 }
16119
16120 if only_carets {
16121 for selection in &mut selections {
16122 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16123 selection.start = word_range.start;
16124 selection.end = word_range.end;
16125 selection.goal = SelectionGoal::None;
16126 selection.reversed = false;
16127 self.select_match_ranges(
16128 selection.start..selection.end,
16129 selection.reversed,
16130 replace_newest,
16131 autoscroll,
16132 window,
16133 cx,
16134 );
16135 }
16136
16137 if selections.len() == 1 {
16138 let selection = selections
16139 .last()
16140 .expect("ensured that there's only one selection");
16141 let query = buffer
16142 .text_for_range(selection.start..selection.end)
16143 .collect::<String>();
16144 let is_empty = query.is_empty();
16145 let select_state = SelectNextState {
16146 query: self.build_query(&[query], cx)?,
16147 wordwise: true,
16148 done: is_empty,
16149 };
16150 self.select_next_state = Some(select_state);
16151 } else {
16152 self.select_next_state = None;
16153 }
16154 } else if let Some(selected_text) = selected_text {
16155 self.select_next_state = Some(SelectNextState {
16156 query: self.build_query(&[selected_text], cx)?,
16157 wordwise: false,
16158 done: false,
16159 });
16160 self.select_next_match_internal(
16161 display_map,
16162 replace_newest,
16163 autoscroll,
16164 window,
16165 cx,
16166 )?;
16167 }
16168 }
16169 Ok(())
16170 }
16171
16172 pub fn select_all_matches(
16173 &mut self,
16174 _action: &SelectAllMatches,
16175 window: &mut Window,
16176 cx: &mut Context<Self>,
16177 ) -> Result<()> {
16178 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16179
16180 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16181
16182 self.select_next_match_internal(&display_map, false, None, window, cx)?;
16183 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
16184 else {
16185 return Ok(());
16186 };
16187
16188 let mut new_selections = Vec::new();
16189 let initial_selection = self.selections.oldest::<MultiBufferOffset>(&display_map);
16190 let reversed = initial_selection.reversed;
16191 let buffer = display_map.buffer_snapshot();
16192 let query_matches = select_next_state
16193 .query
16194 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
16195
16196 for query_match in query_matches.into_iter() {
16197 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
16198 let offset_range = if reversed {
16199 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
16200 } else {
16201 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
16202 };
16203
16204 let is_partial_word_match = select_next_state.wordwise
16205 && (buffer.is_inside_word(offset_range.start, None)
16206 || buffer.is_inside_word(offset_range.end, None));
16207
16208 let is_initial_selection = MultiBufferOffset(query_match.start())
16209 == initial_selection.start
16210 && MultiBufferOffset(query_match.end()) == initial_selection.end;
16211
16212 if !is_partial_word_match && !is_initial_selection {
16213 new_selections.push(offset_range);
16214 }
16215 }
16216
16217 // Ensure that the initial range is the last selection, as
16218 // `MutableSelectionsCollection::select_ranges` makes the last selection
16219 // the newest selection, which the editor then relies on as the primary
16220 // cursor for scroll targeting. Without this, the last match would then
16221 // be automatically focused when the user started editing the selected
16222 // matches.
16223 let initial_directed_range = if reversed {
16224 initial_selection.end..initial_selection.start
16225 } else {
16226 initial_selection.start..initial_selection.end
16227 };
16228 new_selections.push(initial_directed_range);
16229
16230 select_next_state.done = true;
16231 self.unfold_ranges(&new_selections, false, false, cx);
16232 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16233 selections.select_ranges(new_selections)
16234 });
16235
16236 Ok(())
16237 }
16238
16239 pub fn select_next(
16240 &mut self,
16241 action: &SelectNext,
16242 window: &mut Window,
16243 cx: &mut Context<Self>,
16244 ) -> Result<()> {
16245 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16246 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16247 self.select_next_match_internal(
16248 &display_map,
16249 action.replace_newest,
16250 Some(Autoscroll::newest()),
16251 window,
16252 cx,
16253 )
16254 }
16255
16256 pub fn select_previous(
16257 &mut self,
16258 action: &SelectPrevious,
16259 window: &mut Window,
16260 cx: &mut Context<Self>,
16261 ) -> Result<()> {
16262 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16263 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16264 let buffer = display_map.buffer_snapshot();
16265 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16266 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16267 let query = &select_prev_state.query;
16268 if !select_prev_state.done {
16269 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16270 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16271 let mut next_selected_range = None;
16272 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16273 let bytes_before_last_selection =
16274 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16275 let bytes_after_first_selection =
16276 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16277 let query_matches = query
16278 .stream_find_iter(bytes_before_last_selection)
16279 .map(|result| (last_selection.start, result))
16280 .chain(
16281 query
16282 .stream_find_iter(bytes_after_first_selection)
16283 .map(|result| (buffer.len(), result)),
16284 );
16285 for (end_offset, query_match) in query_matches {
16286 let query_match = query_match.unwrap(); // can only fail due to I/O
16287 let offset_range =
16288 end_offset - query_match.end()..end_offset - query_match.start();
16289
16290 if !select_prev_state.wordwise
16291 || (!buffer.is_inside_word(offset_range.start, None)
16292 && !buffer.is_inside_word(offset_range.end, None))
16293 {
16294 next_selected_range = Some(offset_range);
16295 break;
16296 }
16297 }
16298
16299 if let Some(next_selected_range) = next_selected_range {
16300 self.select_match_ranges(
16301 next_selected_range,
16302 last_selection.reversed,
16303 action.replace_newest,
16304 Some(Autoscroll::newest()),
16305 window,
16306 cx,
16307 );
16308 } else {
16309 select_prev_state.done = true;
16310 }
16311 }
16312
16313 self.select_prev_state = Some(select_prev_state);
16314 } else {
16315 let mut only_carets = true;
16316 let mut same_text_selected = true;
16317 let mut selected_text = None;
16318
16319 let mut selections_iter = selections.iter().peekable();
16320 while let Some(selection) = selections_iter.next() {
16321 if selection.start != selection.end {
16322 only_carets = false;
16323 }
16324
16325 if same_text_selected {
16326 if selected_text.is_none() {
16327 selected_text =
16328 Some(buffer.text_for_range(selection.range()).collect::<String>());
16329 }
16330
16331 if let Some(next_selection) = selections_iter.peek() {
16332 if next_selection.len() == selection.len() {
16333 let next_selected_text = buffer
16334 .text_for_range(next_selection.range())
16335 .collect::<String>();
16336 if Some(next_selected_text) != selected_text {
16337 same_text_selected = false;
16338 selected_text = None;
16339 }
16340 } else {
16341 same_text_selected = false;
16342 selected_text = None;
16343 }
16344 }
16345 }
16346 }
16347
16348 if only_carets {
16349 for selection in &mut selections {
16350 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16351 selection.start = word_range.start;
16352 selection.end = word_range.end;
16353 selection.goal = SelectionGoal::None;
16354 selection.reversed = false;
16355 self.select_match_ranges(
16356 selection.start..selection.end,
16357 selection.reversed,
16358 action.replace_newest,
16359 Some(Autoscroll::newest()),
16360 window,
16361 cx,
16362 );
16363 }
16364 if selections.len() == 1 {
16365 let selection = selections
16366 .last()
16367 .expect("ensured that there's only one selection");
16368 let query = buffer
16369 .text_for_range(selection.start..selection.end)
16370 .collect::<String>();
16371 let is_empty = query.is_empty();
16372 let select_state = SelectNextState {
16373 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16374 wordwise: true,
16375 done: is_empty,
16376 };
16377 self.select_prev_state = Some(select_state);
16378 } else {
16379 self.select_prev_state = None;
16380 }
16381 } else if let Some(selected_text) = selected_text {
16382 self.select_prev_state = Some(SelectNextState {
16383 query: self
16384 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16385 wordwise: false,
16386 done: false,
16387 });
16388 self.select_previous(action, window, cx)?;
16389 }
16390 }
16391 Ok(())
16392 }
16393
16394 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16395 /// setting the case sensitivity based on the global
16396 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16397 /// editor's settings.
16398 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16399 where
16400 I: IntoIterator<Item = P>,
16401 P: AsRef<[u8]>,
16402 {
16403 let case_sensitive = self
16404 .select_next_is_case_sensitive
16405 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16406
16407 let mut builder = AhoCorasickBuilder::new();
16408 builder.ascii_case_insensitive(!case_sensitive);
16409 builder.build(patterns)
16410 }
16411
16412 pub fn find_next_match(
16413 &mut self,
16414 _: &FindNextMatch,
16415 window: &mut Window,
16416 cx: &mut Context<Self>,
16417 ) -> Result<()> {
16418 let selections = self.selections.disjoint_anchors_arc();
16419 match selections.first() {
16420 Some(first) if selections.len() >= 2 => {
16421 self.change_selections(Default::default(), window, cx, |s| {
16422 s.select_ranges([first.range()]);
16423 });
16424 }
16425 _ => self.select_next(
16426 &SelectNext {
16427 replace_newest: true,
16428 },
16429 window,
16430 cx,
16431 )?,
16432 }
16433 Ok(())
16434 }
16435
16436 pub fn find_previous_match(
16437 &mut self,
16438 _: &FindPreviousMatch,
16439 window: &mut Window,
16440 cx: &mut Context<Self>,
16441 ) -> Result<()> {
16442 let selections = self.selections.disjoint_anchors_arc();
16443 match selections.last() {
16444 Some(last) if selections.len() >= 2 => {
16445 self.change_selections(Default::default(), window, cx, |s| {
16446 s.select_ranges([last.range()]);
16447 });
16448 }
16449 _ => self.select_previous(
16450 &SelectPrevious {
16451 replace_newest: true,
16452 },
16453 window,
16454 cx,
16455 )?,
16456 }
16457 Ok(())
16458 }
16459
16460 pub fn toggle_comments(
16461 &mut self,
16462 action: &ToggleComments,
16463 window: &mut Window,
16464 cx: &mut Context<Self>,
16465 ) {
16466 if self.read_only(cx) {
16467 return;
16468 }
16469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16470 let text_layout_details = &self.text_layout_details(window, cx);
16471 self.transact(window, cx, |this, window, cx| {
16472 let mut selections = this
16473 .selections
16474 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16475 let mut edits = Vec::new();
16476 let mut selection_edit_ranges = Vec::new();
16477 let mut last_toggled_row = None;
16478 let snapshot = this.buffer.read(cx).read(cx);
16479 let empty_str: Arc<str> = Arc::default();
16480 let mut suffixes_inserted = Vec::new();
16481 let ignore_indent = action.ignore_indent;
16482
16483 fn comment_prefix_range(
16484 snapshot: &MultiBufferSnapshot,
16485 row: MultiBufferRow,
16486 comment_prefix: &str,
16487 comment_prefix_whitespace: &str,
16488 ignore_indent: bool,
16489 ) -> Range<Point> {
16490 let indent_size = if ignore_indent {
16491 0
16492 } else {
16493 snapshot.indent_size_for_line(row).len
16494 };
16495
16496 let start = Point::new(row.0, indent_size);
16497
16498 let mut line_bytes = snapshot
16499 .bytes_in_range(start..snapshot.max_point())
16500 .flatten()
16501 .copied();
16502
16503 // If this line currently begins with the line comment prefix, then record
16504 // the range containing the prefix.
16505 if line_bytes
16506 .by_ref()
16507 .take(comment_prefix.len())
16508 .eq(comment_prefix.bytes())
16509 {
16510 // Include any whitespace that matches the comment prefix.
16511 let matching_whitespace_len = line_bytes
16512 .zip(comment_prefix_whitespace.bytes())
16513 .take_while(|(a, b)| a == b)
16514 .count() as u32;
16515 let end = Point::new(
16516 start.row,
16517 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16518 );
16519 start..end
16520 } else {
16521 start..start
16522 }
16523 }
16524
16525 fn comment_suffix_range(
16526 snapshot: &MultiBufferSnapshot,
16527 row: MultiBufferRow,
16528 comment_suffix: &str,
16529 comment_suffix_has_leading_space: bool,
16530 ) -> Range<Point> {
16531 let end = Point::new(row.0, snapshot.line_len(row));
16532 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16533
16534 let mut line_end_bytes = snapshot
16535 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16536 .flatten()
16537 .copied();
16538
16539 let leading_space_len = if suffix_start_column > 0
16540 && line_end_bytes.next() == Some(b' ')
16541 && comment_suffix_has_leading_space
16542 {
16543 1
16544 } else {
16545 0
16546 };
16547
16548 // If this line currently begins with the line comment prefix, then record
16549 // the range containing the prefix.
16550 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16551 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16552 start..end
16553 } else {
16554 end..end
16555 }
16556 }
16557
16558 // TODO: Handle selections that cross excerpts
16559 for selection in &mut selections {
16560 let start_column = snapshot
16561 .indent_size_for_line(MultiBufferRow(selection.start.row))
16562 .len;
16563 let language = if let Some(language) =
16564 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16565 {
16566 language
16567 } else {
16568 continue;
16569 };
16570
16571 selection_edit_ranges.clear();
16572
16573 // If multiple selections contain a given row, avoid processing that
16574 // row more than once.
16575 let mut start_row = MultiBufferRow(selection.start.row);
16576 if last_toggled_row == Some(start_row) {
16577 start_row = start_row.next_row();
16578 }
16579 let end_row =
16580 if selection.end.row > selection.start.row && selection.end.column == 0 {
16581 MultiBufferRow(selection.end.row - 1)
16582 } else {
16583 MultiBufferRow(selection.end.row)
16584 };
16585 last_toggled_row = Some(end_row);
16586
16587 if start_row > end_row {
16588 continue;
16589 }
16590
16591 // If the language has line comments, toggle those.
16592 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16593
16594 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16595 if ignore_indent {
16596 full_comment_prefixes = full_comment_prefixes
16597 .into_iter()
16598 .map(|s| Arc::from(s.trim_end()))
16599 .collect();
16600 }
16601
16602 if !full_comment_prefixes.is_empty() {
16603 let first_prefix = full_comment_prefixes
16604 .first()
16605 .expect("prefixes is non-empty");
16606 let prefix_trimmed_lengths = full_comment_prefixes
16607 .iter()
16608 .map(|p| p.trim_end_matches(' ').len())
16609 .collect::<SmallVec<[usize; 4]>>();
16610
16611 let mut all_selection_lines_are_comments = true;
16612
16613 for row in start_row.0..=end_row.0 {
16614 let row = MultiBufferRow(row);
16615 if start_row < end_row && snapshot.is_line_blank(row) {
16616 continue;
16617 }
16618
16619 let prefix_range = full_comment_prefixes
16620 .iter()
16621 .zip(prefix_trimmed_lengths.iter().copied())
16622 .map(|(prefix, trimmed_prefix_len)| {
16623 comment_prefix_range(
16624 snapshot.deref(),
16625 row,
16626 &prefix[..trimmed_prefix_len],
16627 &prefix[trimmed_prefix_len..],
16628 ignore_indent,
16629 )
16630 })
16631 .max_by_key(|range| range.end.column - range.start.column)
16632 .expect("prefixes is non-empty");
16633
16634 if prefix_range.is_empty() {
16635 all_selection_lines_are_comments = false;
16636 }
16637
16638 selection_edit_ranges.push(prefix_range);
16639 }
16640
16641 if all_selection_lines_are_comments {
16642 edits.extend(
16643 selection_edit_ranges
16644 .iter()
16645 .cloned()
16646 .map(|range| (range, empty_str.clone())),
16647 );
16648 } else {
16649 let min_column = selection_edit_ranges
16650 .iter()
16651 .map(|range| range.start.column)
16652 .min()
16653 .unwrap_or(0);
16654 edits.extend(selection_edit_ranges.iter().map(|range| {
16655 let position = Point::new(range.start.row, min_column);
16656 (position..position, first_prefix.clone())
16657 }));
16658 }
16659 } else if let Some(BlockCommentConfig {
16660 start: full_comment_prefix,
16661 end: comment_suffix,
16662 ..
16663 }) = language.block_comment()
16664 {
16665 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16666 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16667 let prefix_range = comment_prefix_range(
16668 snapshot.deref(),
16669 start_row,
16670 comment_prefix,
16671 comment_prefix_whitespace,
16672 ignore_indent,
16673 );
16674 let suffix_range = comment_suffix_range(
16675 snapshot.deref(),
16676 end_row,
16677 comment_suffix.trim_start_matches(' '),
16678 comment_suffix.starts_with(' '),
16679 );
16680
16681 if prefix_range.is_empty() || suffix_range.is_empty() {
16682 edits.push((
16683 prefix_range.start..prefix_range.start,
16684 full_comment_prefix.clone(),
16685 ));
16686 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16687 suffixes_inserted.push((end_row, comment_suffix.len()));
16688 } else {
16689 edits.push((prefix_range, empty_str.clone()));
16690 edits.push((suffix_range, empty_str.clone()));
16691 }
16692 } else {
16693 continue;
16694 }
16695 }
16696
16697 drop(snapshot);
16698 this.buffer.update(cx, |buffer, cx| {
16699 buffer.edit(edits, None, cx);
16700 });
16701
16702 // Adjust selections so that they end before any comment suffixes that
16703 // were inserted.
16704 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16705 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16706 let snapshot = this.buffer.read(cx).read(cx);
16707 for selection in &mut selections {
16708 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16709 match row.cmp(&MultiBufferRow(selection.end.row)) {
16710 Ordering::Less => {
16711 suffixes_inserted.next();
16712 continue;
16713 }
16714 Ordering::Greater => break,
16715 Ordering::Equal => {
16716 if selection.end.column == snapshot.line_len(row) {
16717 if selection.is_empty() {
16718 selection.start.column -= suffix_len as u32;
16719 }
16720 selection.end.column -= suffix_len as u32;
16721 }
16722 break;
16723 }
16724 }
16725 }
16726 }
16727
16728 drop(snapshot);
16729 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16730
16731 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16732 let selections_on_single_row = selections.windows(2).all(|selections| {
16733 selections[0].start.row == selections[1].start.row
16734 && selections[0].end.row == selections[1].end.row
16735 && selections[0].start.row == selections[0].end.row
16736 });
16737 let selections_selecting = selections
16738 .iter()
16739 .any(|selection| selection.start != selection.end);
16740 let advance_downwards = action.advance_downwards
16741 && selections_on_single_row
16742 && !selections_selecting
16743 && !matches!(this.mode, EditorMode::SingleLine);
16744
16745 if advance_downwards {
16746 let snapshot = this.buffer.read(cx).snapshot(cx);
16747
16748 this.change_selections(Default::default(), window, cx, |s| {
16749 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16750 let mut point = display_point.to_point(display_snapshot);
16751 point.row += 1;
16752 point = snapshot.clip_point(point, Bias::Left);
16753 let display_point = point.to_display_point(display_snapshot);
16754 let goal = SelectionGoal::HorizontalPosition(
16755 display_snapshot
16756 .x_for_display_point(display_point, text_layout_details)
16757 .into(),
16758 );
16759 (display_point, goal)
16760 })
16761 });
16762 }
16763 });
16764 }
16765
16766 pub fn select_enclosing_symbol(
16767 &mut self,
16768 _: &SelectEnclosingSymbol,
16769 window: &mut Window,
16770 cx: &mut Context<Self>,
16771 ) {
16772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16773
16774 let buffer = self.buffer.read(cx).snapshot(cx);
16775 let old_selections = self
16776 .selections
16777 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16778 .into_boxed_slice();
16779
16780 fn update_selection(
16781 selection: &Selection<MultiBufferOffset>,
16782 buffer_snap: &MultiBufferSnapshot,
16783 ) -> Option<Selection<MultiBufferOffset>> {
16784 let cursor = selection.head();
16785 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16786 for symbol in symbols.iter().rev() {
16787 let start = symbol.range.start.to_offset(buffer_snap);
16788 let end = symbol.range.end.to_offset(buffer_snap);
16789 let new_range = start..end;
16790 if start < selection.start || end > selection.end {
16791 return Some(Selection {
16792 id: selection.id,
16793 start: new_range.start,
16794 end: new_range.end,
16795 goal: SelectionGoal::None,
16796 reversed: selection.reversed,
16797 });
16798 }
16799 }
16800 None
16801 }
16802
16803 let mut selected_larger_symbol = false;
16804 let new_selections = old_selections
16805 .iter()
16806 .map(|selection| match update_selection(selection, &buffer) {
16807 Some(new_selection) => {
16808 if new_selection.range() != selection.range() {
16809 selected_larger_symbol = true;
16810 }
16811 new_selection
16812 }
16813 None => selection.clone(),
16814 })
16815 .collect::<Vec<_>>();
16816
16817 if selected_larger_symbol {
16818 self.change_selections(Default::default(), window, cx, |s| {
16819 s.select(new_selections);
16820 });
16821 }
16822 }
16823
16824 pub fn select_larger_syntax_node(
16825 &mut self,
16826 _: &SelectLargerSyntaxNode,
16827 window: &mut Window,
16828 cx: &mut Context<Self>,
16829 ) {
16830 let Some(visible_row_count) = self.visible_row_count() else {
16831 return;
16832 };
16833 let old_selections: Box<[_]> = self
16834 .selections
16835 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16836 .into();
16837 if old_selections.is_empty() {
16838 return;
16839 }
16840
16841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16842
16843 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16844 let buffer = self.buffer.read(cx).snapshot(cx);
16845
16846 let mut selected_larger_node = false;
16847 let mut new_selections = old_selections
16848 .iter()
16849 .map(|selection| {
16850 let old_range = selection.start..selection.end;
16851
16852 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16853 // manually select word at selection
16854 if ["string_content", "inline"].contains(&node.kind()) {
16855 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16856 // ignore if word is already selected
16857 if !word_range.is_empty() && old_range != word_range {
16858 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16859 // only select word if start and end point belongs to same word
16860 if word_range == last_word_range {
16861 selected_larger_node = true;
16862 return Selection {
16863 id: selection.id,
16864 start: word_range.start,
16865 end: word_range.end,
16866 goal: SelectionGoal::None,
16867 reversed: selection.reversed,
16868 };
16869 }
16870 }
16871 }
16872 }
16873
16874 let mut new_range = old_range.clone();
16875 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16876 new_range = range;
16877 if !node.is_named() {
16878 continue;
16879 }
16880 if !display_map.intersects_fold(new_range.start)
16881 && !display_map.intersects_fold(new_range.end)
16882 {
16883 break;
16884 }
16885 }
16886
16887 selected_larger_node |= new_range != old_range;
16888 Selection {
16889 id: selection.id,
16890 start: new_range.start,
16891 end: new_range.end,
16892 goal: SelectionGoal::None,
16893 reversed: selection.reversed,
16894 }
16895 })
16896 .collect::<Vec<_>>();
16897
16898 if !selected_larger_node {
16899 return; // don't put this call in the history
16900 }
16901
16902 // scroll based on transformation done to the last selection created by the user
16903 let (last_old, last_new) = old_selections
16904 .last()
16905 .zip(new_selections.last().cloned())
16906 .expect("old_selections isn't empty");
16907
16908 let is_selection_reversed = if new_selections.len() == 1 {
16909 let should_be_reversed = last_old.start != last_new.start;
16910 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
16911 should_be_reversed
16912 } else {
16913 last_new.reversed
16914 };
16915
16916 if selected_larger_node {
16917 self.select_syntax_node_history.disable_clearing = true;
16918 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16919 s.select(new_selections.clone());
16920 });
16921 self.select_syntax_node_history.disable_clearing = false;
16922 }
16923
16924 let start_row = last_new.start.to_display_point(&display_map).row().0;
16925 let end_row = last_new.end.to_display_point(&display_map).row().0;
16926 let selection_height = end_row - start_row + 1;
16927 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16928
16929 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16930 let scroll_behavior = if fits_on_the_screen {
16931 self.request_autoscroll(Autoscroll::fit(), cx);
16932 SelectSyntaxNodeScrollBehavior::FitSelection
16933 } else if is_selection_reversed {
16934 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16935 SelectSyntaxNodeScrollBehavior::CursorTop
16936 } else {
16937 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16938 SelectSyntaxNodeScrollBehavior::CursorBottom
16939 };
16940
16941 let old_selections: Box<[Selection<Anchor>]> = old_selections
16942 .iter()
16943 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16944 .collect();
16945 self.select_syntax_node_history.push((
16946 old_selections,
16947 scroll_behavior,
16948 is_selection_reversed,
16949 ));
16950 }
16951
16952 pub fn select_smaller_syntax_node(
16953 &mut self,
16954 _: &SelectSmallerSyntaxNode,
16955 window: &mut Window,
16956 cx: &mut Context<Self>,
16957 ) {
16958 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16959
16960 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16961 self.select_syntax_node_history.pop()
16962 {
16963 if let Some(selection) = selections.last_mut() {
16964 selection.reversed = is_selection_reversed;
16965 }
16966
16967 let snapshot = self.buffer.read(cx).snapshot(cx);
16968 let selections: Vec<Selection<MultiBufferOffset>> = selections
16969 .iter()
16970 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16971 .collect();
16972
16973 self.select_syntax_node_history.disable_clearing = true;
16974 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16975 s.select(selections);
16976 });
16977 self.select_syntax_node_history.disable_clearing = false;
16978
16979 match scroll_behavior {
16980 SelectSyntaxNodeScrollBehavior::CursorTop => {
16981 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16982 }
16983 SelectSyntaxNodeScrollBehavior::FitSelection => {
16984 self.request_autoscroll(Autoscroll::fit(), cx);
16985 }
16986 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16987 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16988 }
16989 }
16990 }
16991 }
16992
16993 pub fn unwrap_syntax_node(
16994 &mut self,
16995 _: &UnwrapSyntaxNode,
16996 window: &mut Window,
16997 cx: &mut Context<Self>,
16998 ) {
16999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17000
17001 let buffer = self.buffer.read(cx).snapshot(cx);
17002 let selections = self
17003 .selections
17004 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17005 .into_iter()
17006 // subtracting the offset requires sorting
17007 .sorted_by_key(|i| i.start);
17008
17009 let full_edits = selections
17010 .into_iter()
17011 .filter_map(|selection| {
17012 let child = if selection.is_empty()
17013 && let Some((_, ancestor_range)) =
17014 buffer.syntax_ancestor(selection.start..selection.end)
17015 {
17016 ancestor_range
17017 } else {
17018 selection.range()
17019 };
17020
17021 let mut parent = child.clone();
17022 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
17023 parent = ancestor_range;
17024 if parent.start < child.start || parent.end > child.end {
17025 break;
17026 }
17027 }
17028
17029 if parent == child {
17030 return None;
17031 }
17032 let text = buffer.text_for_range(child).collect::<String>();
17033 Some((selection.id, parent, text))
17034 })
17035 .collect::<Vec<_>>();
17036 if full_edits.is_empty() {
17037 return;
17038 }
17039
17040 self.transact(window, cx, |this, window, cx| {
17041 this.buffer.update(cx, |buffer, cx| {
17042 buffer.edit(
17043 full_edits
17044 .iter()
17045 .map(|(_, p, t)| (p.clone(), t.clone()))
17046 .collect::<Vec<_>>(),
17047 None,
17048 cx,
17049 );
17050 });
17051 this.change_selections(Default::default(), window, cx, |s| {
17052 let mut offset = 0;
17053 let mut selections = vec![];
17054 for (id, parent, text) in full_edits {
17055 let start = parent.start - offset;
17056 offset += (parent.end - parent.start) - text.len();
17057 selections.push(Selection {
17058 id,
17059 start,
17060 end: start + text.len(),
17061 reversed: false,
17062 goal: Default::default(),
17063 });
17064 }
17065 s.select(selections);
17066 });
17067 });
17068 }
17069
17070 pub fn select_next_syntax_node(
17071 &mut self,
17072 _: &SelectNextSyntaxNode,
17073 window: &mut Window,
17074 cx: &mut Context<Self>,
17075 ) {
17076 let old_selections = self.selections.all_anchors(&self.display_snapshot(cx));
17077 if old_selections.is_empty() {
17078 return;
17079 }
17080
17081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17082
17083 let buffer = self.buffer.read(cx).snapshot(cx);
17084 let mut selected_sibling = false;
17085
17086 let new_selections = old_selections
17087 .iter()
17088 .map(|selection| {
17089 let old_range =
17090 selection.start.to_offset(&buffer)..selection.end.to_offset(&buffer);
17091 if let Some(results) = buffer.map_excerpt_ranges(
17092 old_range,
17093 |buf, _excerpt_range, input_buffer_range| {
17094 let Some(node) = buf.syntax_next_sibling(input_buffer_range) else {
17095 return Vec::new();
17096 };
17097 vec![(
17098 BufferOffset(node.byte_range().start)
17099 ..BufferOffset(node.byte_range().end),
17100 (),
17101 )]
17102 },
17103 ) && let [(new_range, _)] = results.as_slice()
17104 {
17105 selected_sibling = true;
17106 let new_range =
17107 buffer.anchor_after(new_range.start)..buffer.anchor_before(new_range.end);
17108 Selection {
17109 id: selection.id,
17110 start: new_range.start,
17111 end: new_range.end,
17112 goal: SelectionGoal::None,
17113 reversed: selection.reversed,
17114 }
17115 } else {
17116 selection.clone()
17117 }
17118 })
17119 .collect::<Vec<_>>();
17120
17121 if selected_sibling {
17122 self.change_selections(
17123 SelectionEffects::scroll(Autoscroll::fit()),
17124 window,
17125 cx,
17126 |s| {
17127 s.select(new_selections);
17128 },
17129 );
17130 }
17131 }
17132
17133 pub fn select_prev_syntax_node(
17134 &mut self,
17135 _: &SelectPreviousSyntaxNode,
17136 window: &mut Window,
17137 cx: &mut Context<Self>,
17138 ) {
17139 let old_selections: Arc<[_]> = self.selections.all_anchors(&self.display_snapshot(cx));
17140
17141 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17142
17143 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
17144 let mut selected_sibling = false;
17145
17146 let new_selections = old_selections
17147 .iter()
17148 .map(|selection| {
17149 let old_range = selection.start.to_offset(&multibuffer_snapshot)
17150 ..selection.end.to_offset(&multibuffer_snapshot);
17151 if let Some(results) = multibuffer_snapshot.map_excerpt_ranges(
17152 old_range,
17153 |buf, _excerpt_range, input_buffer_range| {
17154 let Some(node) = buf.syntax_prev_sibling(input_buffer_range) else {
17155 return Vec::new();
17156 };
17157 vec![(
17158 BufferOffset(node.byte_range().start)
17159 ..BufferOffset(node.byte_range().end),
17160 (),
17161 )]
17162 },
17163 ) && let [(new_range, _)] = results.as_slice()
17164 {
17165 selected_sibling = true;
17166 let new_range = multibuffer_snapshot.anchor_after(new_range.start)
17167 ..multibuffer_snapshot.anchor_before(new_range.end);
17168 Selection {
17169 id: selection.id,
17170 start: new_range.start,
17171 end: new_range.end,
17172 goal: SelectionGoal::None,
17173 reversed: selection.reversed,
17174 }
17175 } else {
17176 selection.clone()
17177 }
17178 })
17179 .collect::<Vec<_>>();
17180
17181 if selected_sibling {
17182 self.change_selections(
17183 SelectionEffects::scroll(Autoscroll::fit()),
17184 window,
17185 cx,
17186 |s| {
17187 s.select(new_selections);
17188 },
17189 );
17190 }
17191 }
17192
17193 pub fn move_to_start_of_larger_syntax_node(
17194 &mut self,
17195 _: &MoveToStartOfLargerSyntaxNode,
17196 window: &mut Window,
17197 cx: &mut Context<Self>,
17198 ) {
17199 self.move_cursors_to_syntax_nodes(window, cx, false);
17200 }
17201
17202 pub fn move_to_end_of_larger_syntax_node(
17203 &mut self,
17204 _: &MoveToEndOfLargerSyntaxNode,
17205 window: &mut Window,
17206 cx: &mut Context<Self>,
17207 ) {
17208 self.move_cursors_to_syntax_nodes(window, cx, true);
17209 }
17210
17211 fn find_syntax_node_boundary(
17212 &self,
17213 selection_pos: MultiBufferOffset,
17214 move_to_end: bool,
17215 display_map: &DisplaySnapshot,
17216 buffer: &MultiBufferSnapshot,
17217 ) -> MultiBufferOffset {
17218 let old_range = selection_pos..selection_pos;
17219 let mut new_pos = selection_pos;
17220 let mut search_range = old_range;
17221 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17222 search_range = range.clone();
17223 if !node.is_named()
17224 || display_map.intersects_fold(range.start)
17225 || display_map.intersects_fold(range.end)
17226 // If cursor is already at the end of the syntax node, continue searching
17227 || (move_to_end && range.end == selection_pos)
17228 // If cursor is already at the start of the syntax node, continue searching
17229 || (!move_to_end && range.start == selection_pos)
17230 {
17231 continue;
17232 }
17233
17234 // If we found a string_content node, find the largest parent that is still string_content
17235 // Enables us to skip to the end of strings without taking multiple steps inside the string
17236 let (_, final_range) = if node.kind() == "string_content" {
17237 let mut current_node = node;
17238 let mut current_range = range;
17239 while let Some((parent, parent_range)) =
17240 buffer.syntax_ancestor(current_range.clone())
17241 {
17242 if parent.kind() == "string_content" {
17243 current_node = parent;
17244 current_range = parent_range;
17245 } else {
17246 break;
17247 }
17248 }
17249
17250 (current_node, current_range)
17251 } else {
17252 (node, range)
17253 };
17254
17255 new_pos = if move_to_end {
17256 final_range.end
17257 } else {
17258 final_range.start
17259 };
17260
17261 break;
17262 }
17263
17264 new_pos
17265 }
17266
17267 fn move_cursors_to_syntax_nodes(
17268 &mut self,
17269 window: &mut Window,
17270 cx: &mut Context<Self>,
17271 move_to_end: bool,
17272 ) -> bool {
17273 let old_selections: Box<[_]> = self
17274 .selections
17275 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17276 .into();
17277 if old_selections.is_empty() {
17278 return false;
17279 }
17280
17281 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17282
17283 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17284 let buffer = self.buffer.read(cx).snapshot(cx);
17285
17286 let mut any_cursor_moved = false;
17287 let new_selections = old_selections
17288 .iter()
17289 .map(|selection| {
17290 if !selection.is_empty() {
17291 return selection.clone();
17292 }
17293
17294 let selection_pos = selection.head();
17295 let new_pos = self.find_syntax_node_boundary(
17296 selection_pos,
17297 move_to_end,
17298 &display_map,
17299 &buffer,
17300 );
17301
17302 any_cursor_moved |= new_pos != selection_pos;
17303
17304 Selection {
17305 id: selection.id,
17306 start: new_pos,
17307 end: new_pos,
17308 goal: SelectionGoal::None,
17309 reversed: false,
17310 }
17311 })
17312 .collect::<Vec<_>>();
17313
17314 self.change_selections(Default::default(), window, cx, |s| {
17315 s.select(new_selections);
17316 });
17317 self.request_autoscroll(Autoscroll::newest(), cx);
17318
17319 any_cursor_moved
17320 }
17321
17322 pub fn select_to_start_of_larger_syntax_node(
17323 &mut self,
17324 _: &SelectToStartOfLargerSyntaxNode,
17325 window: &mut Window,
17326 cx: &mut Context<Self>,
17327 ) {
17328 self.select_to_syntax_nodes(window, cx, false);
17329 }
17330
17331 pub fn select_to_end_of_larger_syntax_node(
17332 &mut self,
17333 _: &SelectToEndOfLargerSyntaxNode,
17334 window: &mut Window,
17335 cx: &mut Context<Self>,
17336 ) {
17337 self.select_to_syntax_nodes(window, cx, true);
17338 }
17339
17340 fn select_to_syntax_nodes(
17341 &mut self,
17342 window: &mut Window,
17343 cx: &mut Context<Self>,
17344 move_to_end: bool,
17345 ) {
17346 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17347
17348 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17349 let buffer = self.buffer.read(cx).snapshot(cx);
17350 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17351
17352 let new_selections = old_selections
17353 .iter()
17354 .map(|selection| {
17355 let new_pos = self.find_syntax_node_boundary(
17356 selection.head(),
17357 move_to_end,
17358 &display_map,
17359 &buffer,
17360 );
17361
17362 let mut new_selection = selection.clone();
17363 new_selection.set_head(new_pos, SelectionGoal::None);
17364 new_selection
17365 })
17366 .collect::<Vec<_>>();
17367
17368 self.change_selections(Default::default(), window, cx, |s| {
17369 s.select(new_selections);
17370 });
17371 }
17372
17373 pub fn move_to_enclosing_bracket(
17374 &mut self,
17375 _: &MoveToEnclosingBracket,
17376 window: &mut Window,
17377 cx: &mut Context<Self>,
17378 ) {
17379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17380 self.change_selections(Default::default(), window, cx, |s| {
17381 s.move_offsets_with(&mut |snapshot, selection| {
17382 let Some(enclosing_bracket_ranges) =
17383 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17384 else {
17385 return;
17386 };
17387
17388 let mut best_length = usize::MAX;
17389 let mut best_inside = false;
17390 let mut best_in_bracket_range = false;
17391 let mut best_destination = None;
17392 for (open, close) in enclosing_bracket_ranges {
17393 let close = close.to_inclusive();
17394 let length = *close.end() - open.start;
17395 let inside = selection.start >= open.end && selection.end <= *close.start();
17396 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17397 || close.contains(&selection.head());
17398
17399 // If best is next to a bracket and current isn't, skip
17400 if !in_bracket_range && best_in_bracket_range {
17401 continue;
17402 }
17403
17404 // Prefer smaller lengths unless best is inside and current isn't
17405 if length > best_length && (best_inside || !inside) {
17406 continue;
17407 }
17408
17409 best_length = length;
17410 best_inside = inside;
17411 best_in_bracket_range = in_bracket_range;
17412 best_destination = Some(
17413 if close.contains(&selection.start) && close.contains(&selection.end) {
17414 if inside { open.end } else { open.start }
17415 } else if inside {
17416 *close.start()
17417 } else {
17418 *close.end()
17419 },
17420 );
17421 }
17422
17423 if let Some(destination) = best_destination {
17424 selection.collapse_to(destination, SelectionGoal::None);
17425 }
17426 })
17427 });
17428 }
17429
17430 pub fn undo_selection(
17431 &mut self,
17432 _: &UndoSelection,
17433 window: &mut Window,
17434 cx: &mut Context<Self>,
17435 ) {
17436 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17437 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17438 self.selection_history.mode = SelectionHistoryMode::Undoing;
17439 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17440 this.end_selection(window, cx);
17441 this.change_selections(
17442 SelectionEffects::scroll(Autoscroll::newest()),
17443 window,
17444 cx,
17445 |s| s.select_anchors(entry.selections.to_vec()),
17446 );
17447 });
17448 self.selection_history.mode = SelectionHistoryMode::Normal;
17449
17450 self.select_next_state = entry.select_next_state;
17451 self.select_prev_state = entry.select_prev_state;
17452 self.add_selections_state = entry.add_selections_state;
17453 }
17454 }
17455
17456 pub fn redo_selection(
17457 &mut self,
17458 _: &RedoSelection,
17459 window: &mut Window,
17460 cx: &mut Context<Self>,
17461 ) {
17462 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17463 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17464 self.selection_history.mode = SelectionHistoryMode::Redoing;
17465 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17466 this.end_selection(window, cx);
17467 this.change_selections(
17468 SelectionEffects::scroll(Autoscroll::newest()),
17469 window,
17470 cx,
17471 |s| s.select_anchors(entry.selections.to_vec()),
17472 );
17473 });
17474 self.selection_history.mode = SelectionHistoryMode::Normal;
17475
17476 self.select_next_state = entry.select_next_state;
17477 self.select_prev_state = entry.select_prev_state;
17478 self.add_selections_state = entry.add_selections_state;
17479 }
17480 }
17481
17482 pub fn expand_excerpts(
17483 &mut self,
17484 action: &ExpandExcerpts,
17485 _: &mut Window,
17486 cx: &mut Context<Self>,
17487 ) {
17488 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17489 }
17490
17491 pub fn expand_excerpts_down(
17492 &mut self,
17493 action: &ExpandExcerptsDown,
17494 _: &mut Window,
17495 cx: &mut Context<Self>,
17496 ) {
17497 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17498 }
17499
17500 pub fn expand_excerpts_up(
17501 &mut self,
17502 action: &ExpandExcerptsUp,
17503 _: &mut Window,
17504 cx: &mut Context<Self>,
17505 ) {
17506 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17507 }
17508
17509 pub fn expand_excerpts_for_direction(
17510 &mut self,
17511 lines: u32,
17512 direction: ExpandExcerptDirection,
17513 cx: &mut Context<Self>,
17514 ) {
17515 let selections = self.selections.disjoint_anchors_arc();
17516
17517 let lines = if lines == 0 {
17518 EditorSettings::get_global(cx).expand_excerpt_lines
17519 } else {
17520 lines
17521 };
17522
17523 let snapshot = self.buffer.read(cx).snapshot(cx);
17524 let excerpt_anchors = selections
17525 .iter()
17526 .flat_map(|selection| {
17527 snapshot
17528 .range_to_buffer_ranges(selection.range())
17529 .into_iter()
17530 .filter_map(|(buffer_snapshot, range, _)| {
17531 snapshot.anchor_in_excerpt(buffer_snapshot.anchor_after(range.start))
17532 })
17533 })
17534 .collect::<Vec<_>>();
17535
17536 if self.delegate_expand_excerpts {
17537 cx.emit(EditorEvent::ExpandExcerptsRequested {
17538 excerpt_anchors,
17539 lines,
17540 direction,
17541 });
17542 return;
17543 }
17544
17545 self.buffer.update(cx, |buffer, cx| {
17546 buffer.expand_excerpts(excerpt_anchors, lines, direction, cx)
17547 })
17548 }
17549
17550 pub(crate) fn expand_excerpt(
17551 &mut self,
17552 excerpt_anchor: Anchor,
17553 direction: ExpandExcerptDirection,
17554 window: &mut Window,
17555 cx: &mut Context<Self>,
17556 ) {
17557 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17558
17559 if self.delegate_expand_excerpts {
17560 cx.emit(EditorEvent::ExpandExcerptsRequested {
17561 excerpt_anchors: vec![excerpt_anchor],
17562 lines: lines_to_expand,
17563 direction,
17564 });
17565 return;
17566 }
17567
17568 let current_scroll_position = self.scroll_position(cx);
17569 let mut scroll = None;
17570
17571 if direction == ExpandExcerptDirection::Down {
17572 let multi_buffer = self.buffer.read(cx);
17573 let snapshot = multi_buffer.snapshot(cx);
17574 if let Some((buffer_snapshot, excerpt_range)) =
17575 snapshot.excerpt_containing(excerpt_anchor..excerpt_anchor)
17576 {
17577 let excerpt_end_row =
17578 Point::from_anchor(&excerpt_range.context.end, &buffer_snapshot).row;
17579 let last_row = buffer_snapshot.max_point().row;
17580 let lines_below = last_row.saturating_sub(excerpt_end_row);
17581 if lines_below >= lines_to_expand {
17582 scroll = Some(
17583 current_scroll_position
17584 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17585 );
17586 }
17587 }
17588 }
17589 if direction == ExpandExcerptDirection::Up
17590 && self
17591 .buffer
17592 .read(cx)
17593 .snapshot(cx)
17594 .excerpt_before(excerpt_anchor)
17595 .is_none()
17596 {
17597 scroll = Some(current_scroll_position);
17598 }
17599
17600 self.buffer.update(cx, |buffer, cx| {
17601 buffer.expand_excerpts([excerpt_anchor], lines_to_expand, direction, cx)
17602 });
17603
17604 if let Some(new_scroll_position) = scroll {
17605 self.set_scroll_position(new_scroll_position, window, cx);
17606 }
17607 }
17608
17609 pub fn go_to_singleton_buffer_point(
17610 &mut self,
17611 point: Point,
17612 window: &mut Window,
17613 cx: &mut Context<Self>,
17614 ) {
17615 self.go_to_singleton_buffer_range(point..point, window, cx);
17616 }
17617
17618 pub fn go_to_singleton_buffer_range(
17619 &mut self,
17620 range: Range<Point>,
17621 window: &mut Window,
17622 cx: &mut Context<Self>,
17623 ) {
17624 let multibuffer = self.buffer().read(cx);
17625 if !multibuffer.is_singleton() {
17626 return;
17627 };
17628 let anchor_range = range.to_anchors(&multibuffer.snapshot(cx));
17629 self.change_selections(
17630 SelectionEffects::default().nav_history(true),
17631 window,
17632 cx,
17633 |s| s.select_anchor_ranges([anchor_range]),
17634 );
17635 }
17636
17637 pub fn go_to_diagnostic(
17638 &mut self,
17639 action: &GoToDiagnostic,
17640 window: &mut Window,
17641 cx: &mut Context<Self>,
17642 ) {
17643 if !self.diagnostics_enabled() {
17644 return;
17645 }
17646 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17647 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17648 }
17649
17650 pub fn go_to_prev_diagnostic(
17651 &mut self,
17652 action: &GoToPreviousDiagnostic,
17653 window: &mut Window,
17654 cx: &mut Context<Self>,
17655 ) {
17656 if !self.diagnostics_enabled() {
17657 return;
17658 }
17659 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17660 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17661 }
17662
17663 pub fn go_to_diagnostic_impl(
17664 &mut self,
17665 direction: Direction,
17666 severity: GoToDiagnosticSeverityFilter,
17667 window: &mut Window,
17668 cx: &mut Context<Self>,
17669 ) {
17670 let buffer = self.buffer.read(cx).snapshot(cx);
17671 let selection = self
17672 .selections
17673 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17674
17675 let mut active_group_id = None;
17676 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17677 && active_group.active_range.start.to_offset(&buffer) == selection.start
17678 {
17679 active_group_id = Some(active_group.group_id);
17680 }
17681
17682 fn filtered<'a>(
17683 severity: GoToDiagnosticSeverityFilter,
17684 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17685 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17686 diagnostics
17687 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17688 .filter(|entry| entry.range.start != entry.range.end)
17689 .filter(|entry| !entry.diagnostic.is_unnecessary)
17690 }
17691
17692 let before = filtered(
17693 severity,
17694 buffer
17695 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17696 .filter(|entry| entry.range.start <= selection.start),
17697 );
17698 let after = filtered(
17699 severity,
17700 buffer
17701 .diagnostics_in_range(selection.start..buffer.len())
17702 .filter(|entry| entry.range.start >= selection.start),
17703 );
17704
17705 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17706 if direction == Direction::Prev {
17707 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17708 {
17709 for diagnostic in prev_diagnostics.into_iter().rev() {
17710 if diagnostic.range.start != selection.start
17711 || active_group_id
17712 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17713 {
17714 found = Some(diagnostic);
17715 break 'outer;
17716 }
17717 }
17718 }
17719 } else {
17720 for diagnostic in after.chain(before) {
17721 if diagnostic.range.start != selection.start
17722 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17723 {
17724 found = Some(diagnostic);
17725 break;
17726 }
17727 }
17728 }
17729 let Some(next_diagnostic) = found else {
17730 return;
17731 };
17732
17733 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17734 let Some((buffer_anchor, _)) = buffer.anchor_to_buffer_anchor(next_diagnostic_start) else {
17735 return;
17736 };
17737 let buffer_id = buffer_anchor.buffer_id;
17738 let snapshot = self.snapshot(window, cx);
17739 if snapshot.intersects_fold(next_diagnostic.range.start) {
17740 self.unfold_ranges(
17741 std::slice::from_ref(&next_diagnostic.range),
17742 true,
17743 false,
17744 cx,
17745 );
17746 }
17747 self.change_selections(Default::default(), window, cx, |s| {
17748 s.select_ranges(vec![
17749 next_diagnostic.range.start..next_diagnostic.range.start,
17750 ])
17751 });
17752 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17753 self.refresh_edit_prediction(false, true, window, cx);
17754 }
17755
17756 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17757 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17758 let snapshot = self.snapshot(window, cx);
17759 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17760 self.go_to_hunk_before_or_after_position(
17761 &snapshot,
17762 selection.head(),
17763 Direction::Next,
17764 true,
17765 window,
17766 cx,
17767 );
17768 }
17769
17770 pub fn go_to_hunk_before_or_after_position(
17771 &mut self,
17772 snapshot: &EditorSnapshot,
17773 position: Point,
17774 direction: Direction,
17775 wrap_around: bool,
17776 window: &mut Window,
17777 cx: &mut Context<Editor>,
17778 ) {
17779 let row = if direction == Direction::Next {
17780 self.hunk_after_position(snapshot, position, wrap_around)
17781 .map(|hunk| hunk.row_range.start)
17782 } else {
17783 self.hunk_before_position(snapshot, position, wrap_around)
17784 };
17785
17786 if let Some(row) = row {
17787 let destination = Point::new(row.0, 0);
17788 let autoscroll = Autoscroll::center();
17789
17790 self.unfold_ranges(&[destination..destination], false, false, cx);
17791 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17792 s.select_ranges([destination..destination]);
17793 });
17794 }
17795 }
17796
17797 fn hunk_after_position(
17798 &mut self,
17799 snapshot: &EditorSnapshot,
17800 position: Point,
17801 wrap_around: bool,
17802 ) -> Option<MultiBufferDiffHunk> {
17803 let result = snapshot
17804 .buffer_snapshot()
17805 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17806 .find(|hunk| hunk.row_range.start.0 > position.row);
17807
17808 if wrap_around {
17809 result.or_else(|| {
17810 snapshot
17811 .buffer_snapshot()
17812 .diff_hunks_in_range(Point::zero()..position)
17813 .find(|hunk| hunk.row_range.end.0 < position.row)
17814 })
17815 } else {
17816 result
17817 }
17818 }
17819
17820 fn go_to_prev_hunk(
17821 &mut self,
17822 _: &GoToPreviousHunk,
17823 window: &mut Window,
17824 cx: &mut Context<Self>,
17825 ) {
17826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17827 let snapshot = self.snapshot(window, cx);
17828 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17829 self.go_to_hunk_before_or_after_position(
17830 &snapshot,
17831 selection.head(),
17832 Direction::Prev,
17833 true,
17834 window,
17835 cx,
17836 );
17837 }
17838
17839 fn hunk_before_position(
17840 &mut self,
17841 snapshot: &EditorSnapshot,
17842 position: Point,
17843 wrap_around: bool,
17844 ) -> Option<MultiBufferRow> {
17845 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17846
17847 if wrap_around {
17848 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17849 } else {
17850 result
17851 }
17852 }
17853
17854 fn go_to_next_change(
17855 &mut self,
17856 _: &GoToNextChange,
17857 window: &mut Window,
17858 cx: &mut Context<Self>,
17859 ) {
17860 if let Some(selections) = self
17861 .change_list
17862 .next_change(1, Direction::Next)
17863 .map(|s| s.to_vec())
17864 {
17865 self.change_selections(Default::default(), window, cx, |s| {
17866 let map = s.display_snapshot();
17867 s.select_display_ranges(selections.iter().map(|a| {
17868 let point = a.to_display_point(&map);
17869 point..point
17870 }))
17871 })
17872 }
17873 }
17874
17875 fn go_to_previous_change(
17876 &mut self,
17877 _: &GoToPreviousChange,
17878 window: &mut Window,
17879 cx: &mut Context<Self>,
17880 ) {
17881 if let Some(selections) = self
17882 .change_list
17883 .next_change(1, Direction::Prev)
17884 .map(|s| s.to_vec())
17885 {
17886 self.change_selections(Default::default(), window, cx, |s| {
17887 let map = s.display_snapshot();
17888 s.select_display_ranges(selections.iter().map(|a| {
17889 let point = a.to_display_point(&map);
17890 point..point
17891 }))
17892 })
17893 }
17894 }
17895
17896 pub fn go_to_next_document_highlight(
17897 &mut self,
17898 _: &GoToNextDocumentHighlight,
17899 window: &mut Window,
17900 cx: &mut Context<Self>,
17901 ) {
17902 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17903 }
17904
17905 pub fn go_to_prev_document_highlight(
17906 &mut self,
17907 _: &GoToPreviousDocumentHighlight,
17908 window: &mut Window,
17909 cx: &mut Context<Self>,
17910 ) {
17911 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17912 }
17913
17914 pub fn go_to_document_highlight_before_or_after_position(
17915 &mut self,
17916 direction: Direction,
17917 window: &mut Window,
17918 cx: &mut Context<Editor>,
17919 ) {
17920 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17921 let snapshot = self.snapshot(window, cx);
17922 let buffer = &snapshot.buffer_snapshot();
17923 let position = self
17924 .selections
17925 .newest::<Point>(&snapshot.display_snapshot)
17926 .head();
17927 let anchor_position = buffer.anchor_after(position);
17928
17929 // Get all document highlights (both read and write)
17930 let mut all_highlights = Vec::new();
17931
17932 if let Some((_, read_highlights)) = self
17933 .background_highlights
17934 .get(&HighlightKey::DocumentHighlightRead)
17935 {
17936 all_highlights.extend(read_highlights.iter());
17937 }
17938
17939 if let Some((_, write_highlights)) = self
17940 .background_highlights
17941 .get(&HighlightKey::DocumentHighlightWrite)
17942 {
17943 all_highlights.extend(write_highlights.iter());
17944 }
17945
17946 if all_highlights.is_empty() {
17947 return;
17948 }
17949
17950 // Sort highlights by position
17951 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17952
17953 let target_highlight = match direction {
17954 Direction::Next => {
17955 // Find the first highlight after the current position
17956 all_highlights
17957 .iter()
17958 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17959 }
17960 Direction::Prev => {
17961 // Find the last highlight before the current position
17962 all_highlights
17963 .iter()
17964 .rev()
17965 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17966 }
17967 };
17968
17969 if let Some(highlight) = target_highlight {
17970 let destination = highlight.start.to_point(buffer);
17971 let autoscroll = Autoscroll::center();
17972
17973 self.unfold_ranges(&[destination..destination], false, false, cx);
17974 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17975 s.select_ranges([destination..destination]);
17976 });
17977 }
17978 }
17979
17980 fn go_to_line<T: 'static>(
17981 &mut self,
17982 position: Anchor,
17983 highlight_color: Option<Hsla>,
17984 window: &mut Window,
17985 cx: &mut Context<Self>,
17986 ) {
17987 let snapshot = self.snapshot(window, cx).display_snapshot;
17988 let position = position.to_point(&snapshot.buffer_snapshot());
17989 let start = snapshot
17990 .buffer_snapshot()
17991 .clip_point(Point::new(position.row, 0), Bias::Left);
17992 let end = start + Point::new(1, 0);
17993 let start = snapshot.buffer_snapshot().anchor_before(start);
17994 let end = snapshot.buffer_snapshot().anchor_before(end);
17995
17996 self.highlight_rows::<T>(
17997 start..end,
17998 highlight_color
17999 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
18000 Default::default(),
18001 cx,
18002 );
18003
18004 if self.buffer.read(cx).is_singleton() {
18005 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
18006 }
18007 }
18008
18009 pub fn go_to_definition(
18010 &mut self,
18011 _: &GoToDefinition,
18012 window: &mut Window,
18013 cx: &mut Context<Self>,
18014 ) -> Task<Result<Navigated>> {
18015 let definition =
18016 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
18017 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
18018 cx.spawn_in(window, async move |editor, cx| {
18019 if definition.await? == Navigated::Yes {
18020 return Ok(Navigated::Yes);
18021 }
18022 match fallback_strategy {
18023 GoToDefinitionFallback::None => Ok(Navigated::No),
18024 GoToDefinitionFallback::FindAllReferences => {
18025 match editor.update_in(cx, |editor, window, cx| {
18026 editor.find_all_references(&FindAllReferences::default(), window, cx)
18027 })? {
18028 Some(references) => references.await,
18029 None => Ok(Navigated::No),
18030 }
18031 }
18032 }
18033 })
18034 }
18035
18036 pub fn go_to_declaration(
18037 &mut self,
18038 _: &GoToDeclaration,
18039 window: &mut Window,
18040 cx: &mut Context<Self>,
18041 ) -> Task<Result<Navigated>> {
18042 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
18043 }
18044
18045 pub fn go_to_declaration_split(
18046 &mut self,
18047 _: &GoToDeclaration,
18048 window: &mut Window,
18049 cx: &mut Context<Self>,
18050 ) -> Task<Result<Navigated>> {
18051 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
18052 }
18053
18054 pub fn go_to_implementation(
18055 &mut self,
18056 _: &GoToImplementation,
18057 window: &mut Window,
18058 cx: &mut Context<Self>,
18059 ) -> Task<Result<Navigated>> {
18060 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
18061 }
18062
18063 pub fn go_to_implementation_split(
18064 &mut self,
18065 _: &GoToImplementationSplit,
18066 window: &mut Window,
18067 cx: &mut Context<Self>,
18068 ) -> Task<Result<Navigated>> {
18069 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
18070 }
18071
18072 pub fn go_to_type_definition(
18073 &mut self,
18074 _: &GoToTypeDefinition,
18075 window: &mut Window,
18076 cx: &mut Context<Self>,
18077 ) -> Task<Result<Navigated>> {
18078 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
18079 }
18080
18081 pub fn go_to_definition_split(
18082 &mut self,
18083 _: &GoToDefinitionSplit,
18084 window: &mut Window,
18085 cx: &mut Context<Self>,
18086 ) -> Task<Result<Navigated>> {
18087 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
18088 }
18089
18090 pub fn go_to_type_definition_split(
18091 &mut self,
18092 _: &GoToTypeDefinitionSplit,
18093 window: &mut Window,
18094 cx: &mut Context<Self>,
18095 ) -> Task<Result<Navigated>> {
18096 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18097 }
18098
18099 fn go_to_definition_of_kind(
18100 &mut self,
18101 kind: GotoDefinitionKind,
18102 split: bool,
18103 window: &mut Window,
18104 cx: &mut Context<Self>,
18105 ) -> Task<Result<Navigated>> {
18106 let Some(provider) = self.semantics_provider.clone() else {
18107 return Task::ready(Ok(Navigated::No));
18108 };
18109 let head = self
18110 .selections
18111 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18112 .head();
18113 let buffer = self.buffer.read(cx);
18114 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18115 return Task::ready(Ok(Navigated::No));
18116 };
18117 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18118 return Task::ready(Ok(Navigated::No));
18119 };
18120
18121 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18122
18123 cx.spawn_in(window, async move |editor, cx| {
18124 let Some(definitions) = definitions.await? else {
18125 return Ok(Navigated::No);
18126 };
18127 let navigated = editor
18128 .update_in(cx, |editor, window, cx| {
18129 editor.navigate_to_hover_links(
18130 Some(kind),
18131 definitions
18132 .into_iter()
18133 .filter(|location| {
18134 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18135 })
18136 .map(HoverLink::Text)
18137 .collect::<Vec<_>>(),
18138 nav_entry,
18139 split,
18140 window,
18141 cx,
18142 )
18143 })?
18144 .await?;
18145 anyhow::Ok(navigated)
18146 })
18147 }
18148
18149 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18150 let selection = self.selections.newest_anchor();
18151 let head = selection.head();
18152 let tail = selection.tail();
18153
18154 let Some((buffer, start_position)) =
18155 self.buffer.read(cx).text_anchor_for_position(head, cx)
18156 else {
18157 return;
18158 };
18159
18160 let end_position = if head != tail {
18161 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18162 return;
18163 };
18164 Some(pos)
18165 } else {
18166 None
18167 };
18168
18169 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18170 let url = if let Some(end_pos) = end_position {
18171 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18172 } else {
18173 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18174 };
18175
18176 if let Some(url) = url {
18177 cx.update(|window, cx| {
18178 if parse_zed_link(&url, cx).is_some() {
18179 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18180 } else {
18181 cx.open_url(&url);
18182 }
18183 })?;
18184 }
18185
18186 anyhow::Ok(())
18187 });
18188
18189 url_finder.detach();
18190 }
18191
18192 pub fn open_selected_filename(
18193 &mut self,
18194 _: &OpenSelectedFilename,
18195 window: &mut Window,
18196 cx: &mut Context<Self>,
18197 ) {
18198 let Some(workspace) = self.workspace() else {
18199 return;
18200 };
18201
18202 let position = self.selections.newest_anchor().head();
18203
18204 let Some((buffer, buffer_position)) =
18205 self.buffer.read(cx).text_anchor_for_position(position, cx)
18206 else {
18207 return;
18208 };
18209
18210 let project = self.project.clone();
18211
18212 cx.spawn_in(window, async move |_, cx| {
18213 let result = find_file(&buffer, project, buffer_position, cx).await;
18214
18215 if let Some((_, path)) = result {
18216 workspace
18217 .update_in(cx, |workspace, window, cx| {
18218 workspace.open_resolved_path(path, window, cx)
18219 })?
18220 .await?;
18221 }
18222 anyhow::Ok(())
18223 })
18224 .detach();
18225 }
18226
18227 pub(crate) fn navigate_to_hover_links(
18228 &mut self,
18229 kind: Option<GotoDefinitionKind>,
18230 definitions: Vec<HoverLink>,
18231 origin: Option<NavigationEntry>,
18232 split: bool,
18233 window: &mut Window,
18234 cx: &mut Context<Editor>,
18235 ) -> Task<Result<Navigated>> {
18236 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18237 let mut first_url_or_file = None;
18238 let definitions: Vec<_> = definitions
18239 .into_iter()
18240 .filter_map(|def| match def {
18241 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18242 HoverLink::InlayHint(lsp_location, server_id) => {
18243 let computation =
18244 self.compute_target_location(lsp_location, server_id, window, cx);
18245 Some(cx.background_spawn(computation))
18246 }
18247 HoverLink::Url(url) => {
18248 first_url_or_file = Some(Either::Left(url));
18249 None
18250 }
18251 HoverLink::File(path) => {
18252 first_url_or_file = Some(Either::Right(path));
18253 None
18254 }
18255 })
18256 .collect();
18257
18258 let workspace = self.workspace();
18259
18260 let excerpt_context_lines = multi_buffer::excerpt_context_lines(cx);
18261 cx.spawn_in(window, async move |editor, cx| {
18262 let locations: Vec<Location> = future::join_all(definitions)
18263 .await
18264 .into_iter()
18265 .filter_map(|location| location.transpose())
18266 .collect::<Result<_>>()
18267 .context("location tasks")?;
18268 let mut locations = cx.update(|_, cx| {
18269 locations
18270 .into_iter()
18271 .map(|location| {
18272 let buffer = location.buffer.read(cx);
18273 (location.buffer, location.range.to_point(buffer))
18274 })
18275 .into_group_map()
18276 })?;
18277 let mut num_locations = 0;
18278 for ranges in locations.values_mut() {
18279 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18280 ranges.dedup();
18281 // Merge overlapping or contained ranges. After sorting by
18282 // (start, Reverse(end)), we can merge in a single pass:
18283 // if the next range starts before the current one ends,
18284 // extend the current range's end if needed.
18285 let mut i = 0;
18286 while i + 1 < ranges.len() {
18287 if ranges[i + 1].start <= ranges[i].end {
18288 let merged_end = ranges[i].end.max(ranges[i + 1].end);
18289 ranges[i].end = merged_end;
18290 ranges.remove(i + 1);
18291 } else {
18292 i += 1;
18293 }
18294 }
18295 let fits_in_one_excerpt = ranges
18296 .iter()
18297 .tuple_windows()
18298 .all(|(a, b)| b.start.row - a.end.row <= 2 * excerpt_context_lines);
18299 num_locations += if fits_in_one_excerpt { 1 } else { ranges.len() };
18300 }
18301
18302 if num_locations > 1 {
18303 let tab_kind = match kind {
18304 Some(GotoDefinitionKind::Implementation) => "Implementations",
18305 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18306 Some(GotoDefinitionKind::Declaration) => "Declarations",
18307 Some(GotoDefinitionKind::Type) => "Types",
18308 };
18309 let title = editor
18310 .update_in(cx, |_, _, cx| {
18311 let target = locations
18312 .iter()
18313 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18314 .map(|(buffer, location)| {
18315 buffer
18316 .read(cx)
18317 .text_for_range(location.clone())
18318 .collect::<String>()
18319 })
18320 .filter(|text| !text.contains('\n'))
18321 .unique()
18322 .take(3)
18323 .join(", ");
18324 if target.is_empty() {
18325 tab_kind.to_owned()
18326 } else {
18327 format!("{tab_kind} for {target}")
18328 }
18329 })
18330 .context("buffer title")?;
18331
18332 let Some(workspace) = workspace else {
18333 return Ok(Navigated::No);
18334 };
18335
18336 let opened = workspace
18337 .update_in(cx, |workspace, window, cx| {
18338 let allow_preview = PreviewTabsSettings::get_global(cx)
18339 .enable_preview_multibuffer_from_code_navigation;
18340 if let Some((target_editor, target_pane)) =
18341 Self::open_locations_in_multibuffer(
18342 workspace,
18343 locations,
18344 title,
18345 split,
18346 allow_preview,
18347 MultibufferSelectionMode::First,
18348 window,
18349 cx,
18350 )
18351 {
18352 // We create our own nav history instead of using
18353 // `target_editor.nav_history` because `nav_history`
18354 // seems to be populated asynchronously when an item
18355 // is added to a pane
18356 let mut nav_history = target_pane
18357 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18358 target_editor.update(cx, |editor, cx| {
18359 let nav_data = editor
18360 .navigation_data(editor.selections.newest_anchor().head(), cx);
18361 let target =
18362 Some(nav_history.navigation_entry(Some(
18363 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18364 )));
18365 nav_history.push_tag(origin, target);
18366 })
18367 }
18368 })
18369 .is_ok();
18370
18371 anyhow::Ok(Navigated::from_bool(opened))
18372 } else if num_locations == 0 {
18373 // If there is one url or file, open it directly
18374 match first_url_or_file {
18375 Some(Either::Left(url)) => {
18376 cx.update(|window, cx| {
18377 if parse_zed_link(&url, cx).is_some() {
18378 window
18379 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18380 } else {
18381 cx.open_url(&url);
18382 }
18383 })?;
18384 Ok(Navigated::Yes)
18385 }
18386 Some(Either::Right(path)) => {
18387 // TODO(andrew): respect preview tab settings
18388 // `enable_keep_preview_on_code_navigation` and
18389 // `enable_preview_file_from_code_navigation`
18390 let Some(workspace) = workspace else {
18391 return Ok(Navigated::No);
18392 };
18393 workspace
18394 .update_in(cx, |workspace, window, cx| {
18395 workspace.open_resolved_path(path, window, cx)
18396 })?
18397 .await?;
18398 Ok(Navigated::Yes)
18399 }
18400 None => Ok(Navigated::No),
18401 }
18402 } else {
18403 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18404
18405 editor.update_in(cx, |editor, window, cx| {
18406 let target_ranges = target_ranges
18407 .into_iter()
18408 .map(|r| editor.range_for_match(&r))
18409 .map(collapse_multiline_range)
18410 .collect::<Vec<_>>();
18411 if !split
18412 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18413 {
18414 let multibuffer = editor.buffer.read(cx);
18415 let target_ranges = target_ranges
18416 .into_iter()
18417 .filter_map(|r| {
18418 let start = multibuffer.buffer_point_to_anchor(
18419 &target_buffer,
18420 r.start,
18421 cx,
18422 )?;
18423 let end = multibuffer.buffer_point_to_anchor(
18424 &target_buffer,
18425 r.end,
18426 cx,
18427 )?;
18428 Some(start..end)
18429 })
18430 .collect::<Vec<_>>();
18431 if target_ranges.is_empty() {
18432 return Navigated::No;
18433 }
18434
18435 editor.change_selections(
18436 SelectionEffects::default().nav_history(true),
18437 window,
18438 cx,
18439 |s| s.select_anchor_ranges(target_ranges),
18440 );
18441
18442 let target =
18443 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18444 if let Some(mut nav_history) = editor.nav_history.clone() {
18445 nav_history.push_tag(origin, target);
18446 }
18447 } else {
18448 let Some(workspace) = workspace else {
18449 return Navigated::No;
18450 };
18451 let pane = workspace.read(cx).active_pane().clone();
18452 window.defer(cx, move |window, cx| {
18453 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18454 workspace.update(cx, |workspace, cx| {
18455 let pane = if split {
18456 workspace.adjacent_pane(window, cx)
18457 } else {
18458 workspace.active_pane().clone()
18459 };
18460
18461 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18462 let keep_old_preview = preview_tabs_settings
18463 .enable_keep_preview_on_code_navigation;
18464 let allow_new_preview = preview_tabs_settings
18465 .enable_preview_file_from_code_navigation;
18466
18467 let editor = workspace.open_project_item(
18468 pane.clone(),
18469 target_buffer.clone(),
18470 true,
18471 true,
18472 keep_old_preview,
18473 allow_new_preview,
18474 window,
18475 cx,
18476 );
18477 (editor, pane)
18478 });
18479 // We create our own nav history instead of using
18480 // `target_editor.nav_history` because `nav_history`
18481 // seems to be populated asynchronously when an item
18482 // is added to a pane
18483 let mut nav_history = target_pane
18484 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18485 target_editor.update(cx, |target_editor, cx| {
18486 // When selecting a definition in a different buffer, disable the nav history
18487 // to avoid creating a history entry at the previous cursor location.
18488 pane.update(cx, |pane, _| pane.disable_history());
18489
18490 let multibuffer = target_editor.buffer.read(cx);
18491 let Some(target_buffer) = multibuffer.as_singleton() else {
18492 return Navigated::No;
18493 };
18494 let target_ranges = target_ranges
18495 .into_iter()
18496 .filter_map(|r| {
18497 let start = multibuffer.buffer_point_to_anchor(
18498 &target_buffer,
18499 r.start,
18500 cx,
18501 )?;
18502 let end = multibuffer.buffer_point_to_anchor(
18503 &target_buffer,
18504 r.end,
18505 cx,
18506 )?;
18507 Some(start..end)
18508 })
18509 .collect::<Vec<_>>();
18510 if target_ranges.is_empty() {
18511 return Navigated::No;
18512 }
18513
18514 target_editor.change_selections(
18515 SelectionEffects::default().nav_history(true),
18516 window,
18517 cx,
18518 |s| s.select_anchor_ranges(target_ranges),
18519 );
18520
18521 let nav_data = target_editor.navigation_data(
18522 target_editor.selections.newest_anchor().head(),
18523 cx,
18524 );
18525 let target =
18526 Some(nav_history.navigation_entry(Some(
18527 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18528 )));
18529 nav_history.push_tag(origin, target);
18530 pane.update(cx, |pane, _| pane.enable_history());
18531 Navigated::Yes
18532 });
18533 });
18534 }
18535 Navigated::Yes
18536 })
18537 }
18538 })
18539 }
18540
18541 fn compute_target_location(
18542 &self,
18543 lsp_location: lsp::Location,
18544 server_id: LanguageServerId,
18545 window: &mut Window,
18546 cx: &mut Context<Self>,
18547 ) -> Task<anyhow::Result<Option<Location>>> {
18548 let Some(project) = self.project.clone() else {
18549 return Task::ready(Ok(None));
18550 };
18551
18552 cx.spawn_in(window, async move |editor, cx| {
18553 let location_task = editor.update(cx, |_, cx| {
18554 project.update(cx, |project, cx| {
18555 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18556 })
18557 })?;
18558 let location = Some({
18559 let target_buffer_handle = location_task.await.context("open local buffer")?;
18560 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18561 let target_start = target_buffer
18562 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18563 let target_end = target_buffer
18564 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18565 target_buffer.anchor_after(target_start)
18566 ..target_buffer.anchor_before(target_end)
18567 });
18568 Location {
18569 buffer: target_buffer_handle,
18570 range,
18571 }
18572 });
18573 Ok(location)
18574 })
18575 }
18576
18577 fn go_to_next_reference(
18578 &mut self,
18579 _: &GoToNextReference,
18580 window: &mut Window,
18581 cx: &mut Context<Self>,
18582 ) {
18583 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18584 if let Some(task) = task {
18585 task.detach();
18586 };
18587 }
18588
18589 fn go_to_prev_reference(
18590 &mut self,
18591 _: &GoToPreviousReference,
18592 window: &mut Window,
18593 cx: &mut Context<Self>,
18594 ) {
18595 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18596 if let Some(task) = task {
18597 task.detach();
18598 };
18599 }
18600
18601 fn go_to_symbol_by_offset(
18602 &mut self,
18603 window: &mut Window,
18604 cx: &mut Context<Self>,
18605 offset: i8,
18606 ) -> Task<Result<()>> {
18607 let editor_snapshot = self.snapshot(window, cx);
18608
18609 // We don't care about multi-buffer symbols
18610 if !editor_snapshot.is_singleton() {
18611 return Task::ready(Ok(()));
18612 }
18613
18614 let cursor_offset = self
18615 .selections
18616 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18617 .head();
18618
18619 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18620 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18621 let buffer = ed.buffer.read(cx).as_singleton()?;
18622 Some(buffer.read(cx).remote_id())
18623 }) else {
18624 return Ok(());
18625 };
18626
18627 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18628 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18629
18630 let multi_snapshot = editor_snapshot.buffer();
18631 let buffer_range = |range: &Range<_>| {
18632 Some(
18633 multi_snapshot
18634 .buffer_anchor_range_to_anchor_range(range.clone())?
18635 .to_offset(multi_snapshot),
18636 )
18637 };
18638
18639 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18640 let current_idx = outline_items
18641 .iter()
18642 .enumerate()
18643 .filter_map(|(idx, item)| {
18644 // Find the closest outline item by distance between outline text and cursor location
18645 let source_range = buffer_range(&item.source_range_for_text)?;
18646 let distance_to_closest_endpoint = cmp::min(
18647 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18648 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18649 );
18650
18651 let item_towards_offset =
18652 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18653 == (offset as isize).signum();
18654
18655 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18656
18657 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18658 // we should not already be within the outline's source range. We then pick the closest outline
18659 // item.
18660 (item_towards_offset && !source_range_contains_cursor)
18661 .then_some((distance_to_closest_endpoint, idx))
18662 })
18663 .min()
18664 .map(|(_, idx)| idx);
18665
18666 let Some(idx) = current_idx else {
18667 return;
18668 };
18669
18670 let Some(range) = buffer_range(&outline_items[idx].source_range_for_text) else {
18671 return;
18672 };
18673 let selection = [range.start..range.start];
18674
18675 let _ = editor
18676 .update(acx, |editor, ecx| {
18677 editor.change_selections(
18678 SelectionEffects::scroll(Autoscroll::newest()),
18679 window,
18680 ecx,
18681 |s| s.select_ranges(selection),
18682 );
18683 })
18684 .ok();
18685 })?;
18686
18687 Ok(())
18688 })
18689 }
18690
18691 fn go_to_next_symbol(
18692 &mut self,
18693 _: &GoToNextSymbol,
18694 window: &mut Window,
18695 cx: &mut Context<Self>,
18696 ) {
18697 self.go_to_symbol_by_offset(window, cx, 1).detach();
18698 }
18699
18700 fn go_to_previous_symbol(
18701 &mut self,
18702 _: &GoToPreviousSymbol,
18703 window: &mut Window,
18704 cx: &mut Context<Self>,
18705 ) {
18706 self.go_to_symbol_by_offset(window, cx, -1).detach();
18707 }
18708
18709 pub fn go_to_reference_before_or_after_position(
18710 &mut self,
18711 direction: Direction,
18712 count: usize,
18713 window: &mut Window,
18714 cx: &mut Context<Self>,
18715 ) -> Option<Task<Result<()>>> {
18716 let selection = self.selections.newest_anchor();
18717 let head = selection.head();
18718
18719 let multi_buffer = self.buffer.read(cx);
18720
18721 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18722 let workspace = self.workspace()?;
18723 let project = workspace.read(cx).project().clone();
18724 let references =
18725 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18726 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18727 let Some(locations) = references.await? else {
18728 return Ok(());
18729 };
18730
18731 if locations.is_empty() {
18732 // totally normal - the cursor may be on something which is not
18733 // a symbol (e.g. a keyword)
18734 log::info!("no references found under cursor");
18735 return Ok(());
18736 }
18737
18738 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18739
18740 let (locations, current_location_index) =
18741 multi_buffer.update(cx, |multi_buffer, cx| {
18742 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18743 let mut locations = locations
18744 .into_iter()
18745 .filter_map(|loc| {
18746 let start = multi_buffer_snapshot.anchor_in_excerpt(loc.range.start)?;
18747 let end = multi_buffer_snapshot.anchor_in_excerpt(loc.range.end)?;
18748 Some(start..end)
18749 })
18750 .collect::<Vec<_>>();
18751 // There is an O(n) implementation, but given this list will be
18752 // small (usually <100 items), the extra O(log(n)) factor isn't
18753 // worth the (surprisingly large amount of) extra complexity.
18754 locations
18755 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18756
18757 let head_offset = head.to_offset(&multi_buffer_snapshot);
18758
18759 let current_location_index = locations.iter().position(|loc| {
18760 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18761 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18762 });
18763
18764 (locations, current_location_index)
18765 });
18766
18767 let Some(current_location_index) = current_location_index else {
18768 // This indicates something has gone wrong, because we already
18769 // handle the "no references" case above
18770 log::error!(
18771 "failed to find current reference under cursor. Total references: {}",
18772 locations.len()
18773 );
18774 return Ok(());
18775 };
18776
18777 let destination_location_index = match direction {
18778 Direction::Next => (current_location_index + count) % locations.len(),
18779 Direction::Prev => {
18780 (current_location_index + locations.len() - count % locations.len())
18781 % locations.len()
18782 }
18783 };
18784
18785 // TODO(cameron): is this needed?
18786 // the thinking is to avoid "jumping to the current location" (avoid
18787 // polluting "jumplist" in vim terms)
18788 if current_location_index == destination_location_index {
18789 return Ok(());
18790 }
18791
18792 let Range { start, end } = locations[destination_location_index];
18793
18794 editor.update_in(cx, |editor, window, cx| {
18795 let effects = SelectionEffects::default();
18796
18797 editor.unfold_ranges(&[start..end], false, false, cx);
18798 editor.change_selections(effects, window, cx, |s| {
18799 s.select_ranges([start..start]);
18800 });
18801 })?;
18802
18803 Ok(())
18804 }))
18805 }
18806
18807 pub fn find_all_references(
18808 &mut self,
18809 action: &FindAllReferences,
18810 window: &mut Window,
18811 cx: &mut Context<Self>,
18812 ) -> Option<Task<Result<Navigated>>> {
18813 let always_open_multibuffer = action.always_open_multibuffer;
18814 let selection = self.selections.newest_anchor();
18815 let multi_buffer = self.buffer.read(cx);
18816 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18817 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18818 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18819 let head = selection_offset.head();
18820
18821 let head_anchor = multi_buffer_snapshot.anchor_at(
18822 head,
18823 if head < selection_offset.tail() {
18824 Bias::Right
18825 } else {
18826 Bias::Left
18827 },
18828 );
18829
18830 match self
18831 .find_all_references_task_sources
18832 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18833 {
18834 Ok(_) => {
18835 log::info!(
18836 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18837 );
18838 return None;
18839 }
18840 Err(i) => {
18841 self.find_all_references_task_sources.insert(i, head_anchor);
18842 }
18843 }
18844
18845 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18846 let workspace = self.workspace()?;
18847 let project = workspace.read(cx).project().clone();
18848 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18849 Some(cx.spawn_in(window, async move |editor, cx| {
18850 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18851 if let Ok(i) = editor
18852 .find_all_references_task_sources
18853 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18854 {
18855 editor.find_all_references_task_sources.remove(i);
18856 }
18857 });
18858
18859 let Some(locations) = references.await? else {
18860 return anyhow::Ok(Navigated::No);
18861 };
18862 let mut locations = cx.update(|_, cx| {
18863 locations
18864 .into_iter()
18865 .map(|location| {
18866 let buffer = location.buffer.read(cx);
18867 (location.buffer, location.range.to_point(buffer))
18868 })
18869 // if special-casing the single-match case, remove ranges
18870 // that intersect current selection
18871 .filter(|(location_buffer, location)| {
18872 if always_open_multibuffer || &buffer != location_buffer {
18873 return true;
18874 }
18875
18876 !location.contains_inclusive(&selection_point.range())
18877 })
18878 .into_group_map()
18879 })?;
18880 if locations.is_empty() {
18881 return anyhow::Ok(Navigated::No);
18882 }
18883 for ranges in locations.values_mut() {
18884 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18885 ranges.dedup();
18886 }
18887 let mut num_locations = 0;
18888 for ranges in locations.values_mut() {
18889 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18890 ranges.dedup();
18891 num_locations += ranges.len();
18892 }
18893
18894 if num_locations == 1 && !always_open_multibuffer {
18895 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18896 let target_range = target_ranges.first().unwrap().clone();
18897
18898 return editor.update_in(cx, |editor, window, cx| {
18899 let range = target_range.to_point(target_buffer.read(cx));
18900 let range = editor.range_for_match(&range);
18901 let range = range.start..range.start;
18902
18903 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18904 editor.go_to_singleton_buffer_range(range, window, cx);
18905 } else {
18906 let pane = workspace.read(cx).active_pane().clone();
18907 window.defer(cx, move |window, cx| {
18908 let target_editor: Entity<Self> =
18909 workspace.update(cx, |workspace, cx| {
18910 let pane = workspace.active_pane().clone();
18911
18912 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18913 let keep_old_preview = preview_tabs_settings
18914 .enable_keep_preview_on_code_navigation;
18915 let allow_new_preview = preview_tabs_settings
18916 .enable_preview_file_from_code_navigation;
18917
18918 workspace.open_project_item(
18919 pane,
18920 target_buffer.clone(),
18921 true,
18922 true,
18923 keep_old_preview,
18924 allow_new_preview,
18925 window,
18926 cx,
18927 )
18928 });
18929 target_editor.update(cx, |target_editor, cx| {
18930 // When selecting a definition in a different buffer, disable the nav history
18931 // to avoid creating a history entry at the previous cursor location.
18932 pane.update(cx, |pane, _| pane.disable_history());
18933 target_editor.go_to_singleton_buffer_range(range, window, cx);
18934 pane.update(cx, |pane, _| pane.enable_history());
18935 });
18936 });
18937 }
18938 Navigated::No
18939 });
18940 }
18941
18942 workspace.update_in(cx, |workspace, window, cx| {
18943 let target = locations
18944 .iter()
18945 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18946 .map(|(buffer, location)| {
18947 buffer
18948 .read(cx)
18949 .text_for_range(location.clone())
18950 .collect::<String>()
18951 })
18952 .filter(|text| !text.contains('\n'))
18953 .unique()
18954 .take(3)
18955 .join(", ");
18956 let title = if target.is_empty() {
18957 "References".to_owned()
18958 } else {
18959 format!("References to {target}")
18960 };
18961 let allow_preview = PreviewTabsSettings::get_global(cx)
18962 .enable_preview_multibuffer_from_code_navigation;
18963 Self::open_locations_in_multibuffer(
18964 workspace,
18965 locations,
18966 title,
18967 false,
18968 allow_preview,
18969 MultibufferSelectionMode::First,
18970 window,
18971 cx,
18972 );
18973 Navigated::Yes
18974 })
18975 }))
18976 }
18977
18978 /// Opens a multibuffer with the given project locations in it.
18979 pub fn open_locations_in_multibuffer(
18980 workspace: &mut Workspace,
18981 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18982 title: String,
18983 split: bool,
18984 allow_preview: bool,
18985 multibuffer_selection_mode: MultibufferSelectionMode,
18986 window: &mut Window,
18987 cx: &mut Context<Workspace>,
18988 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18989 if locations.is_empty() {
18990 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18991 return None;
18992 }
18993
18994 let capability = workspace.project().read(cx).capability();
18995 let mut ranges = <Vec<Range<Anchor>>>::new();
18996
18997 // a key to find existing multibuffer editors with the same set of locations
18998 // to prevent us from opening more and more multibuffer tabs for searches and the like
18999 let mut key = (title.clone(), vec![]);
19000 let excerpt_buffer = cx.new(|cx| {
19001 let key = &mut key.1;
19002 let mut multibuffer = MultiBuffer::new(capability);
19003 for (buffer, mut ranges_for_buffer) in locations {
19004 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
19005 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
19006 multibuffer.set_excerpts_for_path(
19007 PathKey::for_buffer(&buffer, cx),
19008 buffer.clone(),
19009 ranges_for_buffer.clone(),
19010 multibuffer_context_lines(cx),
19011 cx,
19012 );
19013 let snapshot = multibuffer.snapshot(cx);
19014 let buffer_snapshot = buffer.read(cx).snapshot();
19015 ranges.extend(ranges_for_buffer.into_iter().filter_map(|range| {
19016 let text_range = buffer_snapshot.anchor_range_inside(range);
19017 let start = snapshot.anchor_in_buffer(text_range.start)?;
19018 let end = snapshot.anchor_in_buffer(text_range.end)?;
19019 Some(start..end)
19020 }))
19021 }
19022
19023 multibuffer.with_title(title)
19024 });
19025 let existing = workspace.active_pane().update(cx, |pane, cx| {
19026 pane.items()
19027 .filter_map(|item| item.downcast::<Editor>())
19028 .find(|editor| {
19029 editor
19030 .read(cx)
19031 .lookup_key
19032 .as_ref()
19033 .and_then(|it| {
19034 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
19035 })
19036 .is_some_and(|it| *it == key)
19037 })
19038 });
19039 let was_existing = existing.is_some();
19040 let editor = existing.unwrap_or_else(|| {
19041 cx.new(|cx| {
19042 let mut editor = Editor::for_multibuffer(
19043 excerpt_buffer,
19044 Some(workspace.project().clone()),
19045 window,
19046 cx,
19047 );
19048 editor.lookup_key = Some(Box::new(key));
19049 editor
19050 })
19051 });
19052 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
19053 MultibufferSelectionMode::First => {
19054 if let Some(first_range) = ranges.first() {
19055 editor.change_selections(
19056 SelectionEffects::no_scroll(),
19057 window,
19058 cx,
19059 |selections| {
19060 selections.clear_disjoint();
19061 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
19062 },
19063 );
19064 }
19065 editor.highlight_background(
19066 HighlightKey::Editor,
19067 &ranges,
19068 |_, theme| theme.colors().editor_highlighted_line_background,
19069 cx,
19070 );
19071 }
19072 MultibufferSelectionMode::All => {
19073 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19074 selections.clear_disjoint();
19075 selections.select_anchor_ranges(ranges);
19076 });
19077 }
19078 });
19079
19080 let item = Box::new(editor.clone());
19081
19082 let pane = if split {
19083 workspace.adjacent_pane(window, cx)
19084 } else {
19085 workspace.active_pane().clone()
19086 };
19087 let activate_pane = split;
19088
19089 let mut destination_index = None;
19090 pane.update(cx, |pane, cx| {
19091 if allow_preview && !was_existing {
19092 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
19093 }
19094 if was_existing && !allow_preview {
19095 pane.unpreview_item_if_preview(item.item_id());
19096 }
19097 pane.add_item(item, activate_pane, true, destination_index, window, cx);
19098 });
19099
19100 Some((editor, pane))
19101 }
19102
19103 pub fn rename(
19104 &mut self,
19105 _: &Rename,
19106 window: &mut Window,
19107 cx: &mut Context<Self>,
19108 ) -> Option<Task<Result<()>>> {
19109 use language::ToOffset as _;
19110
19111 let provider = self.semantics_provider.clone()?;
19112 let selection = self.selections.newest_anchor().clone();
19113 let (cursor_buffer, cursor_buffer_position) = self
19114 .buffer
19115 .read(cx)
19116 .text_anchor_for_position(selection.head(), cx)?;
19117 let (tail_buffer, cursor_buffer_position_end) = self
19118 .buffer
19119 .read(cx)
19120 .text_anchor_for_position(selection.tail(), cx)?;
19121 if tail_buffer != cursor_buffer {
19122 return None;
19123 }
19124
19125 let snapshot = cursor_buffer.read(cx).snapshot();
19126 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
19127 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
19128 let prepare_rename = provider.range_for_rename(&cursor_buffer, cursor_buffer_position, cx);
19129 drop(snapshot);
19130
19131 Some(cx.spawn_in(window, async move |this, cx| {
19132 let rename_range = prepare_rename.await?;
19133 if let Some(rename_range) = rename_range {
19134 this.update_in(cx, |this, window, cx| {
19135 let snapshot = cursor_buffer.read(cx).snapshot();
19136 let rename_buffer_range = rename_range.to_offset(&snapshot);
19137 let cursor_offset_in_rename_range =
19138 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
19139 let cursor_offset_in_rename_range_end =
19140 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
19141
19142 this.take_rename(false, window, cx);
19143 let buffer = this.buffer.read(cx).read(cx);
19144 let cursor_offset = selection.head().to_offset(&buffer);
19145 let rename_start =
19146 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
19147 let rename_end = rename_start + rename_buffer_range.len();
19148 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
19149 let mut old_highlight_id = None;
19150 let old_name: Arc<str> = buffer
19151 .chunks(rename_start..rename_end, true)
19152 .map(|chunk| {
19153 if old_highlight_id.is_none() {
19154 old_highlight_id = chunk.syntax_highlight_id;
19155 }
19156 chunk.text
19157 })
19158 .collect::<String>()
19159 .into();
19160
19161 drop(buffer);
19162
19163 // Position the selection in the rename editor so that it matches the current selection.
19164 this.show_local_selections = false;
19165 let rename_editor = cx.new(|cx| {
19166 let mut editor = Editor::single_line(window, cx);
19167 editor.buffer.update(cx, |buffer, cx| {
19168 buffer.edit(
19169 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
19170 None,
19171 cx,
19172 )
19173 });
19174 let cursor_offset_in_rename_range =
19175 MultiBufferOffset(cursor_offset_in_rename_range);
19176 let cursor_offset_in_rename_range_end =
19177 MultiBufferOffset(cursor_offset_in_rename_range_end);
19178 let rename_selection_range = match cursor_offset_in_rename_range
19179 .cmp(&cursor_offset_in_rename_range_end)
19180 {
19181 Ordering::Equal => {
19182 editor.select_all(&SelectAll, window, cx);
19183 return editor;
19184 }
19185 Ordering::Less => {
19186 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
19187 }
19188 Ordering::Greater => {
19189 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
19190 }
19191 };
19192 if rename_selection_range.end.0 > old_name.len() {
19193 editor.select_all(&SelectAll, window, cx);
19194 } else {
19195 editor.change_selections(Default::default(), window, cx, |s| {
19196 s.select_ranges([rename_selection_range]);
19197 });
19198 }
19199 editor
19200 });
19201 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19202 if e == &EditorEvent::Focused {
19203 cx.emit(EditorEvent::FocusedIn)
19204 }
19205 })
19206 .detach();
19207
19208 let write_highlights =
19209 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19210 let read_highlights =
19211 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19212 let ranges = write_highlights
19213 .iter()
19214 .flat_map(|(_, ranges)| ranges.iter())
19215 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19216 .cloned()
19217 .collect();
19218
19219 this.highlight_text(
19220 HighlightKey::Rename,
19221 ranges,
19222 HighlightStyle {
19223 fade_out: Some(0.6),
19224 ..Default::default()
19225 },
19226 cx,
19227 );
19228 let rename_focus_handle = rename_editor.focus_handle(cx);
19229 window.focus(&rename_focus_handle, cx);
19230 let block_id = this.insert_blocks(
19231 [BlockProperties {
19232 style: BlockStyle::Flex,
19233 placement: BlockPlacement::Below(range.start),
19234 height: Some(1),
19235 render: Arc::new({
19236 let rename_editor = rename_editor.clone();
19237 move |cx: &mut BlockContext| {
19238 let mut text_style = cx.editor_style.text.clone();
19239 if let Some(highlight_style) = old_highlight_id
19240 .and_then(|h| cx.editor_style.syntax.get(h).cloned())
19241 {
19242 text_style = text_style.highlight(highlight_style);
19243 }
19244 div()
19245 .block_mouse_except_scroll()
19246 .pl(cx.anchor_x)
19247 .child(EditorElement::new(
19248 &rename_editor,
19249 EditorStyle {
19250 background: cx.theme().system().transparent,
19251 local_player: cx.editor_style.local_player,
19252 text: text_style,
19253 scrollbar_width: cx.editor_style.scrollbar_width,
19254 syntax: cx.editor_style.syntax.clone(),
19255 status: cx.editor_style.status.clone(),
19256 inlay_hints_style: HighlightStyle {
19257 font_weight: Some(FontWeight::BOLD),
19258 ..make_inlay_hints_style(cx.app)
19259 },
19260 edit_prediction_styles: make_suggestion_styles(
19261 cx.app,
19262 ),
19263 ..EditorStyle::default()
19264 },
19265 ))
19266 .into_any_element()
19267 }
19268 }),
19269 priority: 0,
19270 }],
19271 Some(Autoscroll::fit()),
19272 cx,
19273 )[0];
19274 this.pending_rename = Some(RenameState {
19275 range,
19276 old_name,
19277 editor: rename_editor,
19278 block_id,
19279 });
19280 })?;
19281 }
19282
19283 Ok(())
19284 }))
19285 }
19286
19287 pub fn confirm_rename(
19288 &mut self,
19289 _: &ConfirmRename,
19290 window: &mut Window,
19291 cx: &mut Context<Self>,
19292 ) -> Option<Task<Result<()>>> {
19293 let rename = self.take_rename(false, window, cx)?;
19294 let workspace = self.workspace()?.downgrade();
19295 let (buffer, start) = self
19296 .buffer
19297 .read(cx)
19298 .text_anchor_for_position(rename.range.start, cx)?;
19299 let (end_buffer, _) = self
19300 .buffer
19301 .read(cx)
19302 .text_anchor_for_position(rename.range.end, cx)?;
19303 if buffer != end_buffer {
19304 return None;
19305 }
19306
19307 let old_name = rename.old_name;
19308 let new_name = rename.editor.read(cx).text(cx);
19309
19310 let rename = self.semantics_provider.as_ref()?.perform_rename(
19311 &buffer,
19312 start,
19313 new_name.clone(),
19314 cx,
19315 )?;
19316
19317 Some(cx.spawn_in(window, async move |editor, cx| {
19318 let project_transaction = rename.await?;
19319 Self::open_project_transaction(
19320 &editor,
19321 workspace,
19322 project_transaction,
19323 format!("Rename: {} → {}", old_name, new_name),
19324 cx,
19325 )
19326 .await?;
19327
19328 editor.update(cx, |editor, cx| {
19329 editor.refresh_document_highlights(cx);
19330 })?;
19331 Ok(())
19332 }))
19333 }
19334
19335 fn take_rename(
19336 &mut self,
19337 moving_cursor: bool,
19338 window: &mut Window,
19339 cx: &mut Context<Self>,
19340 ) -> Option<RenameState> {
19341 let rename = self.pending_rename.take()?;
19342 if rename.editor.focus_handle(cx).is_focused(window) {
19343 window.focus(&self.focus_handle, cx);
19344 }
19345
19346 self.remove_blocks(
19347 [rename.block_id].into_iter().collect(),
19348 Some(Autoscroll::fit()),
19349 cx,
19350 );
19351 self.clear_highlights(HighlightKey::Rename, cx);
19352 self.show_local_selections = true;
19353
19354 if moving_cursor {
19355 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19356 editor
19357 .selections
19358 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19359 .head()
19360 });
19361
19362 // Update the selection to match the position of the selection inside
19363 // the rename editor.
19364 let snapshot = self.buffer.read(cx).read(cx);
19365 let rename_range = rename.range.to_offset(&snapshot);
19366 let cursor_in_editor = snapshot
19367 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19368 .min(rename_range.end);
19369 drop(snapshot);
19370
19371 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19372 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19373 });
19374 } else {
19375 self.refresh_document_highlights(cx);
19376 }
19377
19378 Some(rename)
19379 }
19380
19381 pub fn pending_rename(&self) -> Option<&RenameState> {
19382 self.pending_rename.as_ref()
19383 }
19384
19385 fn format(
19386 &mut self,
19387 _: &Format,
19388 window: &mut Window,
19389 cx: &mut Context<Self>,
19390 ) -> Option<Task<Result<()>>> {
19391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19392
19393 let project = match &self.project {
19394 Some(project) => project.clone(),
19395 None => return None,
19396 };
19397
19398 Some(self.perform_format(
19399 project,
19400 FormatTrigger::Manual,
19401 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19402 window,
19403 cx,
19404 ))
19405 }
19406
19407 fn format_selections(
19408 &mut self,
19409 _: &FormatSelections,
19410 window: &mut Window,
19411 cx: &mut Context<Self>,
19412 ) -> Option<Task<Result<()>>> {
19413 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19414
19415 let project = match &self.project {
19416 Some(project) => project.clone(),
19417 None => return None,
19418 };
19419
19420 let ranges = self
19421 .selections
19422 .all_adjusted(&self.display_snapshot(cx))
19423 .into_iter()
19424 .map(|selection| selection.range())
19425 .collect_vec();
19426
19427 Some(self.perform_format(
19428 project,
19429 FormatTrigger::Manual,
19430 FormatTarget::Ranges(ranges),
19431 window,
19432 cx,
19433 ))
19434 }
19435
19436 fn perform_format(
19437 &mut self,
19438 project: Entity<Project>,
19439 trigger: FormatTrigger,
19440 target: FormatTarget,
19441 window: &mut Window,
19442 cx: &mut Context<Self>,
19443 ) -> Task<Result<()>> {
19444 let buffer = self.buffer.clone();
19445 let (buffers, target) = match target {
19446 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19447 FormatTarget::Ranges(selection_ranges) => {
19448 let multi_buffer = buffer.read(cx);
19449 let snapshot = multi_buffer.read(cx);
19450 let mut buffers = HashSet::default();
19451 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19452 BTreeMap::new();
19453 for selection_range in selection_ranges {
19454 for (buffer_snapshot, buffer_range, _) in
19455 snapshot.range_to_buffer_ranges(selection_range.start..selection_range.end)
19456 {
19457 let buffer_id = buffer_snapshot.remote_id();
19458 let start = buffer_snapshot.anchor_before(buffer_range.start);
19459 let end = buffer_snapshot.anchor_after(buffer_range.end);
19460 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19461 buffer_id_to_ranges
19462 .entry(buffer_id)
19463 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19464 .or_insert_with(|| vec![start..end]);
19465 }
19466 }
19467 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19468 }
19469 };
19470
19471 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19472 let selections_prev = transaction_id_prev
19473 .and_then(|transaction_id_prev| {
19474 // default to selections as they were after the last edit, if we have them,
19475 // instead of how they are now.
19476 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19477 // will take you back to where you made the last edit, instead of staying where you scrolled
19478 self.selection_history
19479 .transaction(transaction_id_prev)
19480 .map(|t| t.0.clone())
19481 })
19482 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19483
19484 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19485 let format = project.update(cx, |project, cx| {
19486 project.format(buffers, target, true, trigger, cx)
19487 });
19488
19489 cx.spawn_in(window, async move |editor, cx| {
19490 let transaction = futures::select_biased! {
19491 transaction = format.log_err().fuse() => transaction,
19492 () = timeout => {
19493 log::warn!("timed out waiting for formatting");
19494 None
19495 }
19496 };
19497
19498 buffer.update(cx, |buffer, cx| {
19499 if let Some(transaction) = transaction
19500 && !buffer.is_singleton()
19501 {
19502 buffer.push_transaction(&transaction.0, cx);
19503 }
19504 cx.notify();
19505 });
19506
19507 if let Some(transaction_id_now) =
19508 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19509 {
19510 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19511 if has_new_transaction {
19512 editor
19513 .update(cx, |editor, _| {
19514 editor
19515 .selection_history
19516 .insert_transaction(transaction_id_now, selections_prev);
19517 })
19518 .ok();
19519 }
19520 }
19521
19522 Ok(())
19523 })
19524 }
19525
19526 fn organize_imports(
19527 &mut self,
19528 _: &OrganizeImports,
19529 window: &mut Window,
19530 cx: &mut Context<Self>,
19531 ) -> Option<Task<Result<()>>> {
19532 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19533 let project = match &self.project {
19534 Some(project) => project.clone(),
19535 None => return None,
19536 };
19537 Some(self.perform_code_action_kind(
19538 project,
19539 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19540 window,
19541 cx,
19542 ))
19543 }
19544
19545 fn perform_code_action_kind(
19546 &mut self,
19547 project: Entity<Project>,
19548 kind: CodeActionKind,
19549 window: &mut Window,
19550 cx: &mut Context<Self>,
19551 ) -> Task<Result<()>> {
19552 let buffer = self.buffer.clone();
19553 let buffers = buffer.read(cx).all_buffers();
19554 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19555 let apply_action = project.update(cx, |project, cx| {
19556 project.apply_code_action_kind(buffers, kind, true, cx)
19557 });
19558 cx.spawn_in(window, async move |_, cx| {
19559 let transaction = futures::select_biased! {
19560 () = timeout => {
19561 log::warn!("timed out waiting for executing code action");
19562 None
19563 }
19564 transaction = apply_action.log_err().fuse() => transaction,
19565 };
19566 buffer.update(cx, |buffer, cx| {
19567 // check if we need this
19568 if let Some(transaction) = transaction
19569 && !buffer.is_singleton()
19570 {
19571 buffer.push_transaction(&transaction.0, cx);
19572 }
19573 cx.notify();
19574 });
19575 Ok(())
19576 })
19577 }
19578
19579 pub fn restart_language_server(
19580 &mut self,
19581 _: &RestartLanguageServer,
19582 _: &mut Window,
19583 cx: &mut Context<Self>,
19584 ) {
19585 if let Some(project) = self.project.clone() {
19586 self.buffer.update(cx, |multi_buffer, cx| {
19587 project.update(cx, |project, cx| {
19588 project.restart_language_servers_for_buffers(
19589 multi_buffer.all_buffers().into_iter().collect(),
19590 HashSet::default(),
19591 cx,
19592 );
19593 });
19594 })
19595 }
19596 }
19597
19598 pub fn stop_language_server(
19599 &mut self,
19600 _: &StopLanguageServer,
19601 _: &mut Window,
19602 cx: &mut Context<Self>,
19603 ) {
19604 if let Some(project) = self.project.clone() {
19605 self.buffer.update(cx, |multi_buffer, cx| {
19606 project.update(cx, |project, cx| {
19607 project.stop_language_servers_for_buffers(
19608 multi_buffer.all_buffers().into_iter().collect(),
19609 HashSet::default(),
19610 cx,
19611 );
19612 });
19613 });
19614 }
19615 }
19616
19617 fn cancel_language_server_work(
19618 workspace: &mut Workspace,
19619 _: &actions::CancelLanguageServerWork,
19620 _: &mut Window,
19621 cx: &mut Context<Workspace>,
19622 ) {
19623 let project = workspace.project();
19624 let buffers = workspace
19625 .active_item(cx)
19626 .and_then(|item| item.act_as::<Editor>(cx))
19627 .map_or(HashSet::default(), |editor| {
19628 editor.read(cx).buffer.read(cx).all_buffers()
19629 });
19630 project.update(cx, |project, cx| {
19631 project.cancel_language_server_work_for_buffers(buffers, cx);
19632 });
19633 }
19634
19635 fn show_character_palette(
19636 &mut self,
19637 _: &ShowCharacterPalette,
19638 window: &mut Window,
19639 _: &mut Context<Self>,
19640 ) {
19641 window.show_character_palette();
19642 }
19643
19644 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19645 if !self.diagnostics_enabled() {
19646 return;
19647 }
19648
19649 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19650 let buffer = self.buffer.read(cx).snapshot(cx);
19651 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19652 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19653 let is_valid = buffer
19654 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19655 .any(|entry| {
19656 entry.diagnostic.is_primary
19657 && !entry.range.is_empty()
19658 && entry.range.start == primary_range_start
19659 && entry.diagnostic.message == active_diagnostics.active_message
19660 });
19661
19662 if !is_valid {
19663 self.dismiss_diagnostics(cx);
19664 }
19665 }
19666 }
19667
19668 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19669 match &self.active_diagnostics {
19670 ActiveDiagnostic::Group(group) => Some(group),
19671 _ => None,
19672 }
19673 }
19674
19675 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19676 if !self.diagnostics_enabled() {
19677 return;
19678 }
19679 self.dismiss_diagnostics(cx);
19680 self.active_diagnostics = ActiveDiagnostic::All;
19681 }
19682
19683 fn activate_diagnostics(
19684 &mut self,
19685 buffer_id: BufferId,
19686 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19687 window: &mut Window,
19688 cx: &mut Context<Self>,
19689 ) {
19690 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19691 return;
19692 }
19693 self.dismiss_diagnostics(cx);
19694 let snapshot = self.snapshot(window, cx);
19695 let buffer = self.buffer.read(cx).snapshot(cx);
19696 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19697 return;
19698 };
19699
19700 let diagnostic_group = buffer
19701 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19702 .collect::<Vec<_>>();
19703
19704 let language_registry = self
19705 .project()
19706 .map(|project| project.read(cx).languages().clone());
19707
19708 let blocks = renderer.render_group(
19709 diagnostic_group,
19710 buffer_id,
19711 snapshot,
19712 cx.weak_entity(),
19713 language_registry,
19714 cx,
19715 );
19716
19717 let blocks = self.display_map.update(cx, |display_map, cx| {
19718 display_map.insert_blocks(blocks, cx).into_iter().collect()
19719 });
19720 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19721 active_range: buffer.anchor_before(diagnostic.range.start)
19722 ..buffer.anchor_after(diagnostic.range.end),
19723 active_message: diagnostic.diagnostic.message.clone(),
19724 group_id: diagnostic.diagnostic.group_id,
19725 blocks,
19726 });
19727 cx.notify();
19728 }
19729
19730 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19731 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19732 return;
19733 };
19734
19735 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19736 if let ActiveDiagnostic::Group(group) = prev {
19737 self.display_map.update(cx, |display_map, cx| {
19738 display_map.remove_blocks(group.blocks, cx);
19739 });
19740 cx.notify();
19741 }
19742 }
19743
19744 /// Disable inline diagnostics rendering for this editor.
19745 pub fn disable_inline_diagnostics(&mut self) {
19746 self.inline_diagnostics_enabled = false;
19747 self.inline_diagnostics_update = Task::ready(());
19748 self.inline_diagnostics.clear();
19749 }
19750
19751 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19752 self.diagnostics_enabled = false;
19753 self.dismiss_diagnostics(cx);
19754 self.inline_diagnostics_update = Task::ready(());
19755 self.inline_diagnostics.clear();
19756 }
19757
19758 pub fn disable_word_completions(&mut self) {
19759 self.word_completions_enabled = false;
19760 }
19761
19762 pub fn diagnostics_enabled(&self) -> bool {
19763 self.diagnostics_enabled && self.lsp_data_enabled()
19764 }
19765
19766 pub fn inline_diagnostics_enabled(&self) -> bool {
19767 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19768 }
19769
19770 pub fn show_inline_diagnostics(&self) -> bool {
19771 self.show_inline_diagnostics
19772 }
19773
19774 pub fn toggle_inline_diagnostics(
19775 &mut self,
19776 _: &ToggleInlineDiagnostics,
19777 window: &mut Window,
19778 cx: &mut Context<Editor>,
19779 ) {
19780 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19781 self.refresh_inline_diagnostics(false, window, cx);
19782 }
19783
19784 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19785 self.diagnostics_max_severity = severity;
19786 self.display_map.update(cx, |display_map, _| {
19787 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19788 });
19789 }
19790
19791 pub fn toggle_diagnostics(
19792 &mut self,
19793 _: &ToggleDiagnostics,
19794 window: &mut Window,
19795 cx: &mut Context<Editor>,
19796 ) {
19797 if !self.diagnostics_enabled() {
19798 return;
19799 }
19800
19801 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19802 EditorSettings::get_global(cx)
19803 .diagnostics_max_severity
19804 .filter(|severity| severity != &DiagnosticSeverity::Off)
19805 .unwrap_or(DiagnosticSeverity::Hint)
19806 } else {
19807 DiagnosticSeverity::Off
19808 };
19809 self.set_max_diagnostics_severity(new_severity, cx);
19810 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19811 self.active_diagnostics = ActiveDiagnostic::None;
19812 self.inline_diagnostics_update = Task::ready(());
19813 self.inline_diagnostics.clear();
19814 } else {
19815 self.refresh_inline_diagnostics(false, window, cx);
19816 }
19817
19818 cx.notify();
19819 }
19820
19821 pub fn toggle_minimap(
19822 &mut self,
19823 _: &ToggleMinimap,
19824 window: &mut Window,
19825 cx: &mut Context<Editor>,
19826 ) {
19827 if self.supports_minimap(cx) {
19828 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19829 }
19830 }
19831
19832 fn refresh_inline_diagnostics(
19833 &mut self,
19834 debounce: bool,
19835 window: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) {
19838 let max_severity = ProjectSettings::get_global(cx)
19839 .diagnostics
19840 .inline
19841 .max_severity
19842 .unwrap_or(self.diagnostics_max_severity);
19843
19844 if !self.inline_diagnostics_enabled()
19845 || !self.diagnostics_enabled()
19846 || !self.show_inline_diagnostics
19847 || max_severity == DiagnosticSeverity::Off
19848 {
19849 self.inline_diagnostics_update = Task::ready(());
19850 self.inline_diagnostics.clear();
19851 return;
19852 }
19853
19854 let debounce_ms = ProjectSettings::get_global(cx)
19855 .diagnostics
19856 .inline
19857 .update_debounce_ms;
19858 let debounce = if debounce && debounce_ms > 0 {
19859 Some(Duration::from_millis(debounce_ms))
19860 } else {
19861 None
19862 };
19863 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19864 if let Some(debounce) = debounce {
19865 cx.background_executor().timer(debounce).await;
19866 }
19867 let Some(snapshot) = editor.upgrade().map(|editor| {
19868 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19869 }) else {
19870 return;
19871 };
19872
19873 let new_inline_diagnostics = cx
19874 .background_spawn(async move {
19875 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19876 for diagnostic_entry in
19877 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19878 {
19879 let message = diagnostic_entry
19880 .diagnostic
19881 .message
19882 .split_once('\n')
19883 .map(|(line, _)| line)
19884 .map(SharedString::new)
19885 .unwrap_or_else(|| {
19886 SharedString::new(&*diagnostic_entry.diagnostic.message)
19887 });
19888 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19889 let (Ok(i) | Err(i)) = inline_diagnostics
19890 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19891 inline_diagnostics.insert(
19892 i,
19893 (
19894 start_anchor,
19895 InlineDiagnostic {
19896 message,
19897 group_id: diagnostic_entry.diagnostic.group_id,
19898 start: diagnostic_entry.range.start.to_point(&snapshot),
19899 is_primary: diagnostic_entry.diagnostic.is_primary,
19900 severity: diagnostic_entry.diagnostic.severity,
19901 },
19902 ),
19903 );
19904 }
19905 inline_diagnostics
19906 })
19907 .await;
19908
19909 editor
19910 .update(cx, |editor, cx| {
19911 editor.inline_diagnostics = new_inline_diagnostics;
19912 cx.notify();
19913 })
19914 .ok();
19915 });
19916 }
19917
19918 fn pull_diagnostics(
19919 &mut self,
19920 buffer_id: BufferId,
19921 _window: &Window,
19922 cx: &mut Context<Self>,
19923 ) -> Option<()> {
19924 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19925 // skip any LSP updates for it.
19926
19927 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19928 return None;
19929 }
19930 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19931 .diagnostics
19932 .lsp_pull_diagnostics;
19933 if !pull_diagnostics_settings.enabled {
19934 return None;
19935 }
19936 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19937 let project = self.project()?.downgrade();
19938 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19939
19940 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19941 cx.background_executor().timer(debounce).await;
19942 if let Ok(task) = project.update(cx, |project, cx| {
19943 project.lsp_store().update(cx, |lsp_store, cx| {
19944 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19945 })
19946 }) {
19947 task.await.log_err();
19948 }
19949 project
19950 .update(cx, |project, cx| {
19951 project.lsp_store().update(cx, |lsp_store, cx| {
19952 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19953 })
19954 })
19955 .log_err();
19956 });
19957
19958 Some(())
19959 }
19960
19961 pub fn set_selections_from_remote(
19962 &mut self,
19963 selections: Vec<Selection<Anchor>>,
19964 pending_selection: Option<Selection<Anchor>>,
19965 window: &mut Window,
19966 cx: &mut Context<Self>,
19967 ) {
19968 let old_cursor_position = self.selections.newest_anchor().head();
19969 self.selections
19970 .change_with(&self.display_snapshot(cx), |s| {
19971 s.select_anchors(selections);
19972 if let Some(pending_selection) = pending_selection {
19973 s.set_pending(pending_selection, SelectMode::Character);
19974 } else {
19975 s.clear_pending();
19976 }
19977 });
19978 self.selections_did_change(
19979 false,
19980 &old_cursor_position,
19981 SelectionEffects::default(),
19982 window,
19983 cx,
19984 );
19985 }
19986
19987 pub fn transact(
19988 &mut self,
19989 window: &mut Window,
19990 cx: &mut Context<Self>,
19991 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19992 ) -> Option<TransactionId> {
19993 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19994 this.start_transaction_at(Instant::now(), window, cx);
19995 update(this, window, cx);
19996 this.end_transaction_at(Instant::now(), cx)
19997 })
19998 }
19999
20000 pub fn start_transaction_at(
20001 &mut self,
20002 now: Instant,
20003 window: &mut Window,
20004 cx: &mut Context<Self>,
20005 ) -> Option<TransactionId> {
20006 self.end_selection(window, cx);
20007 if let Some(tx_id) = self
20008 .buffer
20009 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
20010 {
20011 self.selection_history
20012 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
20013 cx.emit(EditorEvent::TransactionBegun {
20014 transaction_id: tx_id,
20015 });
20016 Some(tx_id)
20017 } else {
20018 None
20019 }
20020 }
20021
20022 pub fn end_transaction_at(
20023 &mut self,
20024 now: Instant,
20025 cx: &mut Context<Self>,
20026 ) -> Option<TransactionId> {
20027 if let Some(transaction_id) = self
20028 .buffer
20029 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
20030 {
20031 if let Some((_, end_selections)) =
20032 self.selection_history.transaction_mut(transaction_id)
20033 {
20034 *end_selections = Some(self.selections.disjoint_anchors_arc());
20035 } else {
20036 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
20037 }
20038
20039 cx.emit(EditorEvent::Edited { transaction_id });
20040 Some(transaction_id)
20041 } else {
20042 None
20043 }
20044 }
20045
20046 pub fn modify_transaction_selection_history(
20047 &mut self,
20048 transaction_id: TransactionId,
20049 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
20050 ) -> bool {
20051 self.selection_history
20052 .transaction_mut(transaction_id)
20053 .map(modify)
20054 .is_some()
20055 }
20056
20057 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
20058 if self.selection_mark_mode {
20059 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20060 s.move_with(&mut |_, sel| {
20061 sel.collapse_to(sel.head(), SelectionGoal::None);
20062 });
20063 })
20064 }
20065 self.selection_mark_mode = true;
20066 cx.notify();
20067 }
20068
20069 pub fn swap_selection_ends(
20070 &mut self,
20071 _: &actions::SwapSelectionEnds,
20072 window: &mut Window,
20073 cx: &mut Context<Self>,
20074 ) {
20075 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20076 s.move_with(&mut |_, sel| {
20077 if sel.start != sel.end {
20078 sel.reversed = !sel.reversed
20079 }
20080 });
20081 });
20082 self.request_autoscroll(Autoscroll::newest(), cx);
20083 cx.notify();
20084 }
20085
20086 pub fn toggle_focus(
20087 workspace: &mut Workspace,
20088 _: &actions::ToggleFocus,
20089 window: &mut Window,
20090 cx: &mut Context<Workspace>,
20091 ) {
20092 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
20093 return;
20094 };
20095 workspace.activate_item(&item, true, true, window, cx);
20096 }
20097
20098 pub fn toggle_fold(
20099 &mut self,
20100 _: &actions::ToggleFold,
20101 window: &mut Window,
20102 cx: &mut Context<Self>,
20103 ) {
20104 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20105 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20106 let selection = self.selections.newest::<Point>(&display_map);
20107
20108 let range = if selection.is_empty() {
20109 let point = selection.head().to_display_point(&display_map);
20110 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20111 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20112 .to_point(&display_map);
20113 start..end
20114 } else {
20115 selection.range()
20116 };
20117 if display_map.folds_in_range(range).next().is_some() {
20118 self.unfold_lines(&Default::default(), window, cx)
20119 } else {
20120 self.fold(&Default::default(), window, cx)
20121 }
20122 } else {
20123 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20124 let buffer_ids: HashSet<_> = self
20125 .selections
20126 .disjoint_anchor_ranges()
20127 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20128 .collect();
20129
20130 let should_unfold = buffer_ids
20131 .iter()
20132 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20133
20134 for buffer_id in buffer_ids {
20135 if should_unfold {
20136 self.unfold_buffer(buffer_id, cx);
20137 } else {
20138 self.fold_buffer(buffer_id, cx);
20139 }
20140 }
20141 }
20142 }
20143
20144 pub fn toggle_fold_recursive(
20145 &mut self,
20146 _: &actions::ToggleFoldRecursive,
20147 window: &mut Window,
20148 cx: &mut Context<Self>,
20149 ) {
20150 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20151
20152 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20153 let range = if selection.is_empty() {
20154 let point = selection.head().to_display_point(&display_map);
20155 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20156 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20157 .to_point(&display_map);
20158 start..end
20159 } else {
20160 selection.range()
20161 };
20162 if display_map.folds_in_range(range).next().is_some() {
20163 self.unfold_recursive(&Default::default(), window, cx)
20164 } else {
20165 self.fold_recursive(&Default::default(), window, cx)
20166 }
20167 }
20168
20169 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
20170 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20171 let mut to_fold = Vec::new();
20172 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20173 let selections = self.selections.all_adjusted(&display_map);
20174
20175 for selection in selections {
20176 let range = selection.range().sorted();
20177 let buffer_start_row = range.start.row;
20178
20179 if range.start.row != range.end.row {
20180 let mut found = false;
20181 let mut row = range.start.row;
20182 while row <= range.end.row {
20183 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20184 {
20185 found = true;
20186 row = crease.range().end.row + 1;
20187 to_fold.push(crease);
20188 } else {
20189 row += 1
20190 }
20191 }
20192 if found {
20193 continue;
20194 }
20195 }
20196
20197 for row in (0..=range.start.row).rev() {
20198 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20199 && crease.range().end.row >= buffer_start_row
20200 {
20201 to_fold.push(crease);
20202 if row <= range.start.row {
20203 break;
20204 }
20205 }
20206 }
20207 }
20208
20209 self.fold_creases(to_fold, true, window, cx);
20210 } else {
20211 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20212 let buffer_ids = self
20213 .selections
20214 .disjoint_anchor_ranges()
20215 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20216 .collect::<HashSet<_>>();
20217 for buffer_id in buffer_ids {
20218 self.fold_buffer(buffer_id, cx);
20219 }
20220 }
20221 }
20222
20223 pub fn toggle_fold_all(
20224 &mut self,
20225 _: &actions::ToggleFoldAll,
20226 window: &mut Window,
20227 cx: &mut Context<Self>,
20228 ) {
20229 let has_folds = if self.buffer.read(cx).is_singleton() {
20230 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20231 let has_folds = display_map
20232 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20233 .next()
20234 .is_some();
20235 has_folds
20236 } else {
20237 let snapshot = self.buffer.read(cx).snapshot(cx);
20238 let has_folds = snapshot
20239 .all_buffer_ids()
20240 .any(|buffer_id| self.is_buffer_folded(buffer_id, cx));
20241 has_folds
20242 };
20243
20244 if has_folds {
20245 self.unfold_all(&actions::UnfoldAll, window, cx);
20246 } else {
20247 self.fold_all(&actions::FoldAll, window, cx);
20248 }
20249 }
20250
20251 fn fold_at_level(
20252 &mut self,
20253 fold_at: &FoldAtLevel,
20254 window: &mut Window,
20255 cx: &mut Context<Self>,
20256 ) {
20257 if !self.buffer.read(cx).is_singleton() {
20258 return;
20259 }
20260
20261 let fold_at_level = fold_at.0;
20262 let snapshot = self.buffer.read(cx).snapshot(cx);
20263 let mut to_fold = Vec::new();
20264 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20265
20266 let row_ranges_to_keep: Vec<Range<u32>> = self
20267 .selections
20268 .all::<Point>(&self.display_snapshot(cx))
20269 .into_iter()
20270 .map(|sel| sel.start.row..sel.end.row)
20271 .collect();
20272
20273 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20274 while start_row < end_row {
20275 match self
20276 .snapshot(window, cx)
20277 .crease_for_buffer_row(MultiBufferRow(start_row))
20278 {
20279 Some(crease) => {
20280 let nested_start_row = crease.range().start.row + 1;
20281 let nested_end_row = crease.range().end.row;
20282
20283 if current_level < fold_at_level {
20284 stack.push((nested_start_row, nested_end_row, current_level + 1));
20285 } else if current_level == fold_at_level {
20286 // Fold iff there is no selection completely contained within the fold region
20287 if !row_ranges_to_keep.iter().any(|selection| {
20288 selection.end >= nested_start_row
20289 && selection.start <= nested_end_row
20290 }) {
20291 to_fold.push(crease);
20292 }
20293 }
20294
20295 start_row = nested_end_row + 1;
20296 }
20297 None => start_row += 1,
20298 }
20299 }
20300 }
20301
20302 self.fold_creases(to_fold, true, window, cx);
20303 }
20304
20305 pub fn fold_at_level_1(
20306 &mut self,
20307 _: &actions::FoldAtLevel1,
20308 window: &mut Window,
20309 cx: &mut Context<Self>,
20310 ) {
20311 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20312 }
20313
20314 pub fn fold_at_level_2(
20315 &mut self,
20316 _: &actions::FoldAtLevel2,
20317 window: &mut Window,
20318 cx: &mut Context<Self>,
20319 ) {
20320 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20321 }
20322
20323 pub fn fold_at_level_3(
20324 &mut self,
20325 _: &actions::FoldAtLevel3,
20326 window: &mut Window,
20327 cx: &mut Context<Self>,
20328 ) {
20329 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20330 }
20331
20332 pub fn fold_at_level_4(
20333 &mut self,
20334 _: &actions::FoldAtLevel4,
20335 window: &mut Window,
20336 cx: &mut Context<Self>,
20337 ) {
20338 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20339 }
20340
20341 pub fn fold_at_level_5(
20342 &mut self,
20343 _: &actions::FoldAtLevel5,
20344 window: &mut Window,
20345 cx: &mut Context<Self>,
20346 ) {
20347 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20348 }
20349
20350 pub fn fold_at_level_6(
20351 &mut self,
20352 _: &actions::FoldAtLevel6,
20353 window: &mut Window,
20354 cx: &mut Context<Self>,
20355 ) {
20356 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20357 }
20358
20359 pub fn fold_at_level_7(
20360 &mut self,
20361 _: &actions::FoldAtLevel7,
20362 window: &mut Window,
20363 cx: &mut Context<Self>,
20364 ) {
20365 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20366 }
20367
20368 pub fn fold_at_level_8(
20369 &mut self,
20370 _: &actions::FoldAtLevel8,
20371 window: &mut Window,
20372 cx: &mut Context<Self>,
20373 ) {
20374 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20375 }
20376
20377 pub fn fold_at_level_9(
20378 &mut self,
20379 _: &actions::FoldAtLevel9,
20380 window: &mut Window,
20381 cx: &mut Context<Self>,
20382 ) {
20383 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20384 }
20385
20386 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20387 if self.buffer.read(cx).is_singleton() {
20388 let mut fold_ranges = Vec::new();
20389 let snapshot = self.buffer.read(cx).snapshot(cx);
20390
20391 for row in 0..snapshot.max_row().0 {
20392 if let Some(foldable_range) = self
20393 .snapshot(window, cx)
20394 .crease_for_buffer_row(MultiBufferRow(row))
20395 {
20396 fold_ranges.push(foldable_range);
20397 }
20398 }
20399
20400 self.fold_creases(fold_ranges, true, window, cx);
20401 } else {
20402 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20403 editor
20404 .update_in(cx, |editor, _, cx| {
20405 let snapshot = editor.buffer.read(cx).snapshot(cx);
20406 for buffer_id in snapshot.all_buffer_ids() {
20407 editor.fold_buffer(buffer_id, cx);
20408 }
20409 })
20410 .ok();
20411 });
20412 }
20413 }
20414
20415 pub fn fold_function_bodies(
20416 &mut self,
20417 _: &actions::FoldFunctionBodies,
20418 window: &mut Window,
20419 cx: &mut Context<Self>,
20420 ) {
20421 let snapshot = self.buffer.read(cx).snapshot(cx);
20422
20423 let ranges = snapshot
20424 .text_object_ranges(
20425 MultiBufferOffset(0)..snapshot.len(),
20426 TreeSitterOptions::default(),
20427 )
20428 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20429 .collect::<Vec<_>>();
20430
20431 let creases = ranges
20432 .into_iter()
20433 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20434 .collect();
20435
20436 self.fold_creases(creases, true, window, cx);
20437 }
20438
20439 pub fn fold_recursive(
20440 &mut self,
20441 _: &actions::FoldRecursive,
20442 window: &mut Window,
20443 cx: &mut Context<Self>,
20444 ) {
20445 let mut to_fold = Vec::new();
20446 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20447 let selections = self.selections.all_adjusted(&display_map);
20448
20449 for selection in selections {
20450 let range = selection.range().sorted();
20451 let buffer_start_row = range.start.row;
20452
20453 if range.start.row != range.end.row {
20454 let mut found = false;
20455 for row in range.start.row..=range.end.row {
20456 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20457 found = true;
20458 to_fold.push(crease);
20459 }
20460 }
20461 if found {
20462 continue;
20463 }
20464 }
20465
20466 for row in (0..=range.start.row).rev() {
20467 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20468 if crease.range().end.row >= buffer_start_row {
20469 to_fold.push(crease);
20470 } else {
20471 break;
20472 }
20473 }
20474 }
20475 }
20476
20477 self.fold_creases(to_fold, true, window, cx);
20478 }
20479
20480 pub fn fold_at(
20481 &mut self,
20482 buffer_row: MultiBufferRow,
20483 window: &mut Window,
20484 cx: &mut Context<Self>,
20485 ) {
20486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20487
20488 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20489 let autoscroll = self
20490 .selections
20491 .all::<Point>(&display_map)
20492 .iter()
20493 .any(|selection| crease.range().overlaps(&selection.range()));
20494
20495 self.fold_creases(vec![crease], autoscroll, window, cx);
20496 }
20497 }
20498
20499 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20500 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20501 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20502 let buffer = display_map.buffer_snapshot();
20503 let selections = self.selections.all::<Point>(&display_map);
20504 let ranges = selections
20505 .iter()
20506 .map(|s| {
20507 let range = s.display_range(&display_map).sorted();
20508 let mut start = range.start.to_point(&display_map);
20509 let mut end = range.end.to_point(&display_map);
20510 start.column = 0;
20511 end.column = buffer.line_len(MultiBufferRow(end.row));
20512 start..end
20513 })
20514 .collect::<Vec<_>>();
20515
20516 self.unfold_ranges(&ranges, true, true, cx);
20517 } else {
20518 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20519 let buffer_ids = self
20520 .selections
20521 .disjoint_anchor_ranges()
20522 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20523 .collect::<HashSet<_>>();
20524 for buffer_id in buffer_ids {
20525 self.unfold_buffer(buffer_id, cx);
20526 }
20527 }
20528 }
20529
20530 pub fn unfold_recursive(
20531 &mut self,
20532 _: &UnfoldRecursive,
20533 _window: &mut Window,
20534 cx: &mut Context<Self>,
20535 ) {
20536 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20537 let selections = self.selections.all::<Point>(&display_map);
20538 let ranges = selections
20539 .iter()
20540 .map(|s| {
20541 let mut range = s.display_range(&display_map).sorted();
20542 *range.start.column_mut() = 0;
20543 *range.end.column_mut() = display_map.line_len(range.end.row());
20544 let start = range.start.to_point(&display_map);
20545 let end = range.end.to_point(&display_map);
20546 start..end
20547 })
20548 .collect::<Vec<_>>();
20549
20550 self.unfold_ranges(&ranges, true, true, cx);
20551 }
20552
20553 pub fn unfold_at(
20554 &mut self,
20555 buffer_row: MultiBufferRow,
20556 _window: &mut Window,
20557 cx: &mut Context<Self>,
20558 ) {
20559 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20560
20561 let intersection_range = Point::new(buffer_row.0, 0)
20562 ..Point::new(
20563 buffer_row.0,
20564 display_map.buffer_snapshot().line_len(buffer_row),
20565 );
20566
20567 let autoscroll = self
20568 .selections
20569 .all::<Point>(&display_map)
20570 .iter()
20571 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20572
20573 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20574 }
20575
20576 pub fn unfold_all(
20577 &mut self,
20578 _: &actions::UnfoldAll,
20579 _window: &mut Window,
20580 cx: &mut Context<Self>,
20581 ) {
20582 if self.buffer.read(cx).is_singleton() {
20583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20584 self.unfold_ranges(
20585 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20586 true,
20587 true,
20588 cx,
20589 );
20590 } else {
20591 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20592 editor
20593 .update(cx, |editor, cx| {
20594 let snapshot = editor.buffer.read(cx).snapshot(cx);
20595 for buffer_id in snapshot.all_buffer_ids() {
20596 editor.unfold_buffer(buffer_id, cx);
20597 }
20598 })
20599 .ok();
20600 });
20601 }
20602 }
20603
20604 pub fn fold_selected_ranges(
20605 &mut self,
20606 _: &FoldSelectedRanges,
20607 window: &mut Window,
20608 cx: &mut Context<Self>,
20609 ) {
20610 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20611 let selections = self.selections.all_adjusted(&display_map);
20612 let ranges = selections
20613 .into_iter()
20614 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20615 .collect::<Vec<_>>();
20616 self.fold_creases(ranges, true, window, cx);
20617 }
20618
20619 pub fn fold_ranges<T: ToOffset + Clone>(
20620 &mut self,
20621 ranges: Vec<Range<T>>,
20622 auto_scroll: bool,
20623 window: &mut Window,
20624 cx: &mut Context<Self>,
20625 ) {
20626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20627 let ranges = ranges
20628 .into_iter()
20629 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20630 .collect::<Vec<_>>();
20631 self.fold_creases(ranges, auto_scroll, window, cx);
20632 }
20633
20634 pub fn fold_creases<T: ToOffset + Clone>(
20635 &mut self,
20636 creases: Vec<Crease<T>>,
20637 auto_scroll: bool,
20638 window: &mut Window,
20639 cx: &mut Context<Self>,
20640 ) {
20641 if creases.is_empty() {
20642 return;
20643 }
20644
20645 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20646
20647 if auto_scroll {
20648 self.request_autoscroll(Autoscroll::fit(), cx);
20649 }
20650
20651 cx.notify();
20652
20653 self.scrollbar_marker_state.dirty = true;
20654 self.update_data_on_scroll(window, cx);
20655 self.folds_did_change(cx);
20656 }
20657
20658 /// Removes any folds whose ranges intersect any of the given ranges.
20659 pub fn unfold_ranges<T: ToOffset + Clone>(
20660 &mut self,
20661 ranges: &[Range<T>],
20662 inclusive: bool,
20663 auto_scroll: bool,
20664 cx: &mut Context<Self>,
20665 ) {
20666 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20667 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20668 });
20669 self.folds_did_change(cx);
20670 }
20671
20672 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20673 self.fold_buffers([buffer_id], cx);
20674 }
20675
20676 pub fn fold_buffers(
20677 &mut self,
20678 buffer_ids: impl IntoIterator<Item = BufferId>,
20679 cx: &mut Context<Self>,
20680 ) {
20681 if self.buffer().read(cx).is_singleton() {
20682 return;
20683 }
20684
20685 let ids_to_fold: Vec<BufferId> = buffer_ids
20686 .into_iter()
20687 .filter(|id| !self.is_buffer_folded(*id, cx))
20688 .collect();
20689
20690 if ids_to_fold.is_empty() {
20691 return;
20692 }
20693
20694 self.display_map.update(cx, |display_map, cx| {
20695 display_map.fold_buffers(ids_to_fold.clone(), cx)
20696 });
20697
20698 let snapshot = self.display_snapshot(cx);
20699 self.selections.change_with(&snapshot, |selections| {
20700 for buffer_id in ids_to_fold.iter().copied() {
20701 selections.remove_selections_from_buffer(buffer_id);
20702 }
20703 });
20704
20705 cx.emit(EditorEvent::BufferFoldToggled {
20706 ids: ids_to_fold,
20707 folded: true,
20708 });
20709 cx.notify();
20710 }
20711
20712 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20713 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20714 return;
20715 }
20716 self.display_map.update(cx, |display_map, cx| {
20717 display_map.unfold_buffers([buffer_id], cx);
20718 });
20719 cx.emit(EditorEvent::BufferFoldToggled {
20720 ids: vec![buffer_id],
20721 folded: false,
20722 });
20723 cx.notify();
20724 }
20725
20726 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20727 self.display_map.read(cx).is_buffer_folded(buffer)
20728 }
20729
20730 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20731 if self.buffer().read(cx).is_singleton() {
20732 return false;
20733 }
20734 !self.folded_buffers(cx).is_empty()
20735 }
20736
20737 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20738 self.display_map.read(cx).folded_buffers()
20739 }
20740
20741 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20742 self.display_map.update(cx, |display_map, cx| {
20743 display_map.disable_header_for_buffer(buffer_id, cx);
20744 });
20745 cx.notify();
20746 }
20747
20748 /// Removes any folds with the given ranges.
20749 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20750 &mut self,
20751 ranges: &[Range<T>],
20752 type_id: TypeId,
20753 auto_scroll: bool,
20754 cx: &mut Context<Self>,
20755 ) {
20756 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20757 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20758 });
20759 self.folds_did_change(cx);
20760 }
20761
20762 fn remove_folds_with<T: ToOffset + Clone>(
20763 &mut self,
20764 ranges: &[Range<T>],
20765 auto_scroll: bool,
20766 cx: &mut Context<Self>,
20767 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20768 ) {
20769 if ranges.is_empty() {
20770 return;
20771 }
20772
20773 self.display_map.update(cx, update);
20774
20775 if auto_scroll {
20776 self.request_autoscroll(Autoscroll::fit(), cx);
20777 }
20778
20779 cx.notify();
20780 self.scrollbar_marker_state.dirty = true;
20781 self.active_indent_guides_state.dirty = true;
20782 }
20783
20784 pub fn update_renderer_widths(
20785 &mut self,
20786 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20787 cx: &mut Context<Self>,
20788 ) -> bool {
20789 self.display_map
20790 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20791 }
20792
20793 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20794 self.display_map.read(cx).fold_placeholder.clone()
20795 }
20796
20797 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20798 self.buffer.update(cx, |buffer, cx| {
20799 buffer.set_all_diff_hunks_expanded(cx);
20800 });
20801 }
20802
20803 pub fn expand_all_diff_hunks(
20804 &mut self,
20805 _: &ExpandAllDiffHunks,
20806 _window: &mut Window,
20807 cx: &mut Context<Self>,
20808 ) {
20809 self.buffer.update(cx, |buffer, cx| {
20810 buffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
20811 });
20812 }
20813
20814 pub fn collapse_all_diff_hunks(
20815 &mut self,
20816 _: &CollapseAllDiffHunks,
20817 _window: &mut Window,
20818 cx: &mut Context<Self>,
20819 ) {
20820 self.buffer.update(cx, |buffer, cx| {
20821 buffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
20822 });
20823 }
20824
20825 pub fn toggle_selected_diff_hunks(
20826 &mut self,
20827 _: &ToggleSelectedDiffHunks,
20828 _window: &mut Window,
20829 cx: &mut Context<Self>,
20830 ) {
20831 let ranges: Vec<_> = self
20832 .selections
20833 .disjoint_anchors()
20834 .iter()
20835 .map(|s| s.range())
20836 .collect();
20837 self.toggle_diff_hunks_in_ranges(ranges, cx);
20838 }
20839
20840 pub fn diff_hunks_in_ranges<'a>(
20841 &'a self,
20842 ranges: &'a [Range<Anchor>],
20843 buffer: &'a MultiBufferSnapshot,
20844 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20845 ranges.iter().flat_map(move |range| {
20846 let end_excerpt = buffer.excerpt_containing(range.end..range.end);
20847 let range = range.to_point(buffer);
20848 let mut peek_end = range.end;
20849 if range.end.row < buffer.max_row().0 {
20850 peek_end = Point::new(range.end.row + 1, 0);
20851 }
20852 buffer
20853 .diff_hunks_in_range(range.start..peek_end)
20854 .filter(move |hunk| {
20855 if let Some((_, excerpt_range)) = &end_excerpt
20856 && let Some(end_anchor) =
20857 buffer.anchor_in_excerpt(excerpt_range.context.end)
20858 && let Some(hunk_end_anchor) =
20859 buffer.anchor_in_excerpt(hunk.excerpt_range.context.end)
20860 && hunk_end_anchor.cmp(&end_anchor, buffer).is_gt()
20861 {
20862 false
20863 } else {
20864 true
20865 }
20866 })
20867 })
20868 }
20869
20870 pub fn has_stageable_diff_hunks_in_ranges(
20871 &self,
20872 ranges: &[Range<Anchor>],
20873 snapshot: &MultiBufferSnapshot,
20874 ) -> bool {
20875 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20876 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20877 }
20878
20879 pub fn toggle_staged_selected_diff_hunks(
20880 &mut self,
20881 _: &::git::ToggleStaged,
20882 _: &mut Window,
20883 cx: &mut Context<Self>,
20884 ) {
20885 let snapshot = self.buffer.read(cx).snapshot(cx);
20886 let ranges: Vec<_> = self
20887 .selections
20888 .disjoint_anchors()
20889 .iter()
20890 .map(|s| s.range())
20891 .collect();
20892 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20893 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20894 }
20895
20896 pub fn set_render_diff_hunk_controls(
20897 &mut self,
20898 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20899 cx: &mut Context<Self>,
20900 ) {
20901 self.render_diff_hunk_controls = render_diff_hunk_controls;
20902 cx.notify();
20903 }
20904
20905 pub fn stage_and_next(
20906 &mut self,
20907 _: &::git::StageAndNext,
20908 window: &mut Window,
20909 cx: &mut Context<Self>,
20910 ) {
20911 self.do_stage_or_unstage_and_next(true, window, cx);
20912 }
20913
20914 pub fn unstage_and_next(
20915 &mut self,
20916 _: &::git::UnstageAndNext,
20917 window: &mut Window,
20918 cx: &mut Context<Self>,
20919 ) {
20920 self.do_stage_or_unstage_and_next(false, window, cx);
20921 }
20922
20923 pub fn stage_or_unstage_diff_hunks(
20924 &mut self,
20925 stage: bool,
20926 ranges: Vec<Range<Anchor>>,
20927 cx: &mut Context<Self>,
20928 ) {
20929 if self.delegate_stage_and_restore {
20930 let snapshot = self.buffer.read(cx).snapshot(cx);
20931 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20932 if !hunks.is_empty() {
20933 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20934 }
20935 return;
20936 }
20937 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20938 cx.spawn(async move |this, cx| {
20939 task.await?;
20940 this.update(cx, |this, cx| {
20941 let snapshot = this.buffer.read(cx).snapshot(cx);
20942 let chunk_by = this
20943 .diff_hunks_in_ranges(&ranges, &snapshot)
20944 .chunk_by(|hunk| hunk.buffer_id);
20945 for (buffer_id, hunks) in &chunk_by {
20946 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20947 }
20948 })
20949 })
20950 .detach_and_log_err(cx);
20951 }
20952
20953 fn save_buffers_for_ranges_if_needed(
20954 &mut self,
20955 ranges: &[Range<Anchor>],
20956 cx: &mut Context<Editor>,
20957 ) -> Task<Result<()>> {
20958 let multibuffer = self.buffer.read(cx);
20959 let snapshot = multibuffer.read(cx);
20960 let buffer_ids: HashSet<_> = ranges
20961 .iter()
20962 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20963 .collect();
20964 drop(snapshot);
20965
20966 let mut buffers = HashSet::default();
20967 for buffer_id in buffer_ids {
20968 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20969 let buffer = buffer_entity.read(cx);
20970 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20971 {
20972 buffers.insert(buffer_entity);
20973 }
20974 }
20975 }
20976
20977 if let Some(project) = &self.project {
20978 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20979 } else {
20980 Task::ready(Ok(()))
20981 }
20982 }
20983
20984 fn do_stage_or_unstage_and_next(
20985 &mut self,
20986 stage: bool,
20987 window: &mut Window,
20988 cx: &mut Context<Self>,
20989 ) {
20990 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20991
20992 if ranges.iter().any(|range| range.start != range.end) {
20993 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20994 return;
20995 }
20996
20997 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20998
20999 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
21000 let wrap_around = !all_diff_hunks_expanded;
21001 let snapshot = self.snapshot(window, cx);
21002 let position = self
21003 .selections
21004 .newest::<Point>(&snapshot.display_snapshot)
21005 .head();
21006
21007 self.go_to_hunk_before_or_after_position(
21008 &snapshot,
21009 position,
21010 Direction::Next,
21011 wrap_around,
21012 window,
21013 cx,
21014 );
21015 }
21016
21017 pub(crate) fn do_stage_or_unstage(
21018 &self,
21019 stage: bool,
21020 buffer_id: BufferId,
21021 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
21022 cx: &mut App,
21023 ) -> Option<()> {
21024 let project = self.project()?;
21025 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
21026 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
21027 let buffer_snapshot = buffer.read(cx).snapshot();
21028 let file_exists = buffer_snapshot
21029 .file()
21030 .is_some_and(|file| file.disk_state().exists());
21031 diff.update(cx, |diff, cx| {
21032 diff.stage_or_unstage_hunks(
21033 stage,
21034 &hunks
21035 .map(|hunk| buffer_diff::DiffHunk {
21036 buffer_range: hunk.buffer_range,
21037 // We don't need to pass in word diffs here because they're only used for rendering and
21038 // this function changes internal state
21039 base_word_diffs: Vec::default(),
21040 buffer_word_diffs: Vec::default(),
21041 diff_base_byte_range: hunk.diff_base_byte_range.start.0
21042 ..hunk.diff_base_byte_range.end.0,
21043 secondary_status: hunk.status.secondary,
21044 range: Point::zero()..Point::zero(), // unused
21045 })
21046 .collect::<Vec<_>>(),
21047 &buffer_snapshot,
21048 file_exists,
21049 cx,
21050 )
21051 });
21052 None
21053 }
21054
21055 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
21056 let ranges: Vec<_> = self
21057 .selections
21058 .disjoint_anchors()
21059 .iter()
21060 .map(|s| s.range())
21061 .collect();
21062 self.buffer
21063 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
21064 }
21065
21066 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
21067 self.buffer.update(cx, |buffer, cx| {
21068 let ranges = vec![Anchor::Min..Anchor::Max];
21069 if !buffer.all_diff_hunks_expanded()
21070 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
21071 {
21072 buffer.collapse_diff_hunks(ranges, cx);
21073 true
21074 } else {
21075 false
21076 }
21077 })
21078 }
21079
21080 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
21081 if self.buffer.read(cx).all_diff_hunks_expanded() {
21082 return true;
21083 }
21084 let ranges = vec![Anchor::Min..Anchor::Max];
21085 self.buffer
21086 .read(cx)
21087 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
21088 }
21089
21090 fn toggle_diff_hunks_in_ranges(
21091 &mut self,
21092 ranges: Vec<Range<Anchor>>,
21093 cx: &mut Context<Editor>,
21094 ) {
21095 self.buffer.update(cx, |buffer, cx| {
21096 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
21097 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
21098 })
21099 }
21100
21101 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
21102 self.buffer.update(cx, |buffer, cx| {
21103 buffer.toggle_single_diff_hunk(range, cx);
21104 })
21105 }
21106
21107 pub(crate) fn apply_all_diff_hunks(
21108 &mut self,
21109 _: &ApplyAllDiffHunks,
21110 window: &mut Window,
21111 cx: &mut Context<Self>,
21112 ) {
21113 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21114
21115 let buffers = self.buffer.read(cx).all_buffers();
21116 for branch_buffer in buffers {
21117 branch_buffer.update(cx, |branch_buffer, cx| {
21118 branch_buffer.merge_into_base(Vec::new(), cx);
21119 });
21120 }
21121
21122 if let Some(project) = self.project.clone() {
21123 self.save(
21124 SaveOptions {
21125 format: true,
21126 autosave: false,
21127 },
21128 project,
21129 window,
21130 cx,
21131 )
21132 .detach_and_log_err(cx);
21133 }
21134 }
21135
21136 pub(crate) fn apply_selected_diff_hunks(
21137 &mut self,
21138 _: &ApplyDiffHunk,
21139 window: &mut Window,
21140 cx: &mut Context<Self>,
21141 ) {
21142 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21143 let snapshot = self.snapshot(window, cx);
21144 let hunks = snapshot.hunks_for_ranges(
21145 self.selections
21146 .all(&snapshot.display_snapshot)
21147 .into_iter()
21148 .map(|selection| selection.range()),
21149 );
21150 let mut ranges_by_buffer = HashMap::default();
21151 self.transact(window, cx, |editor, _window, cx| {
21152 for hunk in hunks {
21153 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
21154 ranges_by_buffer
21155 .entry(buffer.clone())
21156 .or_insert_with(Vec::new)
21157 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
21158 }
21159 }
21160
21161 for (buffer, ranges) in ranges_by_buffer {
21162 buffer.update(cx, |buffer, cx| {
21163 buffer.merge_into_base(ranges, cx);
21164 });
21165 }
21166 });
21167
21168 if let Some(project) = self.project.clone() {
21169 self.save(
21170 SaveOptions {
21171 format: true,
21172 autosave: false,
21173 },
21174 project,
21175 window,
21176 cx,
21177 )
21178 .detach_and_log_err(cx);
21179 }
21180 }
21181
21182 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
21183 if hovered != self.gutter_hovered {
21184 self.gutter_hovered = hovered;
21185 cx.notify();
21186 }
21187 }
21188
21189 pub fn insert_blocks(
21190 &mut self,
21191 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
21192 autoscroll: Option<Autoscroll>,
21193 cx: &mut Context<Self>,
21194 ) -> Vec<CustomBlockId> {
21195 let blocks = self
21196 .display_map
21197 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
21198 if let Some(autoscroll) = autoscroll {
21199 self.request_autoscroll(autoscroll, cx);
21200 }
21201 cx.notify();
21202 blocks
21203 }
21204
21205 pub fn resize_blocks(
21206 &mut self,
21207 heights: HashMap<CustomBlockId, u32>,
21208 autoscroll: Option<Autoscroll>,
21209 cx: &mut Context<Self>,
21210 ) {
21211 self.display_map
21212 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21213 if let Some(autoscroll) = autoscroll {
21214 self.request_autoscroll(autoscroll, cx);
21215 }
21216 cx.notify();
21217 }
21218
21219 pub fn replace_blocks(
21220 &mut self,
21221 renderers: HashMap<CustomBlockId, RenderBlock>,
21222 autoscroll: Option<Autoscroll>,
21223 cx: &mut Context<Self>,
21224 ) {
21225 self.display_map
21226 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21227 if let Some(autoscroll) = autoscroll {
21228 self.request_autoscroll(autoscroll, cx);
21229 }
21230 cx.notify();
21231 }
21232
21233 pub fn remove_blocks(
21234 &mut self,
21235 block_ids: HashSet<CustomBlockId>,
21236 autoscroll: Option<Autoscroll>,
21237 cx: &mut Context<Self>,
21238 ) {
21239 self.display_map.update(cx, |display_map, cx| {
21240 display_map.remove_blocks(block_ids, cx)
21241 });
21242 if let Some(autoscroll) = autoscroll {
21243 self.request_autoscroll(autoscroll, cx);
21244 }
21245 cx.notify();
21246 }
21247
21248 pub fn row_for_block(
21249 &self,
21250 block_id: CustomBlockId,
21251 cx: &mut Context<Self>,
21252 ) -> Option<DisplayRow> {
21253 self.display_map
21254 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21255 }
21256
21257 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21258 self.focused_block = Some(focused_block);
21259 }
21260
21261 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21262 self.focused_block.take()
21263 }
21264
21265 pub fn insert_creases(
21266 &mut self,
21267 creases: impl IntoIterator<Item = Crease<Anchor>>,
21268 cx: &mut Context<Self>,
21269 ) -> Vec<CreaseId> {
21270 self.display_map
21271 .update(cx, |map, cx| map.insert_creases(creases, cx))
21272 }
21273
21274 pub fn remove_creases(
21275 &mut self,
21276 ids: impl IntoIterator<Item = CreaseId>,
21277 cx: &mut Context<Self>,
21278 ) -> Vec<(CreaseId, Range<Anchor>)> {
21279 self.display_map
21280 .update(cx, |map, cx| map.remove_creases(ids, cx))
21281 }
21282
21283 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21284 self.display_map
21285 .update(cx, |map, cx| map.snapshot(cx))
21286 .longest_row()
21287 }
21288
21289 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21290 self.display_map
21291 .update(cx, |map, cx| map.snapshot(cx))
21292 .max_point()
21293 }
21294
21295 pub fn text(&self, cx: &App) -> String {
21296 self.buffer.read(cx).read(cx).text()
21297 }
21298
21299 pub fn is_empty(&self, cx: &App) -> bool {
21300 self.buffer.read(cx).read(cx).is_empty()
21301 }
21302
21303 pub fn text_option(&self, cx: &App) -> Option<String> {
21304 let text = self.text(cx);
21305 let text = text.trim();
21306
21307 if text.is_empty() {
21308 return None;
21309 }
21310
21311 Some(text.to_string())
21312 }
21313
21314 pub fn set_text(
21315 &mut self,
21316 text: impl Into<Arc<str>>,
21317 window: &mut Window,
21318 cx: &mut Context<Self>,
21319 ) {
21320 self.transact(window, cx, |this, _, cx| {
21321 this.buffer
21322 .read(cx)
21323 .as_singleton()
21324 .expect("you can only call set_text on editors for singleton buffers")
21325 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21326 });
21327 }
21328
21329 pub fn display_text(&self, cx: &mut App) -> String {
21330 self.display_map
21331 .update(cx, |map, cx| map.snapshot(cx))
21332 .text()
21333 }
21334
21335 fn create_minimap(
21336 &self,
21337 minimap_settings: MinimapSettings,
21338 window: &mut Window,
21339 cx: &mut Context<Self>,
21340 ) -> Option<Entity<Self>> {
21341 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21342 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21343 }
21344
21345 fn initialize_new_minimap(
21346 &self,
21347 minimap_settings: MinimapSettings,
21348 window: &mut Window,
21349 cx: &mut Context<Self>,
21350 ) -> Entity<Self> {
21351 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21352 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21353
21354 let mut minimap = Editor::new_internal(
21355 EditorMode::Minimap {
21356 parent: cx.weak_entity(),
21357 },
21358 self.buffer.clone(),
21359 None,
21360 Some(self.display_map.clone()),
21361 window,
21362 cx,
21363 );
21364 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21365 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21366 minimap.scroll_manager.clone_state(
21367 &self.scroll_manager,
21368 &my_snapshot,
21369 &minimap_snapshot,
21370 cx,
21371 );
21372 minimap.set_text_style_refinement(TextStyleRefinement {
21373 font_size: Some(MINIMAP_FONT_SIZE),
21374 font_weight: Some(MINIMAP_FONT_WEIGHT),
21375 font_family: Some(MINIMAP_FONT_FAMILY),
21376 ..Default::default()
21377 });
21378 minimap.update_minimap_configuration(minimap_settings, cx);
21379 cx.new(|_| minimap)
21380 }
21381
21382 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21383 let current_line_highlight = minimap_settings
21384 .current_line_highlight
21385 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21386 self.set_current_line_highlight(Some(current_line_highlight));
21387 }
21388
21389 pub fn minimap(&self) -> Option<&Entity<Self>> {
21390 self.minimap
21391 .as_ref()
21392 .filter(|_| self.minimap_visibility.visible())
21393 }
21394
21395 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21396 let mut wrap_guides = smallvec![];
21397
21398 if self.show_wrap_guides == Some(false) {
21399 return wrap_guides;
21400 }
21401
21402 let settings = self.buffer.read(cx).language_settings(cx);
21403 if settings.show_wrap_guides {
21404 match self.soft_wrap_mode(cx) {
21405 SoftWrap::Column(soft_wrap) => {
21406 wrap_guides.push((soft_wrap as usize, true));
21407 }
21408 SoftWrap::Bounded(soft_wrap) => {
21409 wrap_guides.push((soft_wrap as usize, true));
21410 }
21411 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21412 }
21413 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21414 }
21415
21416 wrap_guides
21417 }
21418
21419 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21420 let settings = self.buffer.read(cx).language_settings(cx);
21421 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21422 match mode {
21423 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21424 SoftWrap::None
21425 }
21426 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21427 language_settings::SoftWrap::PreferredLineLength => {
21428 SoftWrap::Column(settings.preferred_line_length)
21429 }
21430 language_settings::SoftWrap::Bounded => {
21431 SoftWrap::Bounded(settings.preferred_line_length)
21432 }
21433 }
21434 }
21435
21436 pub fn set_soft_wrap_mode(
21437 &mut self,
21438 mode: language_settings::SoftWrap,
21439 cx: &mut Context<Self>,
21440 ) {
21441 self.soft_wrap_mode_override = Some(mode);
21442 cx.notify();
21443 }
21444
21445 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21446 self.hard_wrap = hard_wrap;
21447 cx.notify();
21448 }
21449
21450 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21451 self.text_style_refinement = Some(style);
21452 }
21453
21454 /// called by the Element so we know what style we were most recently rendered with.
21455 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21456 // We intentionally do not inform the display map about the minimap style
21457 // so that wrapping is not recalculated and stays consistent for the editor
21458 // and its linked minimap.
21459 if !self.mode.is_minimap() {
21460 let font = style.text.font();
21461 let font_size = style.text.font_size.to_pixels(window.rem_size());
21462 let display_map = self
21463 .placeholder_display_map
21464 .as_ref()
21465 .filter(|_| self.is_empty(cx))
21466 .unwrap_or(&self.display_map);
21467
21468 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21469 }
21470 self.style = Some(style);
21471 }
21472
21473 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21474 if self.style.is_none() {
21475 self.style = Some(self.create_style(cx));
21476 }
21477 self.style.as_ref().unwrap()
21478 }
21479
21480 // Called by the element. This method is not designed to be called outside of the editor
21481 // element's layout code because it does not notify when rewrapping is computed synchronously.
21482 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21483 if self.is_empty(cx) {
21484 self.placeholder_display_map
21485 .as_ref()
21486 .map_or(false, |display_map| {
21487 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21488 })
21489 } else {
21490 self.display_map
21491 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21492 }
21493 }
21494
21495 pub fn set_soft_wrap(&mut self) {
21496 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21497 }
21498
21499 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21500 if self.soft_wrap_mode_override.is_some() {
21501 self.soft_wrap_mode_override.take();
21502 } else {
21503 let soft_wrap = match self.soft_wrap_mode(cx) {
21504 SoftWrap::GitDiff => return,
21505 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21506 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21507 language_settings::SoftWrap::None
21508 }
21509 };
21510 self.soft_wrap_mode_override = Some(soft_wrap);
21511 }
21512 cx.notify();
21513 }
21514
21515 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21516 let Some(workspace) = self.workspace() else {
21517 return;
21518 };
21519 let fs = workspace.read(cx).app_state().fs.clone();
21520 let current_show = TabBarSettings::get_global(cx).show;
21521 update_settings_file(fs, cx, move |setting, _| {
21522 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21523 });
21524 }
21525
21526 pub fn toggle_indent_guides(
21527 &mut self,
21528 _: &ToggleIndentGuides,
21529 _: &mut Window,
21530 cx: &mut Context<Self>,
21531 ) {
21532 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21533 self.buffer
21534 .read(cx)
21535 .language_settings(cx)
21536 .indent_guides
21537 .enabled
21538 });
21539 self.show_indent_guides = Some(!currently_enabled);
21540 cx.notify();
21541 }
21542
21543 fn should_show_indent_guides(&self) -> Option<bool> {
21544 self.show_indent_guides
21545 }
21546
21547 pub fn disable_indent_guides_for_buffer(
21548 &mut self,
21549 buffer_id: BufferId,
21550 cx: &mut Context<Self>,
21551 ) {
21552 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21553 cx.notify();
21554 }
21555
21556 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21557 self.buffers_with_disabled_indent_guides
21558 .contains(&buffer_id)
21559 }
21560
21561 pub fn toggle_line_numbers(
21562 &mut self,
21563 _: &ToggleLineNumbers,
21564 _: &mut Window,
21565 cx: &mut Context<Self>,
21566 ) {
21567 let mut editor_settings = EditorSettings::get_global(cx).clone();
21568 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21569 EditorSettings::override_global(editor_settings, cx);
21570 }
21571
21572 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21573 if let Some(show_line_numbers) = self.show_line_numbers {
21574 return show_line_numbers;
21575 }
21576 EditorSettings::get_global(cx).gutter.line_numbers
21577 }
21578
21579 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21580 match (
21581 self.use_relative_line_numbers,
21582 EditorSettings::get_global(cx).relative_line_numbers,
21583 ) {
21584 (None, setting) => setting,
21585 (Some(false), _) => RelativeLineNumbers::Disabled,
21586 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21587 (Some(true), _) => RelativeLineNumbers::Enabled,
21588 }
21589 }
21590
21591 pub fn toggle_relative_line_numbers(
21592 &mut self,
21593 _: &ToggleRelativeLineNumbers,
21594 _: &mut Window,
21595 cx: &mut Context<Self>,
21596 ) {
21597 let is_relative = self.relative_line_numbers(cx);
21598 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21599 }
21600
21601 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21602 self.use_relative_line_numbers = is_relative;
21603 cx.notify();
21604 }
21605
21606 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21607 self.show_gutter = show_gutter;
21608 cx.notify();
21609 }
21610
21611 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21612 self.show_scrollbars = ScrollbarAxes {
21613 horizontal: show,
21614 vertical: show,
21615 };
21616 cx.notify();
21617 }
21618
21619 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21620 self.show_scrollbars.vertical = show;
21621 cx.notify();
21622 }
21623
21624 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21625 self.show_scrollbars.horizontal = show;
21626 cx.notify();
21627 }
21628
21629 pub fn set_minimap_visibility(
21630 &mut self,
21631 minimap_visibility: MinimapVisibility,
21632 window: &mut Window,
21633 cx: &mut Context<Self>,
21634 ) {
21635 if self.minimap_visibility != minimap_visibility {
21636 if minimap_visibility.visible() && self.minimap.is_none() {
21637 let minimap_settings = EditorSettings::get_global(cx).minimap;
21638 self.minimap =
21639 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21640 }
21641 self.minimap_visibility = minimap_visibility;
21642 cx.notify();
21643 }
21644 }
21645
21646 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21647 self.set_show_scrollbars(false, cx);
21648 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21649 }
21650
21651 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21652 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21653 }
21654
21655 /// Normally the text in full mode and auto height editors is padded on the
21656 /// left side by roughly half a character width for improved hit testing.
21657 ///
21658 /// Use this method to disable this for cases where this is not wanted (e.g.
21659 /// if you want to align the editor text with some other text above or below)
21660 /// or if you want to add this padding to single-line editors.
21661 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21662 self.offset_content = offset_content;
21663 cx.notify();
21664 }
21665
21666 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21667 self.show_line_numbers = Some(show_line_numbers);
21668 cx.notify();
21669 }
21670
21671 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21672 self.disable_expand_excerpt_buttons = true;
21673 cx.notify();
21674 }
21675
21676 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21677 self.number_deleted_lines = number;
21678 cx.notify();
21679 }
21680
21681 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21682 self.delegate_expand_excerpts = delegate;
21683 }
21684
21685 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21686 self.delegate_stage_and_restore = delegate;
21687 }
21688
21689 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21690 self.delegate_open_excerpts = delegate;
21691 }
21692
21693 pub fn set_on_local_selections_changed(
21694 &mut self,
21695 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21696 ) {
21697 self.on_local_selections_changed = callback;
21698 }
21699
21700 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21701 self.suppress_selection_callback = suppress;
21702 }
21703
21704 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21705 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21706 cx.notify();
21707 }
21708
21709 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21710 self.show_code_actions = Some(show_code_actions);
21711 cx.notify();
21712 }
21713
21714 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21715 self.show_runnables = Some(show_runnables);
21716 cx.notify();
21717 }
21718
21719 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21720 self.show_breakpoints = Some(show_breakpoints);
21721 cx.notify();
21722 }
21723
21724 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21725 self.show_diff_review_button = show;
21726 cx.notify();
21727 }
21728
21729 pub fn show_diff_review_button(&self) -> bool {
21730 self.show_diff_review_button
21731 }
21732
21733 pub fn render_diff_review_button(
21734 &self,
21735 display_row: DisplayRow,
21736 width: Pixels,
21737 cx: &mut Context<Self>,
21738 ) -> impl IntoElement {
21739 let text_color = cx.theme().colors().text;
21740 let icon_color = cx.theme().colors().icon_accent;
21741
21742 h_flex()
21743 .id("diff_review_button")
21744 .cursor_pointer()
21745 .w(width - px(1.))
21746 .h(relative(0.9))
21747 .justify_center()
21748 .rounded_sm()
21749 .border_1()
21750 .border_color(text_color.opacity(0.1))
21751 .bg(text_color.opacity(0.15))
21752 .hover(|s| {
21753 s.bg(icon_color.opacity(0.4))
21754 .border_color(icon_color.opacity(0.5))
21755 })
21756 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21757 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21758 .on_mouse_down(
21759 gpui::MouseButton::Left,
21760 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21761 editor.start_diff_review_drag(display_row, window, cx);
21762 }),
21763 )
21764 }
21765
21766 pub fn start_diff_review_drag(
21767 &mut self,
21768 display_row: DisplayRow,
21769 window: &mut Window,
21770 cx: &mut Context<Self>,
21771 ) {
21772 let snapshot = self.snapshot(window, cx);
21773 let point = snapshot
21774 .display_snapshot
21775 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21776 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21777 self.diff_review_drag_state = Some(DiffReviewDragState {
21778 start_anchor: anchor,
21779 current_anchor: anchor,
21780 });
21781 cx.notify();
21782 }
21783
21784 pub fn update_diff_review_drag(
21785 &mut self,
21786 display_row: DisplayRow,
21787 window: &mut Window,
21788 cx: &mut Context<Self>,
21789 ) {
21790 if self.diff_review_drag_state.is_none() {
21791 return;
21792 }
21793 let snapshot = self.snapshot(window, cx);
21794 let point = snapshot
21795 .display_snapshot
21796 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21797 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21798 if let Some(drag_state) = &mut self.diff_review_drag_state {
21799 drag_state.current_anchor = anchor;
21800 cx.notify();
21801 }
21802 }
21803
21804 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21805 if let Some(drag_state) = self.diff_review_drag_state.take() {
21806 let snapshot = self.snapshot(window, cx);
21807 let range = drag_state.row_range(&snapshot.display_snapshot);
21808 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21809 }
21810 cx.notify();
21811 }
21812
21813 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21814 self.diff_review_drag_state = None;
21815 cx.notify();
21816 }
21817
21818 /// Calculates the appropriate block height for the diff review overlay.
21819 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21820 /// and 2 lines per comment when expanded.
21821 fn calculate_overlay_height(
21822 &self,
21823 hunk_key: &DiffHunkKey,
21824 comments_expanded: bool,
21825 snapshot: &MultiBufferSnapshot,
21826 ) -> u32 {
21827 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21828 let base_height: u32 = 2; // Input row with avatar and buttons
21829
21830 if comment_count == 0 {
21831 base_height
21832 } else if comments_expanded {
21833 // Header (1 line) + 2 lines per comment
21834 base_height + 1 + (comment_count as u32 * 2)
21835 } else {
21836 // Just header when collapsed
21837 base_height + 1
21838 }
21839 }
21840
21841 pub fn show_diff_review_overlay(
21842 &mut self,
21843 display_range: Range<DisplayRow>,
21844 window: &mut Window,
21845 cx: &mut Context<Self>,
21846 ) {
21847 let Range { start, end } = display_range.sorted();
21848
21849 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21850 let editor_snapshot = self.snapshot(window, cx);
21851
21852 // Convert display rows to multibuffer points
21853 let start_point = editor_snapshot
21854 .display_snapshot
21855 .display_point_to_point(start.as_display_point(), Bias::Left);
21856 let end_point = editor_snapshot
21857 .display_snapshot
21858 .display_point_to_point(end.as_display_point(), Bias::Left);
21859 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21860
21861 // Create anchor range for the selected lines (start of first line to end of last line)
21862 let line_end = Point::new(
21863 end_point.row,
21864 buffer_snapshot.line_len(end_multi_buffer_row),
21865 );
21866 let anchor_range =
21867 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21868
21869 // Compute the hunk key for this display row
21870 let file_path = buffer_snapshot
21871 .file_at(start_point)
21872 .map(|file: &Arc<dyn language::File>| file.path().clone())
21873 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21874 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21875 let new_hunk_key = DiffHunkKey {
21876 file_path,
21877 hunk_start_anchor,
21878 };
21879
21880 // Check if we already have an overlay for this hunk
21881 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21882 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21883 }) {
21884 // Just focus the existing overlay's prompt editor
21885 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21886 window.focus(&focus_handle, cx);
21887 return;
21888 }
21889
21890 // Dismiss overlays that have no comments for their hunks
21891 self.dismiss_overlays_without_comments(cx);
21892
21893 // Get the current user's avatar URI from the project's user_store
21894 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21895 let user_store = project.read(cx).user_store();
21896 user_store
21897 .read(cx)
21898 .current_user()
21899 .map(|user| user.avatar_uri.clone())
21900 });
21901
21902 // Create anchor at the end of the last row so the block appears immediately below it
21903 // Use multibuffer coordinates for anchor creation
21904 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21905 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21906
21907 // Use the hunk key we already computed
21908 let hunk_key = new_hunk_key;
21909
21910 // Create the prompt editor for the review input
21911 let prompt_editor = cx.new(|cx| {
21912 let mut editor = Editor::single_line(window, cx);
21913 editor.set_placeholder_text("Add a review comment...", window, cx);
21914 editor
21915 });
21916
21917 // Register the Newline action on the prompt editor to submit the review
21918 let parent_editor = cx.entity().downgrade();
21919 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21920 prompt_editor.register_action({
21921 let parent_editor = parent_editor.clone();
21922 move |_: &crate::actions::Newline, window, cx| {
21923 if let Some(editor) = parent_editor.upgrade() {
21924 editor.update(cx, |editor, cx| {
21925 editor.submit_diff_review_comment(window, cx);
21926 });
21927 }
21928 }
21929 })
21930 });
21931
21932 // Calculate initial height based on existing comments for this hunk
21933 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21934
21935 // Create the overlay block
21936 let prompt_editor_for_render = prompt_editor.clone();
21937 let hunk_key_for_render = hunk_key.clone();
21938 let editor_handle = cx.entity().downgrade();
21939 let block = BlockProperties {
21940 style: BlockStyle::Sticky,
21941 placement: BlockPlacement::Below(anchor),
21942 height: Some(initial_height),
21943 render: Arc::new(move |cx| {
21944 Self::render_diff_review_overlay(
21945 &prompt_editor_for_render,
21946 &hunk_key_for_render,
21947 &editor_handle,
21948 cx,
21949 )
21950 }),
21951 priority: 0,
21952 };
21953
21954 let block_ids = self.insert_blocks([block], None, cx);
21955 let Some(block_id) = block_ids.into_iter().next() else {
21956 log::error!("Failed to insert diff review overlay block");
21957 return;
21958 };
21959
21960 self.diff_review_overlays.push(DiffReviewOverlay {
21961 anchor_range,
21962 block_id,
21963 prompt_editor: prompt_editor.clone(),
21964 hunk_key,
21965 comments_expanded: true,
21966 inline_edit_editors: HashMap::default(),
21967 inline_edit_subscriptions: HashMap::default(),
21968 user_avatar_uri,
21969 _subscription: subscription,
21970 });
21971
21972 // Focus the prompt editor
21973 let focus_handle = prompt_editor.focus_handle(cx);
21974 window.focus(&focus_handle, cx);
21975
21976 cx.notify();
21977 }
21978
21979 /// Dismisses all diff review overlays.
21980 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21981 if self.diff_review_overlays.is_empty() {
21982 return;
21983 }
21984 let block_ids: HashSet<_> = self
21985 .diff_review_overlays
21986 .drain(..)
21987 .map(|overlay| overlay.block_id)
21988 .collect();
21989 self.remove_blocks(block_ids, None, cx);
21990 cx.notify();
21991 }
21992
21993 /// Dismisses overlays that have no comments stored for their hunks.
21994 /// Keeps overlays that have at least one comment.
21995 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21996 let snapshot = self.buffer.read(cx).snapshot(cx);
21997
21998 // First, compute which overlays have comments (to avoid borrow issues with retain)
21999 let overlays_with_comments: Vec<bool> = self
22000 .diff_review_overlays
22001 .iter()
22002 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
22003 .collect();
22004
22005 // Now collect block IDs to remove and retain overlays
22006 let mut block_ids_to_remove = HashSet::default();
22007 let mut index = 0;
22008 self.diff_review_overlays.retain(|overlay| {
22009 let has_comments = overlays_with_comments[index];
22010 index += 1;
22011 if !has_comments {
22012 block_ids_to_remove.insert(overlay.block_id);
22013 }
22014 has_comments
22015 });
22016
22017 if !block_ids_to_remove.is_empty() {
22018 self.remove_blocks(block_ids_to_remove, None, cx);
22019 cx.notify();
22020 }
22021 }
22022
22023 /// Refreshes the diff review overlay block to update its height and render function.
22024 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
22025 fn refresh_diff_review_overlay_height(
22026 &mut self,
22027 hunk_key: &DiffHunkKey,
22028 _window: &mut Window,
22029 cx: &mut Context<Self>,
22030 ) {
22031 // Extract all needed data from overlay first to avoid borrow conflicts
22032 let snapshot = self.buffer.read(cx).snapshot(cx);
22033 let (comments_expanded, block_id, prompt_editor) = {
22034 let Some(overlay) = self
22035 .diff_review_overlays
22036 .iter()
22037 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22038 else {
22039 return;
22040 };
22041
22042 (
22043 overlay.comments_expanded,
22044 overlay.block_id,
22045 overlay.prompt_editor.clone(),
22046 )
22047 };
22048
22049 // Calculate new height
22050 let snapshot = self.buffer.read(cx).snapshot(cx);
22051 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
22052
22053 // Update the block height using resize_blocks (avoids flicker)
22054 let mut heights = HashMap::default();
22055 heights.insert(block_id, new_height);
22056 self.resize_blocks(heights, None, cx);
22057
22058 // Update the render function using replace_blocks (avoids flicker)
22059 let hunk_key_for_render = hunk_key.clone();
22060 let editor_handle = cx.entity().downgrade();
22061 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
22062 Arc::new(move |cx| {
22063 Self::render_diff_review_overlay(
22064 &prompt_editor,
22065 &hunk_key_for_render,
22066 &editor_handle,
22067 cx,
22068 )
22069 });
22070
22071 let mut renderers = HashMap::default();
22072 renderers.insert(block_id, render);
22073 self.replace_blocks(renderers, None, cx);
22074 }
22075
22076 /// Action handler for SubmitDiffReviewComment.
22077 pub fn submit_diff_review_comment_action(
22078 &mut self,
22079 _: &SubmitDiffReviewComment,
22080 window: &mut Window,
22081 cx: &mut Context<Self>,
22082 ) {
22083 self.submit_diff_review_comment(window, cx);
22084 }
22085
22086 /// Stores the diff review comment locally.
22087 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
22088 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22089 // Find the overlay that currently has focus
22090 let overlay_index = self
22091 .diff_review_overlays
22092 .iter()
22093 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
22094 let Some(overlay_index) = overlay_index else {
22095 return;
22096 };
22097 let overlay = &self.diff_review_overlays[overlay_index];
22098
22099 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
22100 if comment_text.is_empty() {
22101 return;
22102 }
22103
22104 let anchor_range = overlay.anchor_range.clone();
22105 let hunk_key = overlay.hunk_key.clone();
22106
22107 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
22108
22109 // Clear the prompt editor but keep the overlay open
22110 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
22111 overlay.prompt_editor.update(cx, |editor, cx| {
22112 editor.clear(window, cx);
22113 });
22114 }
22115
22116 // Refresh the overlay to update the block height for the new comment
22117 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22118
22119 cx.notify();
22120 }
22121
22122 /// Returns the prompt editor for the diff review overlay, if one is active.
22123 /// This is primarily used for testing.
22124 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
22125 self.diff_review_overlays
22126 .first()
22127 .map(|overlay| &overlay.prompt_editor)
22128 }
22129
22130 /// Returns the line range for the first diff review overlay, if one is active.
22131 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
22132 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
22133 let overlay = self.diff_review_overlays.first()?;
22134 let snapshot = self.buffer.read(cx).snapshot(cx);
22135 let start_point = overlay.anchor_range.start.to_point(&snapshot);
22136 let end_point = overlay.anchor_range.end.to_point(&snapshot);
22137 let start_row = snapshot
22138 .point_to_buffer_point(start_point)
22139 .map(|(_, p)| p.row)
22140 .unwrap_or(start_point.row);
22141 let end_row = snapshot
22142 .point_to_buffer_point(end_point)
22143 .map(|(_, p)| p.row)
22144 .unwrap_or(end_point.row);
22145 Some((start_row, end_row))
22146 }
22147
22148 /// Sets whether the comments section is expanded in the diff review overlay.
22149 /// This is primarily used for testing.
22150 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
22151 for overlay in &mut self.diff_review_overlays {
22152 overlay.comments_expanded = expanded;
22153 }
22154 cx.notify();
22155 }
22156
22157 /// Compares two DiffHunkKeys for equality by resolving their anchors.
22158 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
22159 a.file_path == b.file_path
22160 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
22161 }
22162
22163 /// Returns comments for a specific hunk, ordered by creation time.
22164 pub fn comments_for_hunk<'a>(
22165 &'a self,
22166 key: &DiffHunkKey,
22167 snapshot: &MultiBufferSnapshot,
22168 ) -> &'a [StoredReviewComment] {
22169 let key_point = key.hunk_start_anchor.to_point(snapshot);
22170 self.stored_review_comments
22171 .iter()
22172 .find(|(k, _)| {
22173 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22174 })
22175 .map(|(_, comments)| comments.as_slice())
22176 .unwrap_or(&[])
22177 }
22178
22179 /// Returns the total count of stored review comments across all hunks.
22180 pub fn total_review_comment_count(&self) -> usize {
22181 self.stored_review_comments
22182 .iter()
22183 .map(|(_, v)| v.len())
22184 .sum()
22185 }
22186
22187 /// Returns the count of comments for a specific hunk.
22188 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
22189 let key_point = key.hunk_start_anchor.to_point(snapshot);
22190 self.stored_review_comments
22191 .iter()
22192 .find(|(k, _)| {
22193 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22194 })
22195 .map(|(_, v)| v.len())
22196 .unwrap_or(0)
22197 }
22198
22199 /// Adds a new review comment to a specific hunk.
22200 pub fn add_review_comment(
22201 &mut self,
22202 hunk_key: DiffHunkKey,
22203 comment: String,
22204 anchor_range: Range<Anchor>,
22205 cx: &mut Context<Self>,
22206 ) -> usize {
22207 let id = self.next_review_comment_id;
22208 self.next_review_comment_id += 1;
22209
22210 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22211
22212 let snapshot = self.buffer.read(cx).snapshot(cx);
22213 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22214
22215 // Find existing entry for this hunk or add a new one
22216 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22217 k.file_path == hunk_key.file_path
22218 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22219 }) {
22220 comments.push(stored_comment);
22221 } else {
22222 self.stored_review_comments
22223 .push((hunk_key, vec![stored_comment]));
22224 }
22225
22226 cx.emit(EditorEvent::ReviewCommentsChanged {
22227 total_count: self.total_review_comment_count(),
22228 });
22229 cx.notify();
22230 id
22231 }
22232
22233 /// Removes a review comment by ID from any hunk.
22234 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22235 for (_, comments) in self.stored_review_comments.iter_mut() {
22236 if let Some(index) = comments.iter().position(|c| c.id == id) {
22237 comments.remove(index);
22238 cx.emit(EditorEvent::ReviewCommentsChanged {
22239 total_count: self.total_review_comment_count(),
22240 });
22241 cx.notify();
22242 return true;
22243 }
22244 }
22245 false
22246 }
22247
22248 /// Updates a review comment's text by ID.
22249 pub fn update_review_comment(
22250 &mut self,
22251 id: usize,
22252 new_comment: String,
22253 cx: &mut Context<Self>,
22254 ) -> bool {
22255 for (_, comments) in self.stored_review_comments.iter_mut() {
22256 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22257 comment.comment = new_comment;
22258 comment.is_editing = false;
22259 cx.emit(EditorEvent::ReviewCommentsChanged {
22260 total_count: self.total_review_comment_count(),
22261 });
22262 cx.notify();
22263 return true;
22264 }
22265 }
22266 false
22267 }
22268
22269 /// Sets a comment's editing state.
22270 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22271 for (_, comments) in self.stored_review_comments.iter_mut() {
22272 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22273 comment.is_editing = is_editing;
22274 cx.notify();
22275 return;
22276 }
22277 }
22278 }
22279
22280 /// Takes all stored comments from all hunks, clearing the storage.
22281 /// Returns a Vec of (hunk_key, comments) pairs.
22282 pub fn take_all_review_comments(
22283 &mut self,
22284 cx: &mut Context<Self>,
22285 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22286 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22287 self.dismiss_all_diff_review_overlays(cx);
22288 let comments = std::mem::take(&mut self.stored_review_comments);
22289 // Reset the ID counter since all comments have been taken
22290 self.next_review_comment_id = 0;
22291 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22292 cx.notify();
22293 comments
22294 }
22295
22296 /// Removes review comments whose anchors are no longer valid or whose
22297 /// associated diff hunks no longer exist.
22298 ///
22299 /// This should be called when the buffer changes to prevent orphaned comments
22300 /// from accumulating.
22301 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22302 let snapshot = self.buffer.read(cx).snapshot(cx);
22303 let original_count = self.total_review_comment_count();
22304
22305 // Remove comments with invalid hunk anchors
22306 self.stored_review_comments
22307 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22308
22309 // Also clean up individual comments with invalid anchor ranges
22310 for (_, comments) in &mut self.stored_review_comments {
22311 comments.retain(|comment| {
22312 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22313 });
22314 }
22315
22316 // Remove empty hunk entries
22317 self.stored_review_comments
22318 .retain(|(_, comments)| !comments.is_empty());
22319
22320 let new_count = self.total_review_comment_count();
22321 if new_count != original_count {
22322 cx.emit(EditorEvent::ReviewCommentsChanged {
22323 total_count: new_count,
22324 });
22325 cx.notify();
22326 }
22327 }
22328
22329 /// Toggles the expanded state of the comments section in the overlay.
22330 pub fn toggle_review_comments_expanded(
22331 &mut self,
22332 _: &ToggleReviewCommentsExpanded,
22333 window: &mut Window,
22334 cx: &mut Context<Self>,
22335 ) {
22336 // Find the overlay that currently has focus, or use the first one
22337 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22338 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22339 overlay.comments_expanded = !overlay.comments_expanded;
22340 Some(overlay.hunk_key.clone())
22341 } else {
22342 None
22343 }
22344 });
22345
22346 // If no focused overlay found, toggle the first one
22347 let hunk_key = overlay_info.or_else(|| {
22348 self.diff_review_overlays.first_mut().map(|overlay| {
22349 overlay.comments_expanded = !overlay.comments_expanded;
22350 overlay.hunk_key.clone()
22351 })
22352 });
22353
22354 if let Some(hunk_key) = hunk_key {
22355 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22356 cx.notify();
22357 }
22358 }
22359
22360 /// Handles the EditReviewComment action - sets a comment into editing mode.
22361 pub fn edit_review_comment(
22362 &mut self,
22363 action: &EditReviewComment,
22364 window: &mut Window,
22365 cx: &mut Context<Self>,
22366 ) {
22367 let comment_id = action.id;
22368
22369 // Set the comment to editing mode
22370 self.set_comment_editing(comment_id, true, cx);
22371
22372 // Find the overlay that contains this comment and create an inline editor if needed
22373 // First, find which hunk this comment belongs to
22374 let hunk_key = self
22375 .stored_review_comments
22376 .iter()
22377 .find_map(|(key, comments)| {
22378 if comments.iter().any(|c| c.id == comment_id) {
22379 Some(key.clone())
22380 } else {
22381 None
22382 }
22383 });
22384
22385 let snapshot = self.buffer.read(cx).snapshot(cx);
22386 if let Some(hunk_key) = hunk_key {
22387 if let Some(overlay) = self
22388 .diff_review_overlays
22389 .iter_mut()
22390 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22391 {
22392 if let std::collections::hash_map::Entry::Vacant(entry) =
22393 overlay.inline_edit_editors.entry(comment_id)
22394 {
22395 // Find the comment text
22396 let comment_text = self
22397 .stored_review_comments
22398 .iter()
22399 .flat_map(|(_, comments)| comments)
22400 .find(|c| c.id == comment_id)
22401 .map(|c| c.comment.clone())
22402 .unwrap_or_default();
22403
22404 // Create inline editor
22405 let parent_editor = cx.entity().downgrade();
22406 let inline_editor = cx.new(|cx| {
22407 let mut editor = Editor::single_line(window, cx);
22408 editor.set_text(&*comment_text, window, cx);
22409 // Select all text for easy replacement
22410 editor.select_all(&crate::actions::SelectAll, window, cx);
22411 editor
22412 });
22413
22414 // Register the Newline action to confirm the edit
22415 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22416 inline_editor.register_action({
22417 let parent_editor = parent_editor.clone();
22418 move |_: &crate::actions::Newline, window, cx| {
22419 if let Some(editor) = parent_editor.upgrade() {
22420 editor.update(cx, |editor, cx| {
22421 editor.confirm_edit_review_comment(comment_id, window, cx);
22422 });
22423 }
22424 }
22425 })
22426 });
22427
22428 // Store the subscription to keep the action handler alive
22429 overlay
22430 .inline_edit_subscriptions
22431 .insert(comment_id, subscription);
22432
22433 // Focus the inline editor
22434 let focus_handle = inline_editor.focus_handle(cx);
22435 window.focus(&focus_handle, cx);
22436
22437 entry.insert(inline_editor);
22438 }
22439 }
22440 }
22441
22442 cx.notify();
22443 }
22444
22445 /// Confirms an inline edit of a review comment.
22446 pub fn confirm_edit_review_comment(
22447 &mut self,
22448 comment_id: usize,
22449 _window: &mut Window,
22450 cx: &mut Context<Self>,
22451 ) {
22452 // Get the new text from the inline editor
22453 // Find the overlay containing this comment's inline editor
22454 let snapshot = self.buffer.read(cx).snapshot(cx);
22455 let hunk_key = self
22456 .stored_review_comments
22457 .iter()
22458 .find_map(|(key, comments)| {
22459 if comments.iter().any(|c| c.id == comment_id) {
22460 Some(key.clone())
22461 } else {
22462 None
22463 }
22464 });
22465
22466 let new_text = hunk_key
22467 .as_ref()
22468 .and_then(|hunk_key| {
22469 self.diff_review_overlays
22470 .iter()
22471 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22472 })
22473 .as_ref()
22474 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22475 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22476
22477 if let Some(new_text) = new_text {
22478 if !new_text.is_empty() {
22479 self.update_review_comment(comment_id, new_text, cx);
22480 }
22481 }
22482
22483 // Remove the inline editor and its subscription
22484 if let Some(hunk_key) = hunk_key {
22485 if let Some(overlay) = self
22486 .diff_review_overlays
22487 .iter_mut()
22488 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22489 {
22490 overlay.inline_edit_editors.remove(&comment_id);
22491 overlay.inline_edit_subscriptions.remove(&comment_id);
22492 }
22493 }
22494
22495 // Clear editing state
22496 self.set_comment_editing(comment_id, false, cx);
22497 }
22498
22499 /// Cancels an inline edit of a review comment.
22500 pub fn cancel_edit_review_comment(
22501 &mut self,
22502 comment_id: usize,
22503 _window: &mut Window,
22504 cx: &mut Context<Self>,
22505 ) {
22506 // Find which hunk this comment belongs to
22507 let hunk_key = self
22508 .stored_review_comments
22509 .iter()
22510 .find_map(|(key, comments)| {
22511 if comments.iter().any(|c| c.id == comment_id) {
22512 Some(key.clone())
22513 } else {
22514 None
22515 }
22516 });
22517
22518 // Remove the inline editor and its subscription
22519 if let Some(hunk_key) = hunk_key {
22520 let snapshot = self.buffer.read(cx).snapshot(cx);
22521 if let Some(overlay) = self
22522 .diff_review_overlays
22523 .iter_mut()
22524 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22525 {
22526 overlay.inline_edit_editors.remove(&comment_id);
22527 overlay.inline_edit_subscriptions.remove(&comment_id);
22528 }
22529 }
22530
22531 // Clear editing state
22532 self.set_comment_editing(comment_id, false, cx);
22533 }
22534
22535 /// Action handler for ConfirmEditReviewComment.
22536 pub fn confirm_edit_review_comment_action(
22537 &mut self,
22538 action: &ConfirmEditReviewComment,
22539 window: &mut Window,
22540 cx: &mut Context<Self>,
22541 ) {
22542 self.confirm_edit_review_comment(action.id, window, cx);
22543 }
22544
22545 /// Action handler for CancelEditReviewComment.
22546 pub fn cancel_edit_review_comment_action(
22547 &mut self,
22548 action: &CancelEditReviewComment,
22549 window: &mut Window,
22550 cx: &mut Context<Self>,
22551 ) {
22552 self.cancel_edit_review_comment(action.id, window, cx);
22553 }
22554
22555 /// Handles the DeleteReviewComment action - removes a comment.
22556 pub fn delete_review_comment(
22557 &mut self,
22558 action: &DeleteReviewComment,
22559 window: &mut Window,
22560 cx: &mut Context<Self>,
22561 ) {
22562 // Get the hunk key before removing the comment
22563 // Find the hunk key from the comment itself
22564 let comment_id = action.id;
22565 let hunk_key = self
22566 .stored_review_comments
22567 .iter()
22568 .find_map(|(key, comments)| {
22569 if comments.iter().any(|c| c.id == comment_id) {
22570 Some(key.clone())
22571 } else {
22572 None
22573 }
22574 });
22575
22576 // Also get it from the overlay for refresh purposes
22577 let overlay_hunk_key = self
22578 .diff_review_overlays
22579 .first()
22580 .map(|o| o.hunk_key.clone());
22581
22582 self.remove_review_comment(action.id, cx);
22583
22584 // Refresh the overlay height after removing a comment
22585 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22586 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22587 }
22588 }
22589
22590 fn render_diff_review_overlay(
22591 prompt_editor: &Entity<Editor>,
22592 hunk_key: &DiffHunkKey,
22593 editor_handle: &WeakEntity<Editor>,
22594 cx: &mut BlockContext,
22595 ) -> AnyElement {
22596 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22597 if ranges.is_empty() {
22598 return None;
22599 }
22600 let formatted: Vec<String> = ranges
22601 .iter()
22602 .map(|(start, end)| {
22603 let start_line = start + 1;
22604 let end_line = end + 1;
22605 if start_line == end_line {
22606 format!("Line {start_line}")
22607 } else {
22608 format!("Lines {start_line}-{end_line}")
22609 }
22610 })
22611 .collect();
22612 // Don't show label for single line in single excerpt
22613 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22614 return None;
22615 }
22616 Some(formatted.join(" ⋯ "))
22617 }
22618
22619 let theme = cx.theme();
22620 let colors = theme.colors();
22621
22622 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22623 editor_handle
22624 .upgrade()
22625 .map(|editor| {
22626 let editor = editor.read(cx);
22627 let snapshot = editor.buffer().read(cx).snapshot(cx);
22628 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22629 let (expanded, editors, avatar_uri, line_ranges) = editor
22630 .diff_review_overlays
22631 .iter()
22632 .find(|overlay| {
22633 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22634 })
22635 .map(|o| {
22636 let start_point = o.anchor_range.start.to_point(&snapshot);
22637 let end_point = o.anchor_range.end.to_point(&snapshot);
22638 // Get line ranges per excerpt to detect discontinuities
22639 let buffer_ranges =
22640 snapshot.range_to_buffer_ranges(start_point..end_point);
22641 let ranges: Vec<(u32, u32)> = buffer_ranges
22642 .iter()
22643 .map(|(buffer_snapshot, range, _)| {
22644 let start = buffer_snapshot.offset_to_point(range.start.0).row;
22645 let end = buffer_snapshot.offset_to_point(range.end.0).row;
22646 (start, end)
22647 })
22648 .collect();
22649 (
22650 o.comments_expanded,
22651 o.inline_edit_editors.clone(),
22652 o.user_avatar_uri.clone(),
22653 if ranges.is_empty() {
22654 None
22655 } else {
22656 Some(ranges)
22657 },
22658 )
22659 })
22660 .unwrap_or((true, HashMap::default(), None, None));
22661 (comments, expanded, editors, avatar_uri, line_ranges)
22662 })
22663 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22664
22665 let comment_count = comments.len();
22666 let avatar_size = px(20.);
22667 let action_icon_size = IconSize::XSmall;
22668
22669 v_flex()
22670 .w_full()
22671 .bg(colors.editor_background)
22672 .border_b_1()
22673 .border_color(colors.border)
22674 .px_2()
22675 .pb_2()
22676 .gap_2()
22677 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22678 .when_some(line_ranges, |el, ranges| {
22679 let label = format_line_ranges(&ranges);
22680 if let Some(label) = label {
22681 el.child(
22682 h_flex()
22683 .w_full()
22684 .px_2()
22685 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22686 )
22687 } else {
22688 el
22689 }
22690 })
22691 // Top row: editable input with user's avatar
22692 .child(
22693 h_flex()
22694 .w_full()
22695 .items_center()
22696 .gap_2()
22697 .px_2()
22698 .py_1p5()
22699 .rounded_md()
22700 .bg(colors.surface_background)
22701 .child(
22702 div()
22703 .size(avatar_size)
22704 .flex_shrink_0()
22705 .rounded_full()
22706 .overflow_hidden()
22707 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22708 Avatar::new(avatar_uri.clone())
22709 .size(avatar_size)
22710 .into_any_element()
22711 } else {
22712 Icon::new(IconName::Person)
22713 .size(IconSize::Small)
22714 .color(ui::Color::Muted)
22715 .into_any_element()
22716 }),
22717 )
22718 .child(
22719 div()
22720 .flex_1()
22721 .border_1()
22722 .border_color(colors.border)
22723 .rounded_md()
22724 .bg(colors.editor_background)
22725 .px_2()
22726 .py_1()
22727 .child(prompt_editor.clone()),
22728 )
22729 .child(
22730 h_flex()
22731 .flex_shrink_0()
22732 .gap_1()
22733 .child(
22734 IconButton::new("diff-review-close", IconName::Close)
22735 .icon_color(ui::Color::Muted)
22736 .icon_size(action_icon_size)
22737 .tooltip(Tooltip::text("Close"))
22738 .on_click(|_, window, cx| {
22739 window
22740 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22741 }),
22742 )
22743 .child(
22744 IconButton::new("diff-review-add", IconName::Return)
22745 .icon_color(ui::Color::Muted)
22746 .icon_size(action_icon_size)
22747 .tooltip(Tooltip::text("Add comment"))
22748 .on_click(|_, window, cx| {
22749 window.dispatch_action(
22750 Box::new(crate::actions::SubmitDiffReviewComment),
22751 cx,
22752 );
22753 }),
22754 ),
22755 ),
22756 )
22757 // Expandable comments section (only shown when there are comments)
22758 .when(comment_count > 0, |el| {
22759 el.child(Self::render_comments_section(
22760 comments,
22761 comments_expanded,
22762 inline_editors,
22763 user_avatar_uri,
22764 avatar_size,
22765 action_icon_size,
22766 colors,
22767 ))
22768 })
22769 .into_any_element()
22770 }
22771
22772 fn render_comments_section(
22773 comments: Vec<StoredReviewComment>,
22774 expanded: bool,
22775 inline_editors: HashMap<usize, Entity<Editor>>,
22776 user_avatar_uri: Option<SharedUri>,
22777 avatar_size: Pixels,
22778 action_icon_size: IconSize,
22779 colors: &theme::ThemeColors,
22780 ) -> impl IntoElement {
22781 let comment_count = comments.len();
22782
22783 v_flex()
22784 .w_full()
22785 .gap_1()
22786 // Header with expand/collapse toggle
22787 .child(
22788 h_flex()
22789 .id("review-comments-header")
22790 .w_full()
22791 .items_center()
22792 .gap_1()
22793 .px_2()
22794 .py_1()
22795 .cursor_pointer()
22796 .rounded_md()
22797 .hover(|style| style.bg(colors.ghost_element_hover))
22798 .on_click(|_, window: &mut Window, cx| {
22799 window.dispatch_action(
22800 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22801 cx,
22802 );
22803 })
22804 .child(
22805 Icon::new(if expanded {
22806 IconName::ChevronDown
22807 } else {
22808 IconName::ChevronRight
22809 })
22810 .size(IconSize::Small)
22811 .color(ui::Color::Muted),
22812 )
22813 .child(
22814 Label::new(format!(
22815 "{} Comment{}",
22816 comment_count,
22817 if comment_count == 1 { "" } else { "s" }
22818 ))
22819 .size(LabelSize::Small)
22820 .color(Color::Muted),
22821 ),
22822 )
22823 // Comments list (when expanded)
22824 .when(expanded, |el| {
22825 el.children(comments.into_iter().map(|comment| {
22826 let inline_editor = inline_editors.get(&comment.id).cloned();
22827 Self::render_comment_row(
22828 comment,
22829 inline_editor,
22830 user_avatar_uri.clone(),
22831 avatar_size,
22832 action_icon_size,
22833 colors,
22834 )
22835 }))
22836 })
22837 }
22838
22839 fn render_comment_row(
22840 comment: StoredReviewComment,
22841 inline_editor: Option<Entity<Editor>>,
22842 user_avatar_uri: Option<SharedUri>,
22843 avatar_size: Pixels,
22844 action_icon_size: IconSize,
22845 colors: &theme::ThemeColors,
22846 ) -> impl IntoElement {
22847 let comment_id = comment.id;
22848 let is_editing = inline_editor.is_some();
22849
22850 h_flex()
22851 .w_full()
22852 .items_center()
22853 .gap_2()
22854 .px_2()
22855 .py_1p5()
22856 .rounded_md()
22857 .bg(colors.surface_background)
22858 .child(
22859 div()
22860 .size(avatar_size)
22861 .flex_shrink_0()
22862 .rounded_full()
22863 .overflow_hidden()
22864 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22865 Avatar::new(avatar_uri.clone())
22866 .size(avatar_size)
22867 .into_any_element()
22868 } else {
22869 Icon::new(IconName::Person)
22870 .size(IconSize::Small)
22871 .color(ui::Color::Muted)
22872 .into_any_element()
22873 }),
22874 )
22875 .child(if let Some(editor) = inline_editor {
22876 // Inline edit mode: show an editable text field
22877 div()
22878 .flex_1()
22879 .border_1()
22880 .border_color(colors.border)
22881 .rounded_md()
22882 .bg(colors.editor_background)
22883 .px_2()
22884 .py_1()
22885 .child(editor)
22886 .into_any_element()
22887 } else {
22888 // Display mode: show the comment text
22889 div()
22890 .flex_1()
22891 .text_sm()
22892 .text_color(colors.text)
22893 .child(comment.comment)
22894 .into_any_element()
22895 })
22896 .child(if is_editing {
22897 // Editing mode: show close and confirm buttons
22898 h_flex()
22899 .gap_1()
22900 .child(
22901 IconButton::new(
22902 format!("diff-review-cancel-edit-{comment_id}"),
22903 IconName::Close,
22904 )
22905 .icon_color(ui::Color::Muted)
22906 .icon_size(action_icon_size)
22907 .tooltip(Tooltip::text("Cancel"))
22908 .on_click(move |_, window, cx| {
22909 window.dispatch_action(
22910 Box::new(crate::actions::CancelEditReviewComment {
22911 id: comment_id,
22912 }),
22913 cx,
22914 );
22915 }),
22916 )
22917 .child(
22918 IconButton::new(
22919 format!("diff-review-confirm-edit-{comment_id}"),
22920 IconName::Return,
22921 )
22922 .icon_color(ui::Color::Muted)
22923 .icon_size(action_icon_size)
22924 .tooltip(Tooltip::text("Confirm"))
22925 .on_click(move |_, window, cx| {
22926 window.dispatch_action(
22927 Box::new(crate::actions::ConfirmEditReviewComment {
22928 id: comment_id,
22929 }),
22930 cx,
22931 );
22932 }),
22933 )
22934 .into_any_element()
22935 } else {
22936 // Display mode: no action buttons for now (edit/delete not yet implemented)
22937 gpui::Empty.into_any_element()
22938 })
22939 }
22940
22941 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22942 if self.display_map.read(cx).masked != masked {
22943 self.display_map.update(cx, |map, _| map.masked = masked);
22944 }
22945 cx.notify()
22946 }
22947
22948 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22949 self.show_wrap_guides = Some(show_wrap_guides);
22950 cx.notify();
22951 }
22952
22953 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22954 self.show_indent_guides = Some(show_indent_guides);
22955 cx.notify();
22956 }
22957
22958 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22959 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22960 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22961 && let Some(dir) = file.abs_path(cx).parent()
22962 {
22963 return Some(dir.to_owned());
22964 }
22965 }
22966
22967 None
22968 }
22969
22970 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22971 self.active_buffer(cx)?
22972 .read(cx)
22973 .file()
22974 .and_then(|f| f.as_local())
22975 }
22976
22977 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22978 self.active_buffer(cx).and_then(|buffer| {
22979 let buffer = buffer.read(cx);
22980 if let Some(project_path) = buffer.project_path(cx) {
22981 let project = self.project()?.read(cx);
22982 project.absolute_path(&project_path, cx)
22983 } else {
22984 buffer
22985 .file()
22986 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22987 }
22988 })
22989 }
22990
22991 pub fn reveal_in_finder(
22992 &mut self,
22993 _: &RevealInFileManager,
22994 _window: &mut Window,
22995 cx: &mut Context<Self>,
22996 ) {
22997 if let Some(path) = self.target_file_abs_path(cx) {
22998 if let Some(project) = self.project() {
22999 project.update(cx, |project, cx| project.reveal_path(&path, cx));
23000 } else {
23001 cx.reveal_path(&path);
23002 }
23003 }
23004 }
23005
23006 pub fn copy_path(
23007 &mut self,
23008 _: &zed_actions::workspace::CopyPath,
23009 _window: &mut Window,
23010 cx: &mut Context<Self>,
23011 ) {
23012 if let Some(path) = self.target_file_abs_path(cx)
23013 && let Some(path) = path.to_str()
23014 {
23015 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
23016 } else {
23017 cx.propagate();
23018 }
23019 }
23020
23021 pub fn copy_relative_path(
23022 &mut self,
23023 _: &zed_actions::workspace::CopyRelativePath,
23024 _window: &mut Window,
23025 cx: &mut Context<Self>,
23026 ) {
23027 if let Some(path) = self.active_buffer(cx).and_then(|buffer| {
23028 let project = self.project()?.read(cx);
23029 let path = buffer.read(cx).file()?.path();
23030 let path = path.display(project.path_style(cx));
23031 Some(path)
23032 }) {
23033 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
23034 } else {
23035 cx.propagate();
23036 }
23037 }
23038
23039 /// Returns the project path for the editor's buffer, if any buffer is
23040 /// opened in the editor.
23041 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
23042 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
23043 buffer.read(cx).project_path(cx)
23044 } else {
23045 None
23046 }
23047 }
23048
23049 // Returns true if the editor handled a go-to-line request
23050 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
23051 maybe!({
23052 let breakpoint_store = self.breakpoint_store.as_ref()?;
23053
23054 let (active_stack_frame, debug_line_pane_id) = {
23055 let store = breakpoint_store.read(cx);
23056 let active_stack_frame = store.active_position().cloned();
23057 let debug_line_pane_id = store.active_debug_line_pane_id();
23058 (active_stack_frame, debug_line_pane_id)
23059 };
23060
23061 let Some(active_stack_frame) = active_stack_frame else {
23062 self.clear_row_highlights::<ActiveDebugLine>();
23063 return None;
23064 };
23065
23066 if let Some(debug_line_pane_id) = debug_line_pane_id {
23067 if let Some(workspace) = self
23068 .workspace
23069 .as_ref()
23070 .and_then(|(workspace, _)| workspace.upgrade())
23071 {
23072 let editor_pane_id = workspace
23073 .read(cx)
23074 .pane_for_item_id(cx.entity_id())
23075 .map(|pane| pane.entity_id());
23076
23077 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
23078 self.clear_row_highlights::<ActiveDebugLine>();
23079 return None;
23080 }
23081 }
23082 }
23083
23084 let position = active_stack_frame.position;
23085
23086 let snapshot = self.buffer.read(cx).snapshot(cx);
23087 let multibuffer_anchor = snapshot.anchor_in_excerpt(position)?;
23088
23089 self.clear_row_highlights::<ActiveDebugLine>();
23090
23091 self.go_to_line::<ActiveDebugLine>(
23092 multibuffer_anchor,
23093 Some(cx.theme().colors().editor_debugger_active_line_background),
23094 window,
23095 cx,
23096 );
23097
23098 cx.notify();
23099
23100 Some(())
23101 })
23102 .is_some()
23103 }
23104
23105 pub fn copy_file_name_without_extension(
23106 &mut self,
23107 _: &CopyFileNameWithoutExtension,
23108 _: &mut Window,
23109 cx: &mut Context<Self>,
23110 ) {
23111 if let Some(file_stem) = self.active_buffer(cx).and_then(|buffer| {
23112 let file = buffer.read(cx).file()?;
23113 file.path().file_stem()
23114 }) {
23115 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
23116 }
23117 }
23118
23119 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
23120 if let Some(file_name) = self.active_buffer(cx).and_then(|buffer| {
23121 let file = buffer.read(cx).file()?;
23122 Some(file.file_name(cx))
23123 }) {
23124 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
23125 }
23126 }
23127
23128 pub fn toggle_git_blame(
23129 &mut self,
23130 _: &::git::Blame,
23131 window: &mut Window,
23132 cx: &mut Context<Self>,
23133 ) {
23134 self.show_git_blame_gutter = !self.show_git_blame_gutter;
23135
23136 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
23137 self.start_git_blame(true, window, cx);
23138 }
23139
23140 cx.notify();
23141 }
23142
23143 pub fn toggle_git_blame_inline(
23144 &mut self,
23145 _: &ToggleGitBlameInline,
23146 window: &mut Window,
23147 cx: &mut Context<Self>,
23148 ) {
23149 self.toggle_git_blame_inline_internal(true, window, cx);
23150 cx.notify();
23151 }
23152
23153 pub fn open_git_blame_commit(
23154 &mut self,
23155 _: &OpenGitBlameCommit,
23156 window: &mut Window,
23157 cx: &mut Context<Self>,
23158 ) {
23159 self.open_git_blame_commit_internal(window, cx);
23160 }
23161
23162 fn open_git_blame_commit_internal(
23163 &mut self,
23164 window: &mut Window,
23165 cx: &mut Context<Self>,
23166 ) -> Option<()> {
23167 let blame = self.blame.as_ref()?;
23168 let snapshot = self.snapshot(window, cx);
23169 let cursor = self
23170 .selections
23171 .newest::<Point>(&snapshot.display_snapshot)
23172 .head();
23173 let (buffer, point) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
23174 let (_, blame_entry) = blame
23175 .update(cx, |blame, cx| {
23176 blame
23177 .blame_for_rows(
23178 &[RowInfo {
23179 buffer_id: Some(buffer.remote_id()),
23180 buffer_row: Some(point.row),
23181 ..Default::default()
23182 }],
23183 cx,
23184 )
23185 .next()
23186 })
23187 .flatten()?;
23188 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23189 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23190 let workspace = self.workspace()?.downgrade();
23191 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23192 None
23193 }
23194
23195 pub fn git_blame_inline_enabled(&self) -> bool {
23196 self.git_blame_inline_enabled
23197 }
23198
23199 pub fn toggle_selection_menu(
23200 &mut self,
23201 _: &ToggleSelectionMenu,
23202 _: &mut Window,
23203 cx: &mut Context<Self>,
23204 ) {
23205 self.show_selection_menu = self
23206 .show_selection_menu
23207 .map(|show_selections_menu| !show_selections_menu)
23208 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23209
23210 cx.notify();
23211 }
23212
23213 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23214 self.show_selection_menu
23215 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23216 }
23217
23218 fn start_git_blame(
23219 &mut self,
23220 user_triggered: bool,
23221 window: &mut Window,
23222 cx: &mut Context<Self>,
23223 ) {
23224 if let Some(project) = self.project() {
23225 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23226 && buffer.read(cx).file().is_none()
23227 {
23228 return;
23229 }
23230
23231 let focused = self.focus_handle(cx).contains_focused(window, cx);
23232
23233 let project = project.clone();
23234 let blame = cx
23235 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23236 self.blame_subscription =
23237 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23238 self.blame = Some(blame);
23239 }
23240 }
23241
23242 fn toggle_git_blame_inline_internal(
23243 &mut self,
23244 user_triggered: bool,
23245 window: &mut Window,
23246 cx: &mut Context<Self>,
23247 ) {
23248 if self.git_blame_inline_enabled {
23249 self.git_blame_inline_enabled = false;
23250 self.show_git_blame_inline = false;
23251 self.show_git_blame_inline_delay_task.take();
23252 } else {
23253 self.git_blame_inline_enabled = true;
23254 self.start_git_blame_inline(user_triggered, window, cx);
23255 }
23256
23257 cx.notify();
23258 }
23259
23260 fn start_git_blame_inline(
23261 &mut self,
23262 user_triggered: bool,
23263 window: &mut Window,
23264 cx: &mut Context<Self>,
23265 ) {
23266 self.start_git_blame(user_triggered, window, cx);
23267
23268 if ProjectSettings::get_global(cx)
23269 .git
23270 .inline_blame_delay()
23271 .is_some()
23272 {
23273 self.start_inline_blame_timer(window, cx);
23274 } else {
23275 self.show_git_blame_inline = true
23276 }
23277 }
23278
23279 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23280 self.blame.as_ref()
23281 }
23282
23283 pub fn show_git_blame_gutter(&self) -> bool {
23284 self.show_git_blame_gutter
23285 }
23286
23287 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23288 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23289 }
23290
23291 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23292 self.show_git_blame_inline
23293 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23294 && !self.newest_selection_head_on_empty_line(cx)
23295 && self.has_blame_entries(cx)
23296 }
23297
23298 fn has_blame_entries(&self, cx: &App) -> bool {
23299 self.blame()
23300 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23301 }
23302
23303 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23304 let cursor_anchor = self.selections.newest_anchor().head();
23305
23306 let snapshot = self.buffer.read(cx).snapshot(cx);
23307 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23308
23309 snapshot.line_len(buffer_row) == 0
23310 }
23311
23312 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23313 let buffer_and_selection = maybe!({
23314 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23315 let selection_range = selection.range();
23316
23317 let multi_buffer = self.buffer().read(cx);
23318 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23319 let buffer_ranges = multi_buffer_snapshot
23320 .range_to_buffer_ranges(selection_range.start..selection_range.end);
23321
23322 let (buffer_snapshot, range, _) = if selection.reversed {
23323 buffer_ranges.first()
23324 } else {
23325 buffer_ranges.last()
23326 }?;
23327
23328 let buffer_range = range.to_point(buffer_snapshot);
23329 let buffer = multi_buffer.buffer(buffer_snapshot.remote_id()).unwrap();
23330
23331 let Some(buffer_diff) = multi_buffer.diff_for(buffer_snapshot.remote_id()) else {
23332 return Some((buffer, buffer_range.start.row..buffer_range.end.row));
23333 };
23334
23335 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23336 let start = buffer_diff_snapshot
23337 .buffer_point_to_base_text_point(buffer_range.start, &buffer_snapshot);
23338 let end = buffer_diff_snapshot
23339 .buffer_point_to_base_text_point(buffer_range.end, &buffer_snapshot);
23340
23341 Some((buffer, start.row..end.row))
23342 });
23343
23344 let Some((buffer, selection)) = buffer_and_selection else {
23345 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23346 };
23347
23348 let Some(project) = self.project() else {
23349 return Task::ready(Err(anyhow!("editor does not have project")));
23350 };
23351
23352 project.update(cx, |project, cx| {
23353 project.get_permalink_to_line(&buffer, selection, cx)
23354 })
23355 }
23356
23357 pub fn copy_permalink_to_line(
23358 &mut self,
23359 _: &CopyPermalinkToLine,
23360 window: &mut Window,
23361 cx: &mut Context<Self>,
23362 ) {
23363 let permalink_task = self.get_permalink_to_line(cx);
23364 let workspace = self.workspace();
23365
23366 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23367 Ok(permalink) => {
23368 cx.update(|_, cx| {
23369 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23370 })
23371 .ok();
23372 }
23373 Err(err) => {
23374 let message = format!("Failed to copy permalink: {err}");
23375
23376 anyhow::Result::<()>::Err(err).log_err();
23377
23378 if let Some(workspace) = workspace {
23379 workspace
23380 .update_in(cx, |workspace, _, cx| {
23381 struct CopyPermalinkToLine;
23382
23383 workspace.show_toast(
23384 Toast::new(
23385 NotificationId::unique::<CopyPermalinkToLine>(),
23386 message,
23387 ),
23388 cx,
23389 )
23390 })
23391 .ok();
23392 }
23393 }
23394 })
23395 .detach();
23396 }
23397
23398 pub fn copy_file_location(
23399 &mut self,
23400 _: &CopyFileLocation,
23401 _: &mut Window,
23402 cx: &mut Context<Self>,
23403 ) {
23404 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23405
23406 let start_line = selection.start.row + 1;
23407 let end_line = selection.end.row + 1;
23408
23409 let end_line = if selection.end.column == 0 && end_line > start_line {
23410 end_line - 1
23411 } else {
23412 end_line
23413 };
23414
23415 if let Some(file_location) = self.active_buffer(cx).and_then(|buffer| {
23416 let project = self.project()?.read(cx);
23417 let file = buffer.read(cx).file()?;
23418 let path = file.path().display(project.path_style(cx));
23419
23420 let location = if start_line == end_line {
23421 format!("{path}:{start_line}")
23422 } else {
23423 format!("{path}:{start_line}-{end_line}")
23424 };
23425 Some(location)
23426 }) {
23427 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23428 }
23429 }
23430
23431 pub fn open_permalink_to_line(
23432 &mut self,
23433 _: &OpenPermalinkToLine,
23434 window: &mut Window,
23435 cx: &mut Context<Self>,
23436 ) {
23437 let permalink_task = self.get_permalink_to_line(cx);
23438 let workspace = self.workspace();
23439
23440 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23441 Ok(permalink) => {
23442 cx.update(|_, cx| {
23443 cx.open_url(permalink.as_ref());
23444 })
23445 .ok();
23446 }
23447 Err(err) => {
23448 let message = format!("Failed to open permalink: {err}");
23449
23450 anyhow::Result::<()>::Err(err).log_err();
23451
23452 if let Some(workspace) = workspace {
23453 workspace.update(cx, |workspace, cx| {
23454 struct OpenPermalinkToLine;
23455
23456 workspace.show_toast(
23457 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23458 cx,
23459 )
23460 });
23461 }
23462 }
23463 })
23464 .detach();
23465 }
23466
23467 pub fn insert_uuid_v4(
23468 &mut self,
23469 _: &InsertUuidV4,
23470 window: &mut Window,
23471 cx: &mut Context<Self>,
23472 ) {
23473 self.insert_uuid(UuidVersion::V4, window, cx);
23474 }
23475
23476 pub fn insert_uuid_v7(
23477 &mut self,
23478 _: &InsertUuidV7,
23479 window: &mut Window,
23480 cx: &mut Context<Self>,
23481 ) {
23482 self.insert_uuid(UuidVersion::V7, window, cx);
23483 }
23484
23485 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23487 self.transact(window, cx, |this, window, cx| {
23488 let edits = this
23489 .selections
23490 .all::<Point>(&this.display_snapshot(cx))
23491 .into_iter()
23492 .map(|selection| {
23493 let uuid = match version {
23494 UuidVersion::V4 => uuid::Uuid::new_v4(),
23495 UuidVersion::V7 => uuid::Uuid::now_v7(),
23496 };
23497
23498 (selection.range(), uuid.to_string())
23499 });
23500 this.edit(edits, cx);
23501 this.refresh_edit_prediction(true, false, window, cx);
23502 });
23503 }
23504
23505 pub fn open_selections_in_multibuffer(
23506 &mut self,
23507 _: &OpenSelectionsInMultibuffer,
23508 window: &mut Window,
23509 cx: &mut Context<Self>,
23510 ) {
23511 let multibuffer = self.buffer.read(cx);
23512
23513 let Some(buffer) = multibuffer.as_singleton() else {
23514 return;
23515 };
23516 let buffer_snapshot = buffer.read(cx).snapshot();
23517
23518 let Some(workspace) = self.workspace() else {
23519 return;
23520 };
23521
23522 let title = multibuffer.title(cx).to_string();
23523
23524 let locations = self
23525 .selections
23526 .all_anchors(&self.display_snapshot(cx))
23527 .iter()
23528 .map(|selection| {
23529 (
23530 buffer.clone(),
23531 (selection.start.text_anchor_in(&buffer_snapshot)
23532 ..selection.end.text_anchor_in(&buffer_snapshot))
23533 .to_point(buffer.read(cx)),
23534 )
23535 })
23536 .into_group_map();
23537
23538 cx.spawn_in(window, async move |_, cx| {
23539 workspace.update_in(cx, |workspace, window, cx| {
23540 Self::open_locations_in_multibuffer(
23541 workspace,
23542 locations,
23543 format!("Selections for '{title}'"),
23544 false,
23545 false,
23546 MultibufferSelectionMode::All,
23547 window,
23548 cx,
23549 );
23550 })
23551 })
23552 .detach();
23553 }
23554
23555 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23556 /// last highlight added will be used.
23557 ///
23558 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23559 pub fn highlight_rows<T: 'static>(
23560 &mut self,
23561 range: Range<Anchor>,
23562 color: Hsla,
23563 options: RowHighlightOptions,
23564 cx: &mut Context<Self>,
23565 ) {
23566 let snapshot = self.buffer().read(cx).snapshot(cx);
23567 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23568 let ix = row_highlights.binary_search_by(|highlight| {
23569 Ordering::Equal
23570 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23571 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23572 });
23573
23574 if let Err(mut ix) = ix {
23575 let index = post_inc(&mut self.highlight_order);
23576
23577 // If this range intersects with the preceding highlight, then merge it with
23578 // the preceding highlight. Otherwise insert a new highlight.
23579 let mut merged = false;
23580 if ix > 0 {
23581 let prev_highlight = &mut row_highlights[ix - 1];
23582 if prev_highlight
23583 .range
23584 .end
23585 .cmp(&range.start, &snapshot)
23586 .is_ge()
23587 {
23588 ix -= 1;
23589 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23590 prev_highlight.range.end = range.end;
23591 }
23592 merged = true;
23593 prev_highlight.index = index;
23594 prev_highlight.color = color;
23595 prev_highlight.options = options;
23596 }
23597 }
23598
23599 if !merged {
23600 row_highlights.insert(
23601 ix,
23602 RowHighlight {
23603 range,
23604 index,
23605 color,
23606 options,
23607 type_id: TypeId::of::<T>(),
23608 },
23609 );
23610 }
23611
23612 // If any of the following highlights intersect with this one, merge them.
23613 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23614 let highlight = &row_highlights[ix];
23615 if next_highlight
23616 .range
23617 .start
23618 .cmp(&highlight.range.end, &snapshot)
23619 .is_le()
23620 {
23621 if next_highlight
23622 .range
23623 .end
23624 .cmp(&highlight.range.end, &snapshot)
23625 .is_gt()
23626 {
23627 row_highlights[ix].range.end = next_highlight.range.end;
23628 }
23629 row_highlights.remove(ix + 1);
23630 } else {
23631 break;
23632 }
23633 }
23634 }
23635 }
23636
23637 /// Remove any highlighted row ranges of the given type that intersect the
23638 /// given ranges.
23639 pub fn remove_highlighted_rows<T: 'static>(
23640 &mut self,
23641 ranges_to_remove: Vec<Range<Anchor>>,
23642 cx: &mut Context<Self>,
23643 ) {
23644 let snapshot = self.buffer().read(cx).snapshot(cx);
23645 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23646 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23647 row_highlights.retain(|highlight| {
23648 while let Some(range_to_remove) = ranges_to_remove.peek() {
23649 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23650 Ordering::Less | Ordering::Equal => {
23651 ranges_to_remove.next();
23652 }
23653 Ordering::Greater => {
23654 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23655 Ordering::Less | Ordering::Equal => {
23656 return false;
23657 }
23658 Ordering::Greater => break,
23659 }
23660 }
23661 }
23662 }
23663
23664 true
23665 })
23666 }
23667
23668 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23669 pub fn clear_row_highlights<T: 'static>(&mut self) {
23670 self.highlighted_rows.remove(&TypeId::of::<T>());
23671 }
23672
23673 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23674 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23675 self.highlighted_rows
23676 .get(&TypeId::of::<T>())
23677 .map_or(&[] as &[_], |vec| vec.as_slice())
23678 .iter()
23679 .map(|highlight| (highlight.range.clone(), highlight.color))
23680 }
23681
23682 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23683 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23684 /// Allows to ignore certain kinds of highlights.
23685 pub fn highlighted_display_rows(
23686 &self,
23687 window: &mut Window,
23688 cx: &mut App,
23689 ) -> BTreeMap<DisplayRow, LineHighlight> {
23690 let snapshot = self.snapshot(window, cx);
23691 let mut used_highlight_orders = HashMap::default();
23692 self.highlighted_rows
23693 .iter()
23694 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23695 .fold(
23696 BTreeMap::<DisplayRow, LineHighlight>::new(),
23697 |mut unique_rows, highlight| {
23698 let start = highlight.range.start.to_display_point(&snapshot);
23699 let end = highlight.range.end.to_display_point(&snapshot);
23700 let start_row = start.row().0;
23701 let end_row = if !highlight.range.end.is_max() && end.column() == 0 {
23702 end.row().0.saturating_sub(1)
23703 } else {
23704 end.row().0
23705 };
23706 for row in start_row..=end_row {
23707 let used_index =
23708 used_highlight_orders.entry(row).or_insert(highlight.index);
23709 if highlight.index >= *used_index {
23710 *used_index = highlight.index;
23711 unique_rows.insert(
23712 DisplayRow(row),
23713 LineHighlight {
23714 include_gutter: highlight.options.include_gutter,
23715 border: None,
23716 background: highlight.color.into(),
23717 type_id: Some(highlight.type_id),
23718 },
23719 );
23720 }
23721 }
23722 unique_rows
23723 },
23724 )
23725 }
23726
23727 pub fn highlighted_display_row_for_autoscroll(
23728 &self,
23729 snapshot: &DisplaySnapshot,
23730 ) -> Option<DisplayRow> {
23731 self.highlighted_rows
23732 .values()
23733 .flat_map(|highlighted_rows| highlighted_rows.iter())
23734 .filter_map(|highlight| {
23735 if highlight.options.autoscroll {
23736 Some(highlight.range.start.to_display_point(snapshot).row())
23737 } else {
23738 None
23739 }
23740 })
23741 .min()
23742 }
23743
23744 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23745 self.highlight_background(
23746 HighlightKey::SearchWithinRange,
23747 ranges,
23748 |_, colors| colors.colors().editor_document_highlight_read_background,
23749 cx,
23750 )
23751 }
23752
23753 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23754 self.breadcrumb_header = Some(new_header);
23755 }
23756
23757 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23758 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23759 }
23760
23761 pub fn highlight_background(
23762 &mut self,
23763 key: HighlightKey,
23764 ranges: &[Range<Anchor>],
23765 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23766 cx: &mut Context<Self>,
23767 ) {
23768 self.background_highlights
23769 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23770 self.scrollbar_marker_state.dirty = true;
23771 cx.notify();
23772 }
23773
23774 pub fn clear_background_highlights(
23775 &mut self,
23776 key: HighlightKey,
23777 cx: &mut Context<Self>,
23778 ) -> Option<BackgroundHighlight> {
23779 let text_highlights = self.background_highlights.remove(&key)?;
23780 if !text_highlights.1.is_empty() {
23781 self.scrollbar_marker_state.dirty = true;
23782 cx.notify();
23783 }
23784 Some(text_highlights)
23785 }
23786
23787 pub fn highlight_gutter<T: 'static>(
23788 &mut self,
23789 ranges: impl Into<Vec<Range<Anchor>>>,
23790 color_fetcher: fn(&App) -> Hsla,
23791 cx: &mut Context<Self>,
23792 ) {
23793 self.gutter_highlights
23794 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23795 cx.notify();
23796 }
23797
23798 pub fn clear_gutter_highlights<T: 'static>(
23799 &mut self,
23800 cx: &mut Context<Self>,
23801 ) -> Option<GutterHighlight> {
23802 cx.notify();
23803 self.gutter_highlights.remove(&TypeId::of::<T>())
23804 }
23805
23806 pub fn insert_gutter_highlight<T: 'static>(
23807 &mut self,
23808 range: Range<Anchor>,
23809 color_fetcher: fn(&App) -> Hsla,
23810 cx: &mut Context<Self>,
23811 ) {
23812 let snapshot = self.buffer().read(cx).snapshot(cx);
23813 let mut highlights = self
23814 .gutter_highlights
23815 .remove(&TypeId::of::<T>())
23816 .map(|(_, highlights)| highlights)
23817 .unwrap_or_default();
23818 let ix = highlights.binary_search_by(|highlight| {
23819 Ordering::Equal
23820 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23821 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23822 });
23823 if let Err(ix) = ix {
23824 highlights.insert(ix, range);
23825 }
23826 self.gutter_highlights
23827 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23828 }
23829
23830 pub fn remove_gutter_highlights<T: 'static>(
23831 &mut self,
23832 ranges_to_remove: Vec<Range<Anchor>>,
23833 cx: &mut Context<Self>,
23834 ) {
23835 let snapshot = self.buffer().read(cx).snapshot(cx);
23836 let Some((color_fetcher, mut gutter_highlights)) =
23837 self.gutter_highlights.remove(&TypeId::of::<T>())
23838 else {
23839 return;
23840 };
23841 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23842 gutter_highlights.retain(|highlight| {
23843 while let Some(range_to_remove) = ranges_to_remove.peek() {
23844 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23845 Ordering::Less | Ordering::Equal => {
23846 ranges_to_remove.next();
23847 }
23848 Ordering::Greater => {
23849 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23850 Ordering::Less | Ordering::Equal => {
23851 return false;
23852 }
23853 Ordering::Greater => break,
23854 }
23855 }
23856 }
23857 }
23858
23859 true
23860 });
23861 self.gutter_highlights
23862 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23863 }
23864
23865 #[cfg(any(test, feature = "test-support"))]
23866 pub fn all_text_highlights(
23867 &self,
23868 window: &mut Window,
23869 cx: &mut Context<Self>,
23870 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23871 let snapshot = self.snapshot(window, cx);
23872 self.display_map.update(cx, |display_map, _| {
23873 display_map
23874 .all_text_highlights()
23875 .map(|(_, highlight)| {
23876 let (style, ranges) = highlight.as_ref();
23877 (
23878 *style,
23879 ranges
23880 .iter()
23881 .map(|range| range.clone().to_display_points(&snapshot))
23882 .collect(),
23883 )
23884 })
23885 .collect()
23886 })
23887 }
23888
23889 #[cfg(any(test, feature = "test-support"))]
23890 pub fn all_text_background_highlights(
23891 &self,
23892 window: &mut Window,
23893 cx: &mut Context<Self>,
23894 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23895 let snapshot = self.snapshot(window, cx);
23896 let buffer = &snapshot.buffer_snapshot();
23897 let start = buffer.anchor_before(MultiBufferOffset(0));
23898 let end = buffer.anchor_after(buffer.len());
23899 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23900 }
23901
23902 #[cfg(any(test, feature = "test-support"))]
23903 pub fn sorted_background_highlights_in_range(
23904 &self,
23905 search_range: Range<Anchor>,
23906 display_snapshot: &DisplaySnapshot,
23907 theme: &Theme,
23908 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23909 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23910 res.sort_by(|a, b| {
23911 a.0.start
23912 .cmp(&b.0.start)
23913 .then_with(|| a.0.end.cmp(&b.0.end))
23914 .then_with(|| a.1.cmp(&b.1))
23915 });
23916 res
23917 }
23918
23919 #[cfg(any(test, feature = "test-support"))]
23920 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23921 let snapshot = self.buffer().read(cx).snapshot(cx);
23922
23923 let highlights = self
23924 .background_highlights
23925 .get(&HighlightKey::BufferSearchHighlights);
23926
23927 if let Some((_color, ranges)) = highlights {
23928 ranges
23929 .iter()
23930 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23931 .collect_vec()
23932 } else {
23933 vec![]
23934 }
23935 }
23936
23937 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23938 self.background_highlights
23939 .get(&key)
23940 .is_some_and(|(_, highlights)| !highlights.is_empty())
23941 }
23942
23943 /// Returns all background highlights for a given range.
23944 ///
23945 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23946 pub fn background_highlights_in_range(
23947 &self,
23948 search_range: Range<Anchor>,
23949 display_snapshot: &DisplaySnapshot,
23950 theme: &Theme,
23951 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23952 let mut results = Vec::new();
23953 for (color_fetcher, ranges) in self.background_highlights.values() {
23954 let start_ix = match ranges.binary_search_by(|probe| {
23955 let cmp = probe
23956 .end
23957 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23958 if cmp.is_gt() {
23959 Ordering::Greater
23960 } else {
23961 Ordering::Less
23962 }
23963 }) {
23964 Ok(i) | Err(i) => i,
23965 };
23966 for (index, range) in ranges[start_ix..].iter().enumerate() {
23967 if range
23968 .start
23969 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23970 .is_ge()
23971 {
23972 break;
23973 }
23974
23975 let color = color_fetcher(&(start_ix + index), theme);
23976 let start = range.start.to_display_point(display_snapshot);
23977 let end = range.end.to_display_point(display_snapshot);
23978 results.push((start..end, color))
23979 }
23980 }
23981 results
23982 }
23983
23984 pub fn gutter_highlights_in_range(
23985 &self,
23986 search_range: Range<Anchor>,
23987 display_snapshot: &DisplaySnapshot,
23988 cx: &App,
23989 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23990 let mut results = Vec::new();
23991 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23992 let color = color_fetcher(cx);
23993 let start_ix = match ranges.binary_search_by(|probe| {
23994 let cmp = probe
23995 .end
23996 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23997 if cmp.is_gt() {
23998 Ordering::Greater
23999 } else {
24000 Ordering::Less
24001 }
24002 }) {
24003 Ok(i) | Err(i) => i,
24004 };
24005 for range in &ranges[start_ix..] {
24006 if range
24007 .start
24008 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
24009 .is_ge()
24010 {
24011 break;
24012 }
24013
24014 let start = range.start.to_display_point(display_snapshot);
24015 let end = range.end.to_display_point(display_snapshot);
24016 results.push((start..end, color))
24017 }
24018 }
24019 results
24020 }
24021
24022 /// Get the text ranges corresponding to the redaction query
24023 pub fn redacted_ranges(
24024 &self,
24025 search_range: Range<Anchor>,
24026 display_snapshot: &DisplaySnapshot,
24027 cx: &App,
24028 ) -> Vec<Range<DisplayPoint>> {
24029 display_snapshot
24030 .buffer_snapshot()
24031 .redacted_ranges(search_range, |file| {
24032 if let Some(file) = file {
24033 file.is_private()
24034 && EditorSettings::get(
24035 Some(SettingsLocation {
24036 worktree_id: file.worktree_id(cx),
24037 path: file.path().as_ref(),
24038 }),
24039 cx,
24040 )
24041 .redact_private_values
24042 } else {
24043 false
24044 }
24045 })
24046 .map(|range| {
24047 range.start.to_display_point(display_snapshot)
24048 ..range.end.to_display_point(display_snapshot)
24049 })
24050 .collect()
24051 }
24052
24053 pub fn highlight_text_key(
24054 &mut self,
24055 key: HighlightKey,
24056 ranges: Vec<Range<Anchor>>,
24057 style: HighlightStyle,
24058 merge: bool,
24059 cx: &mut Context<Self>,
24060 ) {
24061 self.display_map.update(cx, |map, cx| {
24062 map.highlight_text(key, ranges, style, merge, cx);
24063 });
24064 cx.notify();
24065 }
24066
24067 pub fn highlight_text(
24068 &mut self,
24069 key: HighlightKey,
24070 ranges: Vec<Range<Anchor>>,
24071 style: HighlightStyle,
24072 cx: &mut Context<Self>,
24073 ) {
24074 self.display_map.update(cx, |map, cx| {
24075 map.highlight_text(key, ranges, style, false, cx)
24076 });
24077 cx.notify();
24078 }
24079
24080 pub fn text_highlights<'a>(
24081 &'a self,
24082 key: HighlightKey,
24083 cx: &'a App,
24084 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
24085 self.display_map.read(cx).text_highlights(key)
24086 }
24087
24088 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
24089 let cleared = self
24090 .display_map
24091 .update(cx, |map, _| map.clear_highlights(key));
24092 if cleared {
24093 cx.notify();
24094 }
24095 }
24096
24097 pub fn clear_highlights_with(
24098 &mut self,
24099 f: &mut dyn FnMut(&HighlightKey) -> bool,
24100 cx: &mut Context<Self>,
24101 ) {
24102 let cleared = self
24103 .display_map
24104 .update(cx, |map, _| map.clear_highlights_with(f));
24105 if cleared {
24106 cx.notify();
24107 }
24108 }
24109
24110 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
24111 (self.read_only(cx) || self.blink_manager.read(cx).visible())
24112 && self.focus_handle.is_focused(window)
24113 }
24114
24115 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
24116 self.show_cursor_when_unfocused = is_enabled;
24117 cx.notify();
24118 }
24119
24120 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
24121 cx.notify();
24122 }
24123
24124 fn on_debug_session_event(
24125 &mut self,
24126 _session: Entity<Session>,
24127 event: &SessionEvent,
24128 cx: &mut Context<Self>,
24129 ) {
24130 if let SessionEvent::InvalidateInlineValue = event {
24131 self.refresh_inline_values(cx);
24132 }
24133 }
24134
24135 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
24136 let Some(semantics) = self.semantics_provider.clone() else {
24137 return;
24138 };
24139
24140 if !self.inline_value_cache.enabled {
24141 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24142 self.splice_inlays(&inlays, Vec::new(), cx);
24143 return;
24144 }
24145
24146 let current_execution_position = self
24147 .highlighted_rows
24148 .get(&TypeId::of::<ActiveDebugLine>())
24149 .and_then(|lines| lines.last().map(|line| line.range.end));
24150
24151 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24152 let inline_values = editor
24153 .update(cx, |editor, cx| {
24154 let Some(current_execution_position) = current_execution_position else {
24155 return Some(Task::ready(Ok(Vec::new())));
24156 };
24157
24158 let (buffer, buffer_anchor) =
24159 editor.buffer.read_with(cx, |multibuffer, cx| {
24160 let multibuffer_snapshot = multibuffer.snapshot(cx);
24161 let (buffer_anchor, _) = multibuffer_snapshot
24162 .anchor_to_buffer_anchor(current_execution_position)?;
24163 let buffer = multibuffer.buffer(buffer_anchor.buffer_id)?;
24164 Some((buffer, buffer_anchor))
24165 })?;
24166
24167 let range = buffer.read(cx).anchor_before(0)..buffer_anchor;
24168
24169 semantics.inline_values(buffer, range, cx)
24170 })
24171 .ok()
24172 .flatten()?
24173 .await
24174 .context("refreshing debugger inlays")
24175 .log_err()?;
24176
24177 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24178
24179 for (buffer_id, inline_value) in inline_values
24180 .into_iter()
24181 .map(|hint| (hint.position.buffer_id, hint))
24182 {
24183 buffer_inline_values
24184 .entry(buffer_id)
24185 .or_default()
24186 .push(inline_value);
24187 }
24188
24189 editor
24190 .update(cx, |editor, cx| {
24191 let snapshot = editor.buffer.read(cx).snapshot(cx);
24192 let mut new_inlays = Vec::default();
24193
24194 for (_buffer_id, inline_values) in buffer_inline_values {
24195 for hint in inline_values {
24196 let Some(anchor) = snapshot.anchor_in_excerpt(hint.position) else {
24197 continue;
24198 };
24199 let inlay = Inlay::debugger(
24200 post_inc(&mut editor.next_inlay_id),
24201 anchor,
24202 hint.text(),
24203 );
24204 if !inlay.text().chars().contains(&'\n') {
24205 new_inlays.push(inlay);
24206 }
24207 }
24208 }
24209
24210 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24211 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24212
24213 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24214 })
24215 .ok()?;
24216 Some(())
24217 });
24218 }
24219
24220 fn on_buffer_event(
24221 &mut self,
24222 multibuffer: &Entity<MultiBuffer>,
24223 event: &multi_buffer::Event,
24224 window: &mut Window,
24225 cx: &mut Context<Self>,
24226 ) {
24227 match event {
24228 multi_buffer::Event::Edited {
24229 edited_buffer,
24230 is_local,
24231 } => {
24232 self.scrollbar_marker_state.dirty = true;
24233 self.active_indent_guides_state.dirty = true;
24234 self.refresh_active_diagnostics(cx);
24235 self.refresh_code_actions(window, cx);
24236 self.refresh_single_line_folds(window, cx);
24237 let snapshot = self.snapshot(window, cx);
24238 self.refresh_matching_bracket_highlights(&snapshot, cx);
24239 self.refresh_outline_symbols_at_cursor(cx);
24240 self.refresh_sticky_headers(&snapshot, cx);
24241 if *is_local && self.has_active_edit_prediction() {
24242 self.update_visible_edit_prediction(window, cx);
24243 }
24244
24245 // Clean up orphaned review comments after edits
24246 self.cleanup_orphaned_review_comments(cx);
24247
24248 if let Some(buffer) = edited_buffer {
24249 if buffer.read(cx).file().is_none() {
24250 cx.emit(EditorEvent::TitleChanged);
24251 }
24252
24253 if self.project.is_some() {
24254 let buffer_id = buffer.read(cx).remote_id();
24255 self.register_buffer(buffer_id, cx);
24256 self.update_lsp_data(Some(buffer_id), window, cx);
24257 self.refresh_inlay_hints(
24258 InlayHintRefreshReason::BufferEdited(buffer_id),
24259 cx,
24260 );
24261 }
24262 }
24263
24264 cx.emit(EditorEvent::BufferEdited);
24265 cx.emit(SearchEvent::MatchesInvalidated);
24266
24267 let Some(project) = &self.project else { return };
24268 let (telemetry, is_via_ssh) = {
24269 let project = project.read(cx);
24270 let telemetry = project.client().telemetry().clone();
24271 let is_via_ssh = project.is_via_remote_server();
24272 (telemetry, is_via_ssh)
24273 };
24274 telemetry.log_edit_event("editor", is_via_ssh);
24275 }
24276 multi_buffer::Event::BufferRangesUpdated {
24277 buffer,
24278 ranges,
24279 path_key,
24280 } => {
24281 self.refresh_document_highlights(cx);
24282 let buffer_id = buffer.read(cx).remote_id();
24283 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24284 && let Some(project) = &self.project
24285 {
24286 update_uncommitted_diff_for_buffer(
24287 cx.entity(),
24288 project,
24289 [buffer.clone()],
24290 self.buffer.clone(),
24291 cx,
24292 )
24293 .detach();
24294 }
24295 self.register_visible_buffers(cx);
24296 self.update_lsp_data(Some(buffer_id), window, cx);
24297 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24298 self.refresh_runnables(None, window, cx);
24299 self.bracket_fetched_tree_sitter_chunks
24300 .retain(|range, _| range.start.buffer_id != buffer_id);
24301 self.colorize_brackets(false, cx);
24302 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24303 self.semantic_token_state.invalidate_buffer(&buffer_id);
24304 cx.emit(EditorEvent::BufferRangesUpdated {
24305 buffer: buffer.clone(),
24306 ranges: ranges.clone(),
24307 path_key: path_key.clone(),
24308 });
24309 }
24310 multi_buffer::Event::BuffersRemoved { removed_buffer_ids } => {
24311 if let Some(inlay_hints) = &mut self.inlay_hints {
24312 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24313 }
24314 self.refresh_inlay_hints(
24315 InlayHintRefreshReason::BuffersRemoved(removed_buffer_ids.clone()),
24316 cx,
24317 );
24318 for buffer_id in removed_buffer_ids {
24319 self.registered_buffers.remove(buffer_id);
24320 self.clear_runnables(Some(*buffer_id));
24321 self.semantic_token_state.invalidate_buffer(buffer_id);
24322 self.display_map.update(cx, |display_map, cx| {
24323 display_map.invalidate_semantic_highlights(*buffer_id);
24324 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24325 });
24326 }
24327
24328 self.display_map.update(cx, |display_map, cx| {
24329 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24330 });
24331
24332 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24333 cx.emit(EditorEvent::BuffersRemoved {
24334 removed_buffer_ids: removed_buffer_ids.clone(),
24335 });
24336 }
24337 multi_buffer::Event::BuffersEdited { buffer_ids } => {
24338 self.display_map.update(cx, |map, cx| {
24339 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24340 });
24341 cx.emit(EditorEvent::BuffersEdited {
24342 buffer_ids: buffer_ids.clone(),
24343 });
24344 }
24345 multi_buffer::Event::Reparsed(buffer_id) => {
24346 self.refresh_runnables(Some(*buffer_id), window, cx);
24347 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24348 self.colorize_brackets(true, cx);
24349 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24350
24351 cx.emit(EditorEvent::Reparsed(*buffer_id));
24352 }
24353 multi_buffer::Event::DiffHunksToggled => {
24354 self.refresh_runnables(None, window, cx);
24355 }
24356 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24357 if !is_fresh_language {
24358 self.registered_buffers.remove(&buffer_id);
24359 }
24360 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24361 cx.emit(EditorEvent::Reparsed(*buffer_id));
24362 self.update_edit_prediction_settings(cx);
24363 cx.notify();
24364 }
24365 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24366 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24367 multi_buffer::Event::FileHandleChanged
24368 | multi_buffer::Event::Reloaded
24369 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24370 multi_buffer::Event::DiagnosticsUpdated => {
24371 self.update_diagnostics_state(window, cx);
24372 }
24373 _ => {}
24374 };
24375 }
24376
24377 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24378 if !self.diagnostics_enabled() {
24379 return;
24380 }
24381 self.refresh_active_diagnostics(cx);
24382 self.refresh_inline_diagnostics(true, window, cx);
24383 self.scrollbar_marker_state.dirty = true;
24384 cx.notify();
24385 }
24386
24387 pub fn start_temporary_diff_override(&mut self) {
24388 self.load_diff_task.take();
24389 self.temporary_diff_override = true;
24390 }
24391
24392 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24393 self.temporary_diff_override = false;
24394 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24395 self.buffer.update(cx, |buffer, cx| {
24396 buffer.set_all_diff_hunks_collapsed(cx);
24397 });
24398
24399 if let Some(project) = self.project.clone() {
24400 self.load_diff_task = Some(
24401 update_uncommitted_diff_for_buffer(
24402 cx.entity(),
24403 &project,
24404 self.buffer.read(cx).all_buffers(),
24405 self.buffer.clone(),
24406 cx,
24407 )
24408 .shared(),
24409 );
24410 }
24411 }
24412
24413 fn on_display_map_changed(
24414 &mut self,
24415 _: Entity<DisplayMap>,
24416 _: &mut Window,
24417 cx: &mut Context<Self>,
24418 ) {
24419 cx.notify();
24420 }
24421
24422 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24423 if !self.mode.is_full() {
24424 return None;
24425 }
24426
24427 let theme_settings = theme_settings::ThemeSettings::get_global(cx);
24428 let theme = cx.theme();
24429 let accent_colors = theme.accents().clone();
24430
24431 let accent_overrides = theme_settings
24432 .theme_overrides
24433 .get(theme.name.as_ref())
24434 .map(|theme_style| &theme_style.accents)
24435 .into_iter()
24436 .flatten()
24437 .chain(
24438 theme_settings
24439 .experimental_theme_overrides
24440 .as_ref()
24441 .map(|overrides| &overrides.accents)
24442 .into_iter()
24443 .flatten(),
24444 )
24445 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24446 .collect();
24447
24448 Some(AccentData {
24449 colors: accent_colors,
24450 overrides: accent_overrides,
24451 })
24452 }
24453
24454 fn fetch_applicable_language_settings(
24455 &self,
24456 cx: &App,
24457 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24458 if !self.mode.is_full() {
24459 return HashMap::default();
24460 }
24461
24462 self.buffer().read(cx).all_buffers().into_iter().fold(
24463 HashMap::default(),
24464 |mut acc, buffer| {
24465 let buffer = buffer.read(cx);
24466 let language = buffer.language().map(|language| language.name());
24467 if let hash_map::Entry::Vacant(v) = acc.entry(language) {
24468 v.insert(LanguageSettings::for_buffer(&buffer, cx).into_owned());
24469 }
24470 acc
24471 },
24472 )
24473 }
24474
24475 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24476 let new_language_settings = self.fetch_applicable_language_settings(cx);
24477 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24478 self.applicable_language_settings = new_language_settings;
24479
24480 let new_accents = self.fetch_accent_data(cx);
24481 let accents_changed = new_accents != self.accent_data;
24482 self.accent_data = new_accents;
24483
24484 if self.diagnostics_enabled() {
24485 let new_severity = EditorSettings::get_global(cx)
24486 .diagnostics_max_severity
24487 .unwrap_or(DiagnosticSeverity::Hint);
24488 self.set_max_diagnostics_severity(new_severity, cx);
24489 }
24490 self.refresh_runnables(None, window, cx);
24491 self.update_edit_prediction_settings(cx);
24492 self.refresh_edit_prediction(true, false, window, cx);
24493 self.refresh_inline_values(cx);
24494
24495 let old_cursor_shape = self.cursor_shape;
24496 let old_show_breadcrumbs = self.show_breadcrumbs;
24497
24498 {
24499 let editor_settings = EditorSettings::get_global(cx);
24500 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24501 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24502 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24503 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24504 }
24505
24506 if old_cursor_shape != self.cursor_shape {
24507 cx.emit(EditorEvent::CursorShapeChanged);
24508 }
24509
24510 if old_show_breadcrumbs != self.show_breadcrumbs {
24511 cx.emit(EditorEvent::BreadcrumbsChanged);
24512 }
24513
24514 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24515 let project_settings = ProjectSettings::get_global(cx);
24516 (
24517 project_settings.session.restore_unsaved_buffers,
24518 project_settings.diagnostics.inline.enabled,
24519 project_settings.git.inline_blame.enabled,
24520 )
24521 };
24522 self.buffer_serialization = self
24523 .should_serialize_buffer()
24524 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24525
24526 if self.mode.is_full() {
24527 if self.show_inline_diagnostics != show_inline_diagnostics {
24528 self.show_inline_diagnostics = show_inline_diagnostics;
24529 self.refresh_inline_diagnostics(false, window, cx);
24530 }
24531
24532 if self.git_blame_inline_enabled != inline_blame_enabled {
24533 self.toggle_git_blame_inline_internal(false, window, cx);
24534 }
24535
24536 let minimap_settings = EditorSettings::get_global(cx).minimap;
24537 if self.minimap_visibility != MinimapVisibility::Disabled {
24538 if self.minimap_visibility.settings_visibility()
24539 != minimap_settings.minimap_enabled()
24540 {
24541 self.set_minimap_visibility(
24542 MinimapVisibility::for_mode(self.mode(), cx),
24543 window,
24544 cx,
24545 );
24546 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24547 minimap_entity.update(cx, |minimap_editor, cx| {
24548 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24549 })
24550 }
24551 }
24552
24553 if language_settings_changed || accents_changed {
24554 self.colorize_brackets(true, cx);
24555 }
24556
24557 if language_settings_changed {
24558 self.clear_disabled_lsp_folding_ranges(window, cx);
24559 self.refresh_document_symbols(None, cx);
24560 }
24561
24562 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24563 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24564 }) {
24565 if !inlay_splice.is_empty() {
24566 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24567 }
24568 self.refresh_document_colors(None, window, cx);
24569 }
24570
24571 self.refresh_inlay_hints(
24572 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24573 self.selections.newest_anchor().head(),
24574 &self.buffer.read(cx).snapshot(cx),
24575 cx,
24576 )),
24577 cx,
24578 );
24579
24580 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24581 .global_lsp_settings
24582 .semantic_token_rules
24583 .clone();
24584 let semantic_token_rules_changed = self
24585 .semantic_token_state
24586 .update_rules(new_semantic_token_rules);
24587 if language_settings_changed || semantic_token_rules_changed {
24588 self.invalidate_semantic_tokens(None);
24589 self.refresh_semantic_tokens(None, None, cx);
24590 }
24591 }
24592
24593 cx.notify();
24594 }
24595
24596 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24597 if !self.mode.is_full() {
24598 return;
24599 }
24600
24601 let new_accents = self.fetch_accent_data(cx);
24602 if new_accents != self.accent_data {
24603 self.accent_data = new_accents;
24604 self.colorize_brackets(true, cx);
24605 }
24606
24607 self.invalidate_semantic_tokens(None);
24608 self.refresh_semantic_tokens(None, None, cx);
24609 }
24610
24611 pub fn set_searchable(&mut self, searchable: bool) {
24612 self.searchable = searchable;
24613 }
24614
24615 pub fn searchable(&self) -> bool {
24616 self.searchable
24617 }
24618
24619 pub fn open_excerpts_in_split(
24620 &mut self,
24621 _: &OpenExcerptsSplit,
24622 window: &mut Window,
24623 cx: &mut Context<Self>,
24624 ) {
24625 self.open_excerpts_common(None, true, window, cx)
24626 }
24627
24628 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24629 self.open_excerpts_common(None, false, window, cx)
24630 }
24631
24632 pub(crate) fn open_excerpts_common(
24633 &mut self,
24634 jump_data: Option<JumpData>,
24635 split: bool,
24636 window: &mut Window,
24637 cx: &mut Context<Self>,
24638 ) {
24639 if self.buffer.read(cx).is_singleton() {
24640 cx.propagate();
24641 return;
24642 }
24643
24644 let mut new_selections_by_buffer = HashMap::default();
24645 match &jump_data {
24646 Some(JumpData::MultiBufferPoint {
24647 anchor,
24648 position,
24649 line_offset_from_top,
24650 }) => {
24651 if let Some(buffer) = self.buffer.read(cx).buffer(anchor.buffer_id) {
24652 let buffer_snapshot = buffer.read(cx).snapshot();
24653 let jump_to_point = if buffer_snapshot.can_resolve(&anchor) {
24654 language::ToPoint::to_point(anchor, &buffer_snapshot)
24655 } else {
24656 buffer_snapshot.clip_point(*position, Bias::Left)
24657 };
24658 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24659 new_selections_by_buffer.insert(
24660 buffer,
24661 (
24662 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24663 Some(*line_offset_from_top),
24664 ),
24665 );
24666 }
24667 }
24668 Some(JumpData::MultiBufferRow {
24669 row,
24670 line_offset_from_top,
24671 }) => {
24672 let point = MultiBufferPoint::new(row.0, 0);
24673 if let Some((buffer, buffer_point)) =
24674 self.buffer.read(cx).point_to_buffer_point(point, cx)
24675 {
24676 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24677 new_selections_by_buffer
24678 .entry(buffer)
24679 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24680 .0
24681 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24682 }
24683 }
24684 None => {
24685 let selections = self
24686 .selections
24687 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24688 let multi_buffer = self.buffer.read(cx);
24689 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
24690 for selection in selections {
24691 for (snapshot, range, anchor) in multi_buffer_snapshot
24692 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24693 {
24694 if let Some((text_anchor, _)) = anchor.and_then(|anchor| {
24695 multi_buffer_snapshot.anchor_to_buffer_anchor(anchor)
24696 }) {
24697 let Some(buffer_handle) = multi_buffer.buffer(text_anchor.buffer_id)
24698 else {
24699 continue;
24700 };
24701 let offset = text::ToOffset::to_offset(
24702 &text_anchor,
24703 &buffer_handle.read(cx).snapshot(),
24704 );
24705 let range = BufferOffset(offset)..BufferOffset(offset);
24706 new_selections_by_buffer
24707 .entry(buffer_handle)
24708 .or_insert((Vec::new(), None))
24709 .0
24710 .push(range)
24711 } else {
24712 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24713 else {
24714 continue;
24715 };
24716 new_selections_by_buffer
24717 .entry(buffer_handle)
24718 .or_insert((Vec::new(), None))
24719 .0
24720 .push(range)
24721 }
24722 }
24723 }
24724 }
24725 }
24726
24727 if self.delegate_open_excerpts {
24728 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24729 .into_iter()
24730 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24731 .collect();
24732 if !selections_by_buffer.is_empty() {
24733 cx.emit(EditorEvent::OpenExcerptsRequested {
24734 selections_by_buffer,
24735 split,
24736 });
24737 }
24738 return;
24739 }
24740
24741 let Some(workspace) = self.workspace() else {
24742 cx.propagate();
24743 return;
24744 };
24745
24746 new_selections_by_buffer
24747 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24748
24749 if new_selections_by_buffer.is_empty() {
24750 return;
24751 }
24752
24753 Self::open_buffers_in_workspace(
24754 workspace.downgrade(),
24755 new_selections_by_buffer,
24756 split,
24757 window,
24758 cx,
24759 );
24760 }
24761
24762 pub(crate) fn open_buffers_in_workspace(
24763 workspace: WeakEntity<Workspace>,
24764 new_selections_by_buffer: HashMap<
24765 Entity<language::Buffer>,
24766 (Vec<Range<BufferOffset>>, Option<u32>),
24767 >,
24768 split: bool,
24769 window: &mut Window,
24770 cx: &mut App,
24771 ) {
24772 // We defer the pane interaction because we ourselves are a workspace item
24773 // and activating a new item causes the pane to call a method on us reentrantly,
24774 // which panics if we're on the stack.
24775 window.defer(cx, move |window, cx| {
24776 workspace
24777 .update(cx, |workspace, cx| {
24778 let pane = if split {
24779 workspace.adjacent_pane(window, cx)
24780 } else {
24781 workspace.active_pane().clone()
24782 };
24783
24784 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24785 let buffer_read = buffer.read(cx);
24786 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24787 (true, project::File::from_dyn(Some(file)).is_some())
24788 } else {
24789 (false, false)
24790 };
24791
24792 // If project file is none workspace.open_project_item will fail to open the excerpt
24793 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24794 // so we check if there's a tab match in that case first
24795 let editor = (!has_file || !is_project_file)
24796 .then(|| {
24797 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24798 // so `workspace.open_project_item` will never find them, always opening a new editor.
24799 // Instead, we try to activate the existing editor in the pane first.
24800 let (editor, pane_item_index, pane_item_id) =
24801 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24802 let editor = item.downcast::<Editor>()?;
24803 let singleton_buffer =
24804 editor.read(cx).buffer().read(cx).as_singleton()?;
24805 if singleton_buffer == buffer {
24806 Some((editor, i, item.item_id()))
24807 } else {
24808 None
24809 }
24810 })?;
24811 pane.update(cx, |pane, cx| {
24812 pane.activate_item(pane_item_index, true, true, window, cx);
24813 if !PreviewTabsSettings::get_global(cx)
24814 .enable_preview_from_multibuffer
24815 {
24816 pane.unpreview_item_if_preview(pane_item_id);
24817 }
24818 });
24819 Some(editor)
24820 })
24821 .flatten()
24822 .unwrap_or_else(|| {
24823 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24824 .enable_keep_preview_on_code_navigation;
24825 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24826 .enable_preview_from_multibuffer;
24827 workspace.open_project_item::<Self>(
24828 pane.clone(),
24829 buffer,
24830 true,
24831 true,
24832 keep_old_preview,
24833 allow_new_preview,
24834 window,
24835 cx,
24836 )
24837 });
24838
24839 editor.update(cx, |editor, cx| {
24840 if has_file && !is_project_file {
24841 editor.set_read_only(true);
24842 }
24843 let autoscroll = match scroll_offset {
24844 Some(scroll_offset) => {
24845 Autoscroll::top_relative(scroll_offset as usize)
24846 }
24847 None => Autoscroll::newest(),
24848 };
24849 let nav_history = editor.nav_history.take();
24850 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24851 let Some(buffer_snapshot) = multibuffer_snapshot.as_singleton() else {
24852 return;
24853 };
24854 editor.change_selections(
24855 SelectionEffects::scroll(autoscroll),
24856 window,
24857 cx,
24858 |s| {
24859 s.select_ranges(ranges.into_iter().map(|range| {
24860 let range = buffer_snapshot.anchor_before(range.start)
24861 ..buffer_snapshot.anchor_after(range.end);
24862 multibuffer_snapshot
24863 .buffer_anchor_range_to_anchor_range(range)
24864 .unwrap()
24865 }));
24866 },
24867 );
24868 editor.nav_history = nav_history;
24869 });
24870 }
24871 })
24872 .ok();
24873 });
24874 }
24875
24876 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24877 let snapshot = self.buffer.read(cx).read(cx);
24878 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24879 Some(
24880 ranges
24881 .iter()
24882 .map(move |range| {
24883 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24884 })
24885 .collect(),
24886 )
24887 }
24888
24889 fn selection_replacement_ranges(
24890 &self,
24891 range: Range<MultiBufferOffsetUtf16>,
24892 cx: &mut App,
24893 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24894 let selections = self
24895 .selections
24896 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24897 let newest_selection = selections
24898 .iter()
24899 .max_by_key(|selection| selection.id)
24900 .unwrap();
24901 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24902 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24903 let snapshot = self.buffer.read(cx).read(cx);
24904 selections
24905 .into_iter()
24906 .map(|mut selection| {
24907 selection.start.0.0 =
24908 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24909 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24910 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24911 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24912 })
24913 .collect()
24914 }
24915
24916 fn report_editor_event(
24917 &self,
24918 reported_event: ReportEditorEvent,
24919 file_extension: Option<String>,
24920 cx: &App,
24921 ) {
24922 if cfg!(any(test, feature = "test-support")) {
24923 return;
24924 }
24925
24926 let Some(project) = &self.project else { return };
24927
24928 // If None, we are in a file without an extension
24929 let file = self
24930 .buffer
24931 .read(cx)
24932 .as_singleton()
24933 .and_then(|b| b.read(cx).file());
24934 let file_extension = file_extension.or(file
24935 .as_ref()
24936 .and_then(|file| Path::new(file.file_name(cx)).extension())
24937 .and_then(|e| e.to_str())
24938 .map(|a| a.to_string()));
24939
24940 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24941 .map(|vim_mode| vim_mode.0)
24942 .unwrap_or(false);
24943
24944 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24945 let copilot_enabled = edit_predictions_provider
24946 == language::language_settings::EditPredictionProvider::Copilot;
24947 let copilot_enabled_for_language = self
24948 .buffer
24949 .read(cx)
24950 .language_settings(cx)
24951 .show_edit_predictions;
24952
24953 let project = project.read(cx);
24954 let event_type = reported_event.event_type();
24955
24956 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24957 telemetry::event!(
24958 event_type,
24959 type = if auto_saved {"autosave"} else {"manual"},
24960 file_extension,
24961 vim_mode,
24962 copilot_enabled,
24963 copilot_enabled_for_language,
24964 edit_predictions_provider,
24965 is_via_ssh = project.is_via_remote_server(),
24966 );
24967 } else {
24968 telemetry::event!(
24969 event_type,
24970 file_extension,
24971 vim_mode,
24972 copilot_enabled,
24973 copilot_enabled_for_language,
24974 edit_predictions_provider,
24975 is_via_ssh = project.is_via_remote_server(),
24976 );
24977 };
24978 }
24979
24980 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24981 /// with each line being an array of {text, highlight} objects.
24982 fn copy_highlight_json(
24983 &mut self,
24984 _: &CopyHighlightJson,
24985 _: &mut Window,
24986 cx: &mut Context<Self>,
24987 ) {
24988 #[derive(Serialize)]
24989 struct Chunk<'a> {
24990 text: String,
24991 highlight: Option<&'a str>,
24992 }
24993
24994 let snapshot = self.buffer.read(cx).snapshot(cx);
24995 let mut selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
24996 let max_point = snapshot.max_point();
24997
24998 let range = if self.selections.line_mode() {
24999 selection.start = Point::new(selection.start.row, 0);
25000 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
25001 selection.goal = SelectionGoal::None;
25002 selection.range()
25003 } else if selection.is_empty() {
25004 Point::new(0, 0)..max_point
25005 } else {
25006 selection.range()
25007 };
25008
25009 let chunks = snapshot.chunks(range, true);
25010 let mut lines = Vec::new();
25011 let mut line: VecDeque<Chunk> = VecDeque::new();
25012
25013 let Some(style) = self.style.as_ref() else {
25014 return;
25015 };
25016
25017 for chunk in chunks {
25018 let highlight = chunk
25019 .syntax_highlight_id
25020 .and_then(|id| style.syntax.get_capture_name(id));
25021
25022 let mut chunk_lines = chunk.text.split('\n').peekable();
25023 while let Some(text) = chunk_lines.next() {
25024 let mut merged_with_last_token = false;
25025 if let Some(last_token) = line.back_mut()
25026 && last_token.highlight == highlight
25027 {
25028 last_token.text.push_str(text);
25029 merged_with_last_token = true;
25030 }
25031
25032 if !merged_with_last_token {
25033 line.push_back(Chunk {
25034 text: text.into(),
25035 highlight,
25036 });
25037 }
25038
25039 if chunk_lines.peek().is_some() {
25040 if line.len() > 1 && line.front().unwrap().text.is_empty() {
25041 line.pop_front();
25042 }
25043 if line.len() > 1 && line.back().unwrap().text.is_empty() {
25044 line.pop_back();
25045 }
25046
25047 lines.push(mem::take(&mut line));
25048 }
25049 }
25050 }
25051
25052 if line.iter().any(|chunk| !chunk.text.is_empty()) {
25053 lines.push(line);
25054 }
25055
25056 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
25057 return;
25058 };
25059 cx.write_to_clipboard(ClipboardItem::new_string(lines));
25060 }
25061
25062 pub fn open_context_menu(
25063 &mut self,
25064 _: &OpenContextMenu,
25065 window: &mut Window,
25066 cx: &mut Context<Self>,
25067 ) {
25068 self.request_autoscroll(Autoscroll::newest(), cx);
25069 let position = self
25070 .selections
25071 .newest_display(&self.display_snapshot(cx))
25072 .start;
25073 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
25074 }
25075
25076 pub fn replay_insert_event(
25077 &mut self,
25078 text: &str,
25079 relative_utf16_range: Option<Range<isize>>,
25080 window: &mut Window,
25081 cx: &mut Context<Self>,
25082 ) {
25083 if !self.input_enabled {
25084 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25085 return;
25086 }
25087 if let Some(relative_utf16_range) = relative_utf16_range {
25088 let selections = self
25089 .selections
25090 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25091 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25092 let new_ranges = selections.into_iter().map(|range| {
25093 let start = MultiBufferOffsetUtf16(OffsetUtf16(
25094 range
25095 .head()
25096 .0
25097 .0
25098 .saturating_add_signed(relative_utf16_range.start),
25099 ));
25100 let end = MultiBufferOffsetUtf16(OffsetUtf16(
25101 range
25102 .head()
25103 .0
25104 .0
25105 .saturating_add_signed(relative_utf16_range.end),
25106 ));
25107 start..end
25108 });
25109 s.select_ranges(new_ranges);
25110 });
25111 }
25112
25113 self.handle_input(text, window, cx);
25114 }
25115
25116 pub fn is_focused(&self, window: &Window) -> bool {
25117 self.focus_handle.is_focused(window)
25118 }
25119
25120 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25121 cx.emit(EditorEvent::Focused);
25122
25123 if let Some(descendant) = self
25124 .last_focused_descendant
25125 .take()
25126 .and_then(|descendant| descendant.upgrade())
25127 {
25128 window.focus(&descendant, cx);
25129 } else {
25130 if let Some(blame) = self.blame.as_ref() {
25131 blame.update(cx, GitBlame::focus)
25132 }
25133
25134 self.blink_manager.update(cx, BlinkManager::enable);
25135 self.show_cursor_names(window, cx);
25136 self.buffer.update(cx, |buffer, cx| {
25137 buffer.finalize_last_transaction(cx);
25138 if self.leader_id.is_none() {
25139 buffer.set_active_selections(
25140 &self.selections.disjoint_anchors_arc(),
25141 self.selections.line_mode(),
25142 self.cursor_shape,
25143 cx,
25144 );
25145 }
25146 });
25147
25148 if let Some(position_map) = self.last_position_map.clone()
25149 && !self.mouse_cursor_hidden
25150 {
25151 EditorElement::mouse_moved(
25152 self,
25153 &MouseMoveEvent {
25154 position: window.mouse_position(),
25155 pressed_button: None,
25156 modifiers: window.modifiers(),
25157 },
25158 &position_map,
25159 None,
25160 window,
25161 cx,
25162 );
25163 }
25164 }
25165 }
25166
25167 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25168 cx.emit(EditorEvent::FocusedIn)
25169 }
25170
25171 fn handle_focus_out(
25172 &mut self,
25173 event: FocusOutEvent,
25174 _window: &mut Window,
25175 cx: &mut Context<Self>,
25176 ) {
25177 if event.blurred != self.focus_handle {
25178 self.last_focused_descendant = Some(event.blurred);
25179 }
25180 self.selection_drag_state = SelectionDragState::None;
25181 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25182 }
25183
25184 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25185 self.blink_manager.update(cx, BlinkManager::disable);
25186 self.buffer
25187 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25188
25189 if let Some(blame) = self.blame.as_ref() {
25190 blame.update(cx, GitBlame::blur)
25191 }
25192 if !self.hover_state.focused(window, cx) {
25193 hide_hover(self, cx);
25194 }
25195 if !self
25196 .context_menu
25197 .borrow()
25198 .as_ref()
25199 .is_some_and(|context_menu| context_menu.focused(window, cx))
25200 {
25201 self.hide_context_menu(window, cx);
25202 }
25203 self.take_active_edit_prediction(true, cx);
25204 cx.emit(EditorEvent::Blurred);
25205 cx.notify();
25206 }
25207
25208 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25209 let mut pending: String = window
25210 .pending_input_keystrokes()
25211 .into_iter()
25212 .flatten()
25213 .filter_map(|keystroke| keystroke.key_char.clone())
25214 .collect();
25215
25216 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25217 pending = "".to_string();
25218 }
25219
25220 let existing_pending = self
25221 .text_highlights(HighlightKey::PendingInput, cx)
25222 .map(|(_, ranges)| ranges.to_vec());
25223 if existing_pending.is_none() && pending.is_empty() {
25224 return;
25225 }
25226 let transaction =
25227 self.transact(window, cx, |this, window, cx| {
25228 let selections = this
25229 .selections
25230 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25231 let edits = selections
25232 .iter()
25233 .map(|selection| (selection.end..selection.end, pending.clone()));
25234 this.edit(edits, cx);
25235 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25236 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25237 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25238 }));
25239 });
25240 if let Some(existing_ranges) = existing_pending {
25241 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25242 this.edit(edits, cx);
25243 }
25244 });
25245
25246 let snapshot = self.snapshot(window, cx);
25247 let ranges = self
25248 .selections
25249 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25250 .into_iter()
25251 .map(|selection| {
25252 snapshot.buffer_snapshot().anchor_after(selection.end)
25253 ..snapshot
25254 .buffer_snapshot()
25255 .anchor_before(selection.end + pending.len())
25256 })
25257 .collect();
25258
25259 if pending.is_empty() {
25260 self.clear_highlights(HighlightKey::PendingInput, cx);
25261 } else {
25262 self.highlight_text(
25263 HighlightKey::PendingInput,
25264 ranges,
25265 HighlightStyle {
25266 underline: Some(UnderlineStyle {
25267 thickness: px(1.),
25268 color: None,
25269 wavy: false,
25270 }),
25271 ..Default::default()
25272 },
25273 cx,
25274 );
25275 }
25276
25277 self.ime_transaction = self.ime_transaction.or(transaction);
25278 if let Some(transaction) = self.ime_transaction {
25279 self.buffer.update(cx, |buffer, cx| {
25280 buffer.group_until_transaction(transaction, cx);
25281 });
25282 }
25283
25284 if self
25285 .text_highlights(HighlightKey::PendingInput, cx)
25286 .is_none()
25287 {
25288 self.ime_transaction.take();
25289 }
25290 }
25291
25292 pub fn register_action_renderer(
25293 &mut self,
25294 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25295 ) -> Subscription {
25296 let id = self.next_editor_action_id.post_inc();
25297 self.editor_actions
25298 .borrow_mut()
25299 .insert(id, Box::new(listener));
25300
25301 let editor_actions = self.editor_actions.clone();
25302 Subscription::new(move || {
25303 editor_actions.borrow_mut().remove(&id);
25304 })
25305 }
25306
25307 pub fn register_action<A: Action>(
25308 &mut self,
25309 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25310 ) -> Subscription {
25311 let id = self.next_editor_action_id.post_inc();
25312 let listener = Arc::new(listener);
25313 self.editor_actions.borrow_mut().insert(
25314 id,
25315 Box::new(move |_, window, _| {
25316 let listener = listener.clone();
25317 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25318 let action = action.downcast_ref().unwrap();
25319 if phase == DispatchPhase::Bubble {
25320 listener(action, window, cx)
25321 }
25322 })
25323 }),
25324 );
25325
25326 let editor_actions = self.editor_actions.clone();
25327 Subscription::new(move || {
25328 editor_actions.borrow_mut().remove(&id);
25329 })
25330 }
25331
25332 pub fn file_header_size(&self) -> u32 {
25333 FILE_HEADER_HEIGHT
25334 }
25335
25336 pub fn restore(
25337 &mut self,
25338 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25339 window: &mut Window,
25340 cx: &mut Context<Self>,
25341 ) {
25342 self.buffer().update(cx, |multi_buffer, cx| {
25343 for (buffer_id, changes) in revert_changes {
25344 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25345 buffer.update(cx, |buffer, cx| {
25346 buffer.edit(
25347 changes
25348 .into_iter()
25349 .map(|(range, text)| (range, text.to_string())),
25350 None,
25351 cx,
25352 );
25353 });
25354 }
25355 }
25356 });
25357 let selections = self
25358 .selections
25359 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
25360 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25361 s.select(selections);
25362 });
25363 }
25364
25365 pub fn to_pixel_point(
25366 &mut self,
25367 source: Anchor,
25368 editor_snapshot: &EditorSnapshot,
25369 window: &mut Window,
25370 cx: &mut App,
25371 ) -> Option<gpui::Point<Pixels>> {
25372 let source_point = source.to_display_point(editor_snapshot);
25373 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25374 }
25375
25376 pub fn display_to_pixel_point(
25377 &mut self,
25378 source: DisplayPoint,
25379 editor_snapshot: &EditorSnapshot,
25380 window: &mut Window,
25381 cx: &mut App,
25382 ) -> Option<gpui::Point<Pixels>> {
25383 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25384 let text_layout_details = self.text_layout_details(window, cx);
25385 let scroll_top = text_layout_details
25386 .scroll_anchor
25387 .scroll_position(editor_snapshot)
25388 .y;
25389
25390 if source.row().as_f64() < scroll_top.floor() {
25391 return None;
25392 }
25393 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25394 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25395 Some(gpui::Point::new(source_x, source_y))
25396 }
25397
25398 pub fn has_visible_completions_menu(&self) -> bool {
25399 !self.edit_prediction_preview_is_active()
25400 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25401 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25402 })
25403 }
25404
25405 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25406 if self.mode.is_minimap() {
25407 return;
25408 }
25409 self.addons
25410 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25411 }
25412
25413 pub fn unregister_addon<T: Addon>(&mut self) {
25414 self.addons.remove(&std::any::TypeId::of::<T>());
25415 }
25416
25417 pub fn addon<T: Addon>(&self) -> Option<&T> {
25418 let type_id = std::any::TypeId::of::<T>();
25419 self.addons
25420 .get(&type_id)
25421 .and_then(|item| item.to_any().downcast_ref::<T>())
25422 }
25423
25424 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25425 let type_id = std::any::TypeId::of::<T>();
25426 self.addons
25427 .get_mut(&type_id)
25428 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25429 }
25430
25431 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25432 let text_layout_details = self.text_layout_details(window, cx);
25433 let style = &text_layout_details.editor_style;
25434 let font_id = window.text_system().resolve_font(&style.text.font());
25435 let font_size = style.text.font_size.to_pixels(window.rem_size());
25436 let line_height = style.text.line_height_in_pixels(window.rem_size());
25437 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25438 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25439
25440 CharacterDimensions {
25441 em_width,
25442 em_advance,
25443 line_height,
25444 }
25445 }
25446
25447 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25448 self.load_diff_task.clone()
25449 }
25450
25451 fn read_metadata_from_db(
25452 &mut self,
25453 item_id: u64,
25454 workspace_id: WorkspaceId,
25455 window: &mut Window,
25456 cx: &mut Context<Editor>,
25457 ) {
25458 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25459 && !self.mode.is_minimap()
25460 && WorkspaceSettings::get(None, cx).restore_on_startup
25461 != RestoreOnStartupBehavior::EmptyTab
25462 {
25463 let buffer_snapshot = OnceCell::new();
25464
25465 // Get file path for path-based fold lookup
25466 let file_path: Option<Arc<Path>> =
25467 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25468 project::File::from_dyn(buffer.read(cx).file())
25469 .map(|file| Arc::from(file.abs_path(cx)))
25470 });
25471
25472 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25473 let db = EditorDb::global(cx);
25474 let (folds, needs_migration) = if let Some(ref path) = file_path {
25475 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
25476 && !folds.is_empty()
25477 {
25478 (Some(folds), false)
25479 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
25480 && !folds.is_empty()
25481 {
25482 // Found old editor_folds data, will migrate to file_folds
25483 (Some(folds), true)
25484 } else {
25485 (None, false)
25486 }
25487 } else {
25488 // No file path, try editor_folds as fallback
25489 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
25490 (folds.filter(|f| !f.is_empty()), false)
25491 };
25492
25493 if let Some(folds) = folds {
25494 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25495 let snapshot_len = snapshot.len().0;
25496
25497 // Helper: search for fingerprint in buffer, return offset if found
25498 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25499 // Ensure we start at a character boundary (defensive)
25500 let search_start = snapshot
25501 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25502 .0;
25503 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25504
25505 let mut byte_offset = search_start;
25506 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25507 if byte_offset > search_end {
25508 break;
25509 }
25510 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25511 return Some(byte_offset);
25512 }
25513 byte_offset += ch.len_utf8();
25514 }
25515 None
25516 };
25517
25518 // Track search position to handle duplicate fingerprints correctly.
25519 // Folds are stored in document order, so we advance after each match.
25520 let mut search_start = 0usize;
25521
25522 // Collect db_folds for migration (only folds with valid fingerprints)
25523 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25524
25525 let valid_folds: Vec<_> = folds
25526 .into_iter()
25527 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25528 // Skip folds without fingerprints (old data before migration)
25529 let sfp = start_fp?;
25530 let efp = end_fp?;
25531 let efp_len = efp.len();
25532
25533 // Fast path: check if fingerprints match at stored offsets
25534 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25535 let start_matches = stored_start < snapshot_len
25536 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25537 let efp_check_pos = stored_end.saturating_sub(efp_len);
25538 let end_matches = efp_check_pos >= stored_start
25539 && stored_end <= snapshot_len
25540 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25541
25542 let (new_start, new_end) = if start_matches && end_matches {
25543 // Offsets unchanged, use stored values
25544 (stored_start, stored_end)
25545 } else if sfp == efp {
25546 // Short fold: identical fingerprints can only match once per search
25547 // Use stored fold length to compute new_end
25548 let new_start = find_fingerprint(&sfp, search_start)?;
25549 let fold_len = stored_end - stored_start;
25550 let new_end = new_start + fold_len;
25551 (new_start, new_end)
25552 } else {
25553 // Slow path: search for fingerprints in buffer
25554 let new_start = find_fingerprint(&sfp, search_start)?;
25555 // Search for end_fp after start, then add efp_len to get actual fold end
25556 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25557 let new_end = efp_pos + efp_len;
25558 (new_start, new_end)
25559 };
25560
25561 // Advance search position for next fold
25562 search_start = new_end;
25563
25564 // Validate fold makes sense (end must be after start)
25565 if new_end <= new_start {
25566 return None;
25567 }
25568
25569 // Collect for migration if needed
25570 if needs_migration {
25571 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25572 }
25573
25574 Some(
25575 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25576 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25577 )
25578 })
25579 .collect();
25580
25581 if !valid_folds.is_empty() {
25582 self.fold_ranges(valid_folds, false, window, cx);
25583
25584 // Migrate from editor_folds to file_folds if we loaded from old table
25585 if needs_migration {
25586 if let Some(ref path) = file_path {
25587 let path = path.clone();
25588 let db = EditorDb::global(cx);
25589 cx.spawn(async move |_, _| {
25590 db.save_file_folds(workspace_id, path, db_folds_for_migration)
25591 .await
25592 .log_err();
25593 })
25594 .detach();
25595 }
25596 }
25597 }
25598 }
25599
25600 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
25601 && !selections.is_empty()
25602 {
25603 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25604 // skip adding the initial selection to selection history
25605 self.selection_history.mode = SelectionHistoryMode::Skipping;
25606 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25607 s.select_ranges(selections.into_iter().map(|(start, end)| {
25608 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25609 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25610 }));
25611 });
25612 self.selection_history.mode = SelectionHistoryMode::Normal;
25613 };
25614 }
25615
25616 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25617 }
25618
25619 /// Load folds from the file_folds database table by file path.
25620 /// Used when manually opening a file that was previously closed.
25621 fn load_folds_from_db(
25622 &mut self,
25623 workspace_id: WorkspaceId,
25624 file_path: PathBuf,
25625 window: &mut Window,
25626 cx: &mut Context<Editor>,
25627 ) {
25628 if self.mode.is_minimap()
25629 || WorkspaceSettings::get(None, cx).restore_on_startup
25630 == RestoreOnStartupBehavior::EmptyTab
25631 {
25632 return;
25633 }
25634
25635 let Some(folds) = EditorDb::global(cx)
25636 .get_file_folds(workspace_id, &file_path)
25637 .log_err()
25638 else {
25639 return;
25640 };
25641 if folds.is_empty() {
25642 return;
25643 }
25644
25645 let snapshot = self.buffer.read(cx).snapshot(cx);
25646 let snapshot_len = snapshot.len().0;
25647
25648 // Helper: search for fingerprint in buffer, return offset if found
25649 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25650 let search_start = snapshot
25651 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25652 .0;
25653 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25654
25655 let mut byte_offset = search_start;
25656 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25657 if byte_offset > search_end {
25658 break;
25659 }
25660 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25661 return Some(byte_offset);
25662 }
25663 byte_offset += ch.len_utf8();
25664 }
25665 None
25666 };
25667
25668 let mut search_start = 0usize;
25669
25670 let valid_folds: Vec<_> = folds
25671 .into_iter()
25672 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25673 let sfp = start_fp?;
25674 let efp = end_fp?;
25675 let efp_len = efp.len();
25676
25677 let start_matches = stored_start < snapshot_len
25678 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25679 let efp_check_pos = stored_end.saturating_sub(efp_len);
25680 let end_matches = efp_check_pos >= stored_start
25681 && stored_end <= snapshot_len
25682 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25683
25684 let (new_start, new_end) = if start_matches && end_matches {
25685 (stored_start, stored_end)
25686 } else if sfp == efp {
25687 let new_start = find_fingerprint(&sfp, search_start)?;
25688 let fold_len = stored_end - stored_start;
25689 let new_end = new_start + fold_len;
25690 (new_start, new_end)
25691 } else {
25692 let new_start = find_fingerprint(&sfp, search_start)?;
25693 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25694 let new_end = efp_pos + efp_len;
25695 (new_start, new_end)
25696 };
25697
25698 search_start = new_end;
25699
25700 if new_end <= new_start {
25701 return None;
25702 }
25703
25704 Some(
25705 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25706 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25707 )
25708 })
25709 .collect();
25710
25711 if !valid_folds.is_empty() {
25712 self.fold_ranges(valid_folds, false, window, cx);
25713 }
25714 }
25715
25716 fn lsp_data_enabled(&self) -> bool {
25717 self.enable_lsp_data && self.mode().is_full()
25718 }
25719
25720 fn update_lsp_data(
25721 &mut self,
25722 for_buffer: Option<BufferId>,
25723 window: &mut Window,
25724 cx: &mut Context<'_, Self>,
25725 ) {
25726 if !self.lsp_data_enabled() {
25727 return;
25728 }
25729
25730 if let Some(buffer_id) = for_buffer {
25731 self.pull_diagnostics(buffer_id, window, cx);
25732 }
25733 self.refresh_semantic_tokens(for_buffer, None, cx);
25734 self.refresh_document_colors(for_buffer, window, cx);
25735 self.refresh_folding_ranges(for_buffer, window, cx);
25736 self.refresh_document_symbols(for_buffer, cx);
25737 }
25738
25739 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25740 if !self.lsp_data_enabled() {
25741 return;
25742 }
25743 let visible_buffers: Vec<_> = self
25744 .visible_buffers(cx)
25745 .into_iter()
25746 .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
25747 .collect();
25748 for visible_buffer in visible_buffers {
25749 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25750 }
25751 }
25752
25753 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25754 if !self.lsp_data_enabled() {
25755 return;
25756 }
25757
25758 if !self.registered_buffers.contains_key(&buffer_id)
25759 && let Some(project) = self.project.as_ref()
25760 {
25761 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25762 project.update(cx, |project, cx| {
25763 self.registered_buffers.insert(
25764 buffer_id,
25765 project.register_buffer_with_language_servers(&buffer, cx),
25766 );
25767 });
25768 } else {
25769 self.registered_buffers.remove(&buffer_id);
25770 }
25771 }
25772 }
25773
25774 fn create_style(&self, cx: &App) -> EditorStyle {
25775 let settings = ThemeSettings::get_global(cx);
25776
25777 let mut text_style = match self.mode {
25778 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25779 color: cx.theme().colors().editor_foreground,
25780 font_family: settings.ui_font.family.clone(),
25781 font_features: settings.ui_font.features.clone(),
25782 font_fallbacks: settings.ui_font.fallbacks.clone(),
25783 font_size: rems(0.875).into(),
25784 font_weight: settings.ui_font.weight,
25785 line_height: relative(settings.buffer_line_height.value()),
25786 ..Default::default()
25787 },
25788 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25789 color: cx.theme().colors().editor_foreground,
25790 font_family: settings.buffer_font.family.clone(),
25791 font_features: settings.buffer_font.features.clone(),
25792 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25793 font_size: settings.buffer_font_size(cx).into(),
25794 font_weight: settings.buffer_font.weight,
25795 line_height: relative(settings.buffer_line_height.value()),
25796 ..Default::default()
25797 },
25798 };
25799 if let Some(text_style_refinement) = &self.text_style_refinement {
25800 text_style.refine(text_style_refinement)
25801 }
25802
25803 let background = match self.mode {
25804 EditorMode::SingleLine => cx.theme().system().transparent,
25805 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25806 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25807 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25808 };
25809
25810 EditorStyle {
25811 background,
25812 border: cx.theme().colors().border,
25813 local_player: cx.theme().players().local(),
25814 text: text_style,
25815 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25816 syntax: cx.theme().syntax().clone(),
25817 status: cx.theme().status().clone(),
25818 inlay_hints_style: make_inlay_hints_style(cx),
25819 edit_prediction_styles: make_suggestion_styles(cx),
25820 unnecessary_code_fade: settings.unnecessary_code_fade,
25821 show_underlines: self.diagnostics_enabled(),
25822 }
25823 }
25824
25825 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
25826 let multibuffer = self.buffer().read(cx);
25827 let is_singleton = multibuffer.is_singleton();
25828 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25829 let buffer = multibuffer.buffer(*buffer_id)?;
25830
25831 let buffer = buffer.read(cx);
25832 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25833 let mut breadcrumbs = if is_singleton {
25834 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25835 buffer
25836 .snapshot()
25837 .resolve_file_path(
25838 self.project
25839 .as_ref()
25840 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25841 .unwrap_or_default(),
25842 cx,
25843 )
25844 .unwrap_or_else(|| {
25845 if multibuffer.is_singleton() {
25846 multibuffer.title(cx).to_string()
25847 } else {
25848 "untitled".to_string()
25849 }
25850 })
25851 });
25852 vec![HighlightedText {
25853 text: text.into(),
25854 highlights: vec![],
25855 }]
25856 } else {
25857 vec![]
25858 };
25859
25860 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
25861 text: symbol.text.clone().into(),
25862 highlights: symbol.highlight_ranges.clone(),
25863 }));
25864 Some(breadcrumbs)
25865 }
25866
25867 fn disable_lsp_data(&mut self) {
25868 self.enable_lsp_data = false;
25869 }
25870
25871 fn disable_runnables(&mut self) {
25872 self.enable_runnables = false;
25873 }
25874
25875 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
25876 self.register_visible_buffers(cx);
25877 self.colorize_brackets(false, cx);
25878 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
25879 if !self.buffer().read(cx).is_singleton() {
25880 self.update_lsp_data(None, window, cx);
25881 self.refresh_runnables(None, window, cx);
25882 }
25883 }
25884}
25885
25886fn edit_for_markdown_paste<'a>(
25887 buffer: &MultiBufferSnapshot,
25888 range: Range<MultiBufferOffset>,
25889 to_insert: &'a str,
25890 url: Option<url::Url>,
25891) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25892 if url.is_none() {
25893 return (range, Cow::Borrowed(to_insert));
25894 };
25895
25896 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25897
25898 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25899 Cow::Borrowed(to_insert)
25900 } else {
25901 Cow::Owned(format!("[{old_text}]({to_insert})"))
25902 };
25903 (range, new_text)
25904}
25905
25906fn process_completion_for_edit(
25907 completion: &Completion,
25908 intent: CompletionIntent,
25909 buffer: &Entity<Buffer>,
25910 cursor_position: &text::Anchor,
25911 cx: &mut Context<Editor>,
25912) -> CompletionEdit {
25913 let buffer = buffer.read(cx);
25914 let buffer_snapshot = buffer.snapshot();
25915 let (snippet, new_text) = if completion.is_snippet() {
25916 let mut snippet_source = completion.new_text.clone();
25917 // Workaround for typescript language server issues so that methods don't expand within
25918 // strings and functions with type expressions. The previous point is used because the query
25919 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25920 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25921 let previous_point = if previous_point.column > 0 {
25922 cursor_position.to_previous_offset(&buffer_snapshot)
25923 } else {
25924 cursor_position.to_offset(&buffer_snapshot)
25925 };
25926 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25927 && scope.prefers_label_for_snippet_in_completion()
25928 && let Some(label) = completion.label()
25929 && matches!(
25930 completion.kind(),
25931 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25932 )
25933 {
25934 snippet_source = label;
25935 }
25936 match Snippet::parse(&snippet_source).log_err() {
25937 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25938 None => (None, completion.new_text.clone()),
25939 }
25940 } else {
25941 (None, completion.new_text.clone())
25942 };
25943
25944 let mut range_to_replace = {
25945 let replace_range = &completion.replace_range;
25946 if let CompletionSource::Lsp {
25947 insert_range: Some(insert_range),
25948 ..
25949 } = &completion.source
25950 {
25951 debug_assert_eq!(
25952 insert_range.start, replace_range.start,
25953 "insert_range and replace_range should start at the same position"
25954 );
25955 debug_assert!(
25956 insert_range
25957 .start
25958 .cmp(cursor_position, &buffer_snapshot)
25959 .is_le(),
25960 "insert_range should start before or at cursor position"
25961 );
25962 debug_assert!(
25963 replace_range
25964 .start
25965 .cmp(cursor_position, &buffer_snapshot)
25966 .is_le(),
25967 "replace_range should start before or at cursor position"
25968 );
25969
25970 let should_replace = match intent {
25971 CompletionIntent::CompleteWithInsert => false,
25972 CompletionIntent::CompleteWithReplace => true,
25973 CompletionIntent::Complete | CompletionIntent::Compose => {
25974 let insert_mode = LanguageSettings::for_buffer(&buffer, cx)
25975 .completions
25976 .lsp_insert_mode;
25977 match insert_mode {
25978 LspInsertMode::Insert => false,
25979 LspInsertMode::Replace => true,
25980 LspInsertMode::ReplaceSubsequence => {
25981 let mut text_to_replace = buffer.chars_for_range(
25982 buffer.anchor_before(replace_range.start)
25983 ..buffer.anchor_after(replace_range.end),
25984 );
25985 let mut current_needle = text_to_replace.next();
25986 for haystack_ch in completion.label.text.chars() {
25987 if let Some(needle_ch) = current_needle
25988 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25989 {
25990 current_needle = text_to_replace.next();
25991 }
25992 }
25993 current_needle.is_none()
25994 }
25995 LspInsertMode::ReplaceSuffix => {
25996 if replace_range
25997 .end
25998 .cmp(cursor_position, &buffer_snapshot)
25999 .is_gt()
26000 {
26001 let range_after_cursor = *cursor_position..replace_range.end;
26002 let text_after_cursor = buffer
26003 .text_for_range(
26004 buffer.anchor_before(range_after_cursor.start)
26005 ..buffer.anchor_after(range_after_cursor.end),
26006 )
26007 .collect::<String>()
26008 .to_ascii_lowercase();
26009 completion
26010 .label
26011 .text
26012 .to_ascii_lowercase()
26013 .ends_with(&text_after_cursor)
26014 } else {
26015 true
26016 }
26017 }
26018 }
26019 }
26020 };
26021
26022 if should_replace {
26023 replace_range.clone()
26024 } else {
26025 insert_range.clone()
26026 }
26027 } else {
26028 replace_range.clone()
26029 }
26030 };
26031
26032 if range_to_replace
26033 .end
26034 .cmp(cursor_position, &buffer_snapshot)
26035 .is_lt()
26036 {
26037 range_to_replace.end = *cursor_position;
26038 }
26039
26040 CompletionEdit {
26041 new_text,
26042 replace_range: range_to_replace,
26043 snippet,
26044 }
26045}
26046
26047struct CompletionEdit {
26048 new_text: String,
26049 replace_range: Range<text::Anchor>,
26050 snippet: Option<Snippet>,
26051}
26052
26053fn comment_delimiter_for_newline(
26054 start_point: &Point,
26055 buffer: &MultiBufferSnapshot,
26056 language: &LanguageScope,
26057) -> Option<Arc<str>> {
26058 let delimiters = language.line_comment_prefixes();
26059 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
26060 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26061
26062 let num_of_whitespaces = snapshot
26063 .chars_for_range(range.clone())
26064 .take_while(|c| c.is_whitespace())
26065 .count();
26066 let comment_candidate = snapshot
26067 .chars_for_range(range.clone())
26068 .skip(num_of_whitespaces)
26069 .take(max_len_of_delimiter + 2)
26070 .collect::<String>();
26071 let (delimiter, trimmed_len, is_repl) = delimiters
26072 .iter()
26073 .filter_map(|delimiter| {
26074 let prefix = delimiter.trim_end();
26075 if comment_candidate.starts_with(prefix) {
26076 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
26077 {
26078 stripped_comment.starts_with(" %%")
26079 } else {
26080 false
26081 };
26082 Some((delimiter, prefix.len(), is_repl))
26083 } else {
26084 None
26085 }
26086 })
26087 .max_by_key(|(_, len, _)| *len)?;
26088
26089 if let Some(BlockCommentConfig {
26090 start: block_start, ..
26091 }) = language.block_comment()
26092 {
26093 let block_start_trimmed = block_start.trim_end();
26094 if block_start_trimmed.starts_with(delimiter.trim_end()) {
26095 let line_content = snapshot
26096 .chars_for_range(range.clone())
26097 .skip(num_of_whitespaces)
26098 .take(block_start_trimmed.len())
26099 .collect::<String>();
26100
26101 if line_content.starts_with(block_start_trimmed) {
26102 return None;
26103 }
26104 }
26105 }
26106
26107 let cursor_is_placed_after_comment_marker =
26108 num_of_whitespaces + trimmed_len <= start_point.column as usize;
26109 if cursor_is_placed_after_comment_marker {
26110 if !is_repl {
26111 return Some(delimiter.clone());
26112 }
26113
26114 let line_content_after_cursor: String = snapshot
26115 .chars_for_range(range)
26116 .skip(start_point.column as usize)
26117 .collect();
26118
26119 if line_content_after_cursor.trim().is_empty() {
26120 return None;
26121 } else {
26122 return Some(delimiter.clone());
26123 }
26124 } else {
26125 None
26126 }
26127}
26128
26129fn documentation_delimiter_for_newline(
26130 start_point: &Point,
26131 buffer: &MultiBufferSnapshot,
26132 language: &LanguageScope,
26133 newline_config: &mut NewlineConfig,
26134) -> Option<Arc<str>> {
26135 let BlockCommentConfig {
26136 start: start_tag,
26137 end: end_tag,
26138 prefix: delimiter,
26139 tab_size: len,
26140 } = language.documentation_comment()?;
26141 let is_within_block_comment = buffer
26142 .language_scope_at(*start_point)
26143 .is_some_and(|scope| scope.override_name() == Some("comment"));
26144 if !is_within_block_comment {
26145 return None;
26146 }
26147
26148 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26149
26150 let num_of_whitespaces = snapshot
26151 .chars_for_range(range.clone())
26152 .take_while(|c| c.is_whitespace())
26153 .count();
26154
26155 // 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.
26156 let column = start_point.column;
26157 let cursor_is_after_start_tag = {
26158 let start_tag_len = start_tag.len();
26159 let start_tag_line = snapshot
26160 .chars_for_range(range.clone())
26161 .skip(num_of_whitespaces)
26162 .take(start_tag_len)
26163 .collect::<String>();
26164 if start_tag_line.starts_with(start_tag.as_ref()) {
26165 num_of_whitespaces + start_tag_len <= column as usize
26166 } else {
26167 false
26168 }
26169 };
26170
26171 let cursor_is_after_delimiter = {
26172 let delimiter_trim = delimiter.trim_end();
26173 let delimiter_line = snapshot
26174 .chars_for_range(range.clone())
26175 .skip(num_of_whitespaces)
26176 .take(delimiter_trim.len())
26177 .collect::<String>();
26178 if delimiter_line.starts_with(delimiter_trim) {
26179 num_of_whitespaces + delimiter_trim.len() <= column as usize
26180 } else {
26181 false
26182 }
26183 };
26184
26185 let mut needs_extra_line = false;
26186 let mut extra_line_additional_indent = IndentSize::spaces(0);
26187
26188 let cursor_is_before_end_tag_if_exists = {
26189 let mut char_position = 0u32;
26190 let mut end_tag_offset = None;
26191
26192 'outer: for chunk in snapshot.text_for_range(range) {
26193 if let Some(byte_pos) = chunk.find(&**end_tag) {
26194 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26195 end_tag_offset = Some(char_position + chars_before_match);
26196 break 'outer;
26197 }
26198 char_position += chunk.chars().count() as u32;
26199 }
26200
26201 if let Some(end_tag_offset) = end_tag_offset {
26202 let cursor_is_before_end_tag = column <= end_tag_offset;
26203 if cursor_is_after_start_tag {
26204 if cursor_is_before_end_tag {
26205 needs_extra_line = true;
26206 }
26207 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26208 if cursor_is_at_start_of_end_tag {
26209 extra_line_additional_indent.len = *len;
26210 }
26211 }
26212 cursor_is_before_end_tag
26213 } else {
26214 true
26215 }
26216 };
26217
26218 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26219 && cursor_is_before_end_tag_if_exists
26220 {
26221 let additional_indent = if cursor_is_after_start_tag {
26222 IndentSize::spaces(*len)
26223 } else {
26224 IndentSize::spaces(0)
26225 };
26226
26227 *newline_config = NewlineConfig::Newline {
26228 additional_indent,
26229 extra_line_additional_indent: if needs_extra_line {
26230 Some(extra_line_additional_indent)
26231 } else {
26232 None
26233 },
26234 prevent_auto_indent: true,
26235 };
26236 Some(delimiter.clone())
26237 } else {
26238 None
26239 }
26240}
26241
26242const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26243
26244fn list_delimiter_for_newline(
26245 start_point: &Point,
26246 buffer: &MultiBufferSnapshot,
26247 language: &LanguageScope,
26248 newline_config: &mut NewlineConfig,
26249) -> Option<Arc<str>> {
26250 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26251
26252 let num_of_whitespaces = snapshot
26253 .chars_for_range(range.clone())
26254 .take_while(|c| c.is_whitespace())
26255 .count();
26256
26257 let task_list_entries: Vec<_> = language
26258 .task_list()
26259 .into_iter()
26260 .flat_map(|config| {
26261 config
26262 .prefixes
26263 .iter()
26264 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26265 })
26266 .collect();
26267 let unordered_list_entries: Vec<_> = language
26268 .unordered_list()
26269 .iter()
26270 .map(|marker| (marker.as_ref(), marker.as_ref()))
26271 .collect();
26272
26273 let all_entries: Vec<_> = task_list_entries
26274 .into_iter()
26275 .chain(unordered_list_entries)
26276 .collect();
26277
26278 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26279 let candidate: String = snapshot
26280 .chars_for_range(range.clone())
26281 .skip(num_of_whitespaces)
26282 .take(max_prefix_len)
26283 .collect();
26284
26285 if let Some((prefix, continuation)) = all_entries
26286 .iter()
26287 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26288 .max_by_key(|(prefix, _)| prefix.len())
26289 {
26290 let end_of_prefix = num_of_whitespaces + prefix.len();
26291 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26292 let has_content_after_marker = snapshot
26293 .chars_for_range(range)
26294 .skip(end_of_prefix)
26295 .any(|c| !c.is_whitespace());
26296
26297 if has_content_after_marker && cursor_is_after_prefix {
26298 return Some((*continuation).into());
26299 }
26300
26301 if start_point.column as usize == end_of_prefix {
26302 if num_of_whitespaces == 0 {
26303 *newline_config = NewlineConfig::ClearCurrentLine;
26304 } else {
26305 *newline_config = NewlineConfig::UnindentCurrentLine {
26306 continuation: (*continuation).into(),
26307 };
26308 }
26309 }
26310
26311 return None;
26312 }
26313 }
26314
26315 let candidate: String = snapshot
26316 .chars_for_range(range.clone())
26317 .skip(num_of_whitespaces)
26318 .take(ORDERED_LIST_MAX_MARKER_LEN)
26319 .collect();
26320
26321 for ordered_config in language.ordered_list() {
26322 let regex = match Regex::new(&ordered_config.pattern) {
26323 Ok(r) => r,
26324 Err(_) => continue,
26325 };
26326
26327 if let Some(captures) = regex.captures(&candidate) {
26328 let full_match = captures.get(0)?;
26329 let marker_len = full_match.len();
26330 let end_of_prefix = num_of_whitespaces + marker_len;
26331 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26332
26333 let has_content_after_marker = snapshot
26334 .chars_for_range(range)
26335 .skip(end_of_prefix)
26336 .any(|c| !c.is_whitespace());
26337
26338 if has_content_after_marker && cursor_is_after_prefix {
26339 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26340 let continuation = ordered_config
26341 .format
26342 .replace("{1}", &(number + 1).to_string());
26343 return Some(continuation.into());
26344 }
26345
26346 if start_point.column as usize == end_of_prefix {
26347 let continuation = ordered_config.format.replace("{1}", "1");
26348 if num_of_whitespaces == 0 {
26349 *newline_config = NewlineConfig::ClearCurrentLine;
26350 } else {
26351 *newline_config = NewlineConfig::UnindentCurrentLine {
26352 continuation: continuation.into(),
26353 };
26354 }
26355 }
26356
26357 return None;
26358 }
26359 }
26360
26361 None
26362}
26363
26364fn is_list_prefix_row(
26365 row: MultiBufferRow,
26366 buffer: &MultiBufferSnapshot,
26367 language: &LanguageScope,
26368) -> bool {
26369 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26370 return false;
26371 };
26372
26373 let num_of_whitespaces = snapshot
26374 .chars_for_range(range.clone())
26375 .take_while(|c| c.is_whitespace())
26376 .count();
26377
26378 let task_list_prefixes: Vec<_> = language
26379 .task_list()
26380 .into_iter()
26381 .flat_map(|config| {
26382 config
26383 .prefixes
26384 .iter()
26385 .map(|p| p.as_ref())
26386 .collect::<Vec<_>>()
26387 })
26388 .collect();
26389 let unordered_list_markers: Vec<_> = language
26390 .unordered_list()
26391 .iter()
26392 .map(|marker| marker.as_ref())
26393 .collect();
26394 let all_prefixes: Vec<_> = task_list_prefixes
26395 .into_iter()
26396 .chain(unordered_list_markers)
26397 .collect();
26398 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26399 let candidate: String = snapshot
26400 .chars_for_range(range.clone())
26401 .skip(num_of_whitespaces)
26402 .take(max_prefix_len)
26403 .collect();
26404 if all_prefixes
26405 .iter()
26406 .any(|prefix| candidate.starts_with(*prefix))
26407 {
26408 return true;
26409 }
26410 }
26411
26412 let ordered_list_candidate: String = snapshot
26413 .chars_for_range(range)
26414 .skip(num_of_whitespaces)
26415 .take(ORDERED_LIST_MAX_MARKER_LEN)
26416 .collect();
26417 for ordered_config in language.ordered_list() {
26418 let regex = match Regex::new(&ordered_config.pattern) {
26419 Ok(r) => r,
26420 Err(_) => continue,
26421 };
26422 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26423 return captures.get(0).is_some();
26424 }
26425 }
26426
26427 false
26428}
26429
26430#[derive(Debug)]
26431enum NewlineConfig {
26432 /// Insert newline with optional additional indent and optional extra blank line
26433 Newline {
26434 additional_indent: IndentSize,
26435 extra_line_additional_indent: Option<IndentSize>,
26436 prevent_auto_indent: bool,
26437 },
26438 /// Clear the current line
26439 ClearCurrentLine,
26440 /// Unindent the current line and add continuation
26441 UnindentCurrentLine { continuation: Arc<str> },
26442}
26443
26444impl NewlineConfig {
26445 fn has_extra_line(&self) -> bool {
26446 matches!(
26447 self,
26448 Self::Newline {
26449 extra_line_additional_indent: Some(_),
26450 ..
26451 }
26452 )
26453 }
26454
26455 fn insert_extra_newline_brackets(
26456 buffer: &MultiBufferSnapshot,
26457 range: Range<MultiBufferOffset>,
26458 language: &language::LanguageScope,
26459 ) -> bool {
26460 let leading_whitespace_len = buffer
26461 .reversed_chars_at(range.start)
26462 .take_while(|c| c.is_whitespace() && *c != '\n')
26463 .map(|c| c.len_utf8())
26464 .sum::<usize>();
26465 let trailing_whitespace_len = buffer
26466 .chars_at(range.end)
26467 .take_while(|c| c.is_whitespace() && *c != '\n')
26468 .map(|c| c.len_utf8())
26469 .sum::<usize>();
26470 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26471
26472 language.brackets().any(|(pair, enabled)| {
26473 let pair_start = pair.start.trim_end();
26474 let pair_end = pair.end.trim_start();
26475
26476 enabled
26477 && pair.newline
26478 && buffer.contains_str_at(range.end, pair_end)
26479 && buffer.contains_str_at(
26480 range.start.saturating_sub_usize(pair_start.len()),
26481 pair_start,
26482 )
26483 })
26484 }
26485
26486 fn insert_extra_newline_tree_sitter(
26487 buffer: &MultiBufferSnapshot,
26488 range: Range<MultiBufferOffset>,
26489 ) -> bool {
26490 let (buffer, range) = match buffer
26491 .range_to_buffer_ranges(range.start..range.end)
26492 .as_slice()
26493 {
26494 [(buffer_snapshot, range, _)] => (buffer_snapshot.clone(), range.clone()),
26495 _ => return false,
26496 };
26497 let pair = {
26498 let mut result: Option<BracketMatch<usize>> = None;
26499
26500 for pair in buffer
26501 .all_bracket_ranges(range.start.0..range.end.0)
26502 .filter(move |pair| {
26503 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26504 })
26505 {
26506 let len = pair.close_range.end - pair.open_range.start;
26507
26508 if let Some(existing) = &result {
26509 let existing_len = existing.close_range.end - existing.open_range.start;
26510 if len > existing_len {
26511 continue;
26512 }
26513 }
26514
26515 result = Some(pair);
26516 }
26517
26518 result
26519 };
26520 let Some(pair) = pair else {
26521 return false;
26522 };
26523 pair.newline_only
26524 && buffer
26525 .chars_for_range(pair.open_range.end..range.start.0)
26526 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26527 .all(|c| c.is_whitespace() && c != '\n')
26528 }
26529}
26530
26531fn update_uncommitted_diff_for_buffer(
26532 editor: Entity<Editor>,
26533 project: &Entity<Project>,
26534 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26535 buffer: Entity<MultiBuffer>,
26536 cx: &mut App,
26537) -> Task<()> {
26538 let mut tasks = Vec::new();
26539 project.update(cx, |project, cx| {
26540 for buffer in buffers {
26541 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26542 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26543 }
26544 }
26545 });
26546 cx.spawn(async move |cx| {
26547 let diffs = future::join_all(tasks).await;
26548 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26549 return;
26550 }
26551
26552 buffer.update(cx, |buffer, cx| {
26553 for diff in diffs.into_iter().flatten() {
26554 buffer.add_diff(diff, cx);
26555 }
26556 });
26557 })
26558}
26559
26560fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26561 let tab_size = tab_size.get() as usize;
26562 let mut width = offset;
26563
26564 for ch in text.chars() {
26565 width += if ch == '\t' {
26566 tab_size - (width % tab_size)
26567 } else {
26568 1
26569 };
26570 }
26571
26572 width - offset
26573}
26574
26575#[cfg(test)]
26576mod tests {
26577 use super::*;
26578
26579 #[test]
26580 fn test_string_size_with_expanded_tabs() {
26581 let nz = |val| NonZeroU32::new(val).unwrap();
26582 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26583 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26584 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26585 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26586 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26587 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26588 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26589 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26590 }
26591}
26592
26593/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26594struct WordBreakingTokenizer<'a> {
26595 input: &'a str,
26596}
26597
26598impl<'a> WordBreakingTokenizer<'a> {
26599 fn new(input: &'a str) -> Self {
26600 Self { input }
26601 }
26602}
26603
26604fn is_char_ideographic(ch: char) -> bool {
26605 use unicode_script::Script::*;
26606 use unicode_script::UnicodeScript;
26607 matches!(ch.script(), Han | Tangut | Yi)
26608}
26609
26610fn is_grapheme_ideographic(text: &str) -> bool {
26611 text.chars().any(is_char_ideographic)
26612}
26613
26614fn is_grapheme_whitespace(text: &str) -> bool {
26615 text.chars().any(|x| x.is_whitespace())
26616}
26617
26618fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26619 text.chars()
26620 .next()
26621 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26622}
26623
26624#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26625enum WordBreakToken<'a> {
26626 Word { token: &'a str, grapheme_len: usize },
26627 InlineWhitespace { token: &'a str, grapheme_len: usize },
26628 Newline,
26629}
26630
26631impl<'a> Iterator for WordBreakingTokenizer<'a> {
26632 /// Yields a span, the count of graphemes in the token, and whether it was
26633 /// whitespace. Note that it also breaks at word boundaries.
26634 type Item = WordBreakToken<'a>;
26635
26636 fn next(&mut self) -> Option<Self::Item> {
26637 use unicode_segmentation::UnicodeSegmentation;
26638 if self.input.is_empty() {
26639 return None;
26640 }
26641
26642 let mut iter = self.input.graphemes(true).peekable();
26643 let mut offset = 0;
26644 let mut grapheme_len = 0;
26645 if let Some(first_grapheme) = iter.next() {
26646 let is_newline = first_grapheme == "\n";
26647 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26648 offset += first_grapheme.len();
26649 grapheme_len += 1;
26650 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26651 if let Some(grapheme) = iter.peek().copied()
26652 && should_stay_with_preceding_ideograph(grapheme)
26653 {
26654 offset += grapheme.len();
26655 grapheme_len += 1;
26656 }
26657 } else {
26658 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26659 let mut next_word_bound = words.peek().copied();
26660 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26661 next_word_bound = words.next();
26662 }
26663 while let Some(grapheme) = iter.peek().copied() {
26664 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26665 break;
26666 };
26667 if is_grapheme_whitespace(grapheme) != is_whitespace
26668 || (grapheme == "\n") != is_newline
26669 {
26670 break;
26671 };
26672 offset += grapheme.len();
26673 grapheme_len += 1;
26674 iter.next();
26675 }
26676 }
26677 let token = &self.input[..offset];
26678 self.input = &self.input[offset..];
26679 if token == "\n" {
26680 Some(WordBreakToken::Newline)
26681 } else if is_whitespace {
26682 Some(WordBreakToken::InlineWhitespace {
26683 token,
26684 grapheme_len,
26685 })
26686 } else {
26687 Some(WordBreakToken::Word {
26688 token,
26689 grapheme_len,
26690 })
26691 }
26692 } else {
26693 None
26694 }
26695 }
26696}
26697
26698#[test]
26699fn test_word_breaking_tokenizer() {
26700 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26701 ("", &[]),
26702 (" ", &[whitespace(" ", 2)]),
26703 ("Ʒ", &[word("Ʒ", 1)]),
26704 ("Ǽ", &[word("Ǽ", 1)]),
26705 ("⋑", &[word("⋑", 1)]),
26706 ("⋑⋑", &[word("⋑⋑", 2)]),
26707 (
26708 "原理,进而",
26709 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26710 ),
26711 (
26712 "hello world",
26713 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26714 ),
26715 (
26716 "hello, world",
26717 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26718 ),
26719 (
26720 " hello world",
26721 &[
26722 whitespace(" ", 2),
26723 word("hello", 5),
26724 whitespace(" ", 1),
26725 word("world", 5),
26726 ],
26727 ),
26728 (
26729 "这是什么 \n 钢笔",
26730 &[
26731 word("这", 1),
26732 word("是", 1),
26733 word("什", 1),
26734 word("么", 1),
26735 whitespace(" ", 1),
26736 newline(),
26737 whitespace(" ", 1),
26738 word("钢", 1),
26739 word("笔", 1),
26740 ],
26741 ),
26742 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26743 ];
26744
26745 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26746 WordBreakToken::Word {
26747 token,
26748 grapheme_len,
26749 }
26750 }
26751
26752 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26753 WordBreakToken::InlineWhitespace {
26754 token,
26755 grapheme_len,
26756 }
26757 }
26758
26759 fn newline() -> WordBreakToken<'static> {
26760 WordBreakToken::Newline
26761 }
26762
26763 for (input, result) in tests {
26764 assert_eq!(
26765 WordBreakingTokenizer::new(input)
26766 .collect::<Vec<_>>()
26767 .as_slice(),
26768 *result,
26769 );
26770 }
26771}
26772
26773fn wrap_with_prefix(
26774 first_line_prefix: String,
26775 subsequent_lines_prefix: String,
26776 unwrapped_text: String,
26777 wrap_column: usize,
26778 tab_size: NonZeroU32,
26779 preserve_existing_whitespace: bool,
26780) -> String {
26781 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26782 let subsequent_lines_prefix_len =
26783 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26784 let mut wrapped_text = String::new();
26785 let mut current_line = first_line_prefix;
26786 let mut is_first_line = true;
26787
26788 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26789 let mut current_line_len = first_line_prefix_len;
26790 let mut in_whitespace = false;
26791 for token in tokenizer {
26792 let have_preceding_whitespace = in_whitespace;
26793 match token {
26794 WordBreakToken::Word {
26795 token,
26796 grapheme_len,
26797 } => {
26798 in_whitespace = false;
26799 let current_prefix_len = if is_first_line {
26800 first_line_prefix_len
26801 } else {
26802 subsequent_lines_prefix_len
26803 };
26804 if current_line_len + grapheme_len > wrap_column
26805 && current_line_len != current_prefix_len
26806 {
26807 wrapped_text.push_str(current_line.trim_end());
26808 wrapped_text.push('\n');
26809 is_first_line = false;
26810 current_line = subsequent_lines_prefix.clone();
26811 current_line_len = subsequent_lines_prefix_len;
26812 }
26813 current_line.push_str(token);
26814 current_line_len += grapheme_len;
26815 }
26816 WordBreakToken::InlineWhitespace {
26817 mut token,
26818 mut grapheme_len,
26819 } => {
26820 in_whitespace = true;
26821 if have_preceding_whitespace && !preserve_existing_whitespace {
26822 continue;
26823 }
26824 if !preserve_existing_whitespace {
26825 // Keep a single whitespace grapheme as-is
26826 if let Some(first) =
26827 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26828 {
26829 token = first;
26830 } else {
26831 token = " ";
26832 }
26833 grapheme_len = 1;
26834 }
26835 let current_prefix_len = if is_first_line {
26836 first_line_prefix_len
26837 } else {
26838 subsequent_lines_prefix_len
26839 };
26840 if current_line_len + grapheme_len > wrap_column {
26841 wrapped_text.push_str(current_line.trim_end());
26842 wrapped_text.push('\n');
26843 is_first_line = false;
26844 current_line = subsequent_lines_prefix.clone();
26845 current_line_len = subsequent_lines_prefix_len;
26846 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26847 current_line.push_str(token);
26848 current_line_len += grapheme_len;
26849 }
26850 }
26851 WordBreakToken::Newline => {
26852 in_whitespace = true;
26853 let current_prefix_len = if is_first_line {
26854 first_line_prefix_len
26855 } else {
26856 subsequent_lines_prefix_len
26857 };
26858 if preserve_existing_whitespace {
26859 wrapped_text.push_str(current_line.trim_end());
26860 wrapped_text.push('\n');
26861 is_first_line = false;
26862 current_line = subsequent_lines_prefix.clone();
26863 current_line_len = subsequent_lines_prefix_len;
26864 } else if have_preceding_whitespace {
26865 continue;
26866 } else if current_line_len + 1 > wrap_column
26867 && current_line_len != current_prefix_len
26868 {
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 {
26875 current_line.push(' ');
26876 current_line_len += 1;
26877 }
26878 }
26879 }
26880 }
26881
26882 if !current_line.is_empty() {
26883 wrapped_text.push_str(¤t_line);
26884 }
26885 wrapped_text
26886}
26887
26888#[test]
26889fn test_wrap_with_prefix() {
26890 assert_eq!(
26891 wrap_with_prefix(
26892 "# ".to_string(),
26893 "# ".to_string(),
26894 "abcdefg".to_string(),
26895 4,
26896 NonZeroU32::new(4).unwrap(),
26897 false,
26898 ),
26899 "# abcdefg"
26900 );
26901 assert_eq!(
26902 wrap_with_prefix(
26903 "".to_string(),
26904 "".to_string(),
26905 "\thello world".to_string(),
26906 8,
26907 NonZeroU32::new(4).unwrap(),
26908 false,
26909 ),
26910 "hello\nworld"
26911 );
26912 assert_eq!(
26913 wrap_with_prefix(
26914 "// ".to_string(),
26915 "// ".to_string(),
26916 "xx \nyy zz aa bb cc".to_string(),
26917 12,
26918 NonZeroU32::new(4).unwrap(),
26919 false,
26920 ),
26921 "// xx yy zz\n// aa bb cc"
26922 );
26923 assert_eq!(
26924 wrap_with_prefix(
26925 String::new(),
26926 String::new(),
26927 "这是什么 \n 钢笔".to_string(),
26928 3,
26929 NonZeroU32::new(4).unwrap(),
26930 false,
26931 ),
26932 "这是什\n么 钢\n笔"
26933 );
26934 assert_eq!(
26935 wrap_with_prefix(
26936 String::new(),
26937 String::new(),
26938 format!("foo{}bar", '\u{2009}'), // thin space
26939 80,
26940 NonZeroU32::new(4).unwrap(),
26941 false,
26942 ),
26943 format!("foo{}bar", '\u{2009}')
26944 );
26945}
26946
26947pub trait CollaborationHub {
26948 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26949 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26950 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26951}
26952
26953impl CollaborationHub for Entity<Project> {
26954 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26955 self.read(cx).collaborators()
26956 }
26957
26958 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26959 self.read(cx).user_store().read(cx).participant_indices()
26960 }
26961
26962 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26963 let this = self.read(cx);
26964 let user_ids = this.collaborators().values().map(|c| c.user_id);
26965 this.user_store().read(cx).participant_names(user_ids, cx)
26966 }
26967}
26968
26969pub trait SemanticsProvider {
26970 fn hover(
26971 &self,
26972 buffer: &Entity<Buffer>,
26973 position: text::Anchor,
26974 cx: &mut App,
26975 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26976
26977 fn inline_values(
26978 &self,
26979 buffer_handle: Entity<Buffer>,
26980 range: Range<text::Anchor>,
26981 cx: &mut App,
26982 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26983
26984 fn applicable_inlay_chunks(
26985 &self,
26986 buffer: &Entity<Buffer>,
26987 ranges: &[Range<text::Anchor>],
26988 cx: &mut App,
26989 ) -> Vec<Range<BufferRow>>;
26990
26991 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26992
26993 fn inlay_hints(
26994 &self,
26995 invalidate: InvalidationStrategy,
26996 buffer: Entity<Buffer>,
26997 ranges: Vec<Range<text::Anchor>>,
26998 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26999 cx: &mut App,
27000 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
27001
27002 fn semantic_tokens(
27003 &self,
27004 buffer: Entity<Buffer>,
27005 refresh: Option<RefreshForServer>,
27006 cx: &mut App,
27007 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
27008
27009 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27010
27011 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27012
27013 fn document_highlights(
27014 &self,
27015 buffer: &Entity<Buffer>,
27016 position: text::Anchor,
27017 cx: &mut App,
27018 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
27019
27020 fn definitions(
27021 &self,
27022 buffer: &Entity<Buffer>,
27023 position: text::Anchor,
27024 kind: GotoDefinitionKind,
27025 cx: &mut App,
27026 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
27027
27028 fn range_for_rename(
27029 &self,
27030 buffer: &Entity<Buffer>,
27031 position: text::Anchor,
27032 cx: &mut App,
27033 ) -> Task<Result<Option<Range<text::Anchor>>>>;
27034
27035 fn perform_rename(
27036 &self,
27037 buffer: &Entity<Buffer>,
27038 position: text::Anchor,
27039 new_name: String,
27040 cx: &mut App,
27041 ) -> Option<Task<Result<ProjectTransaction>>>;
27042}
27043
27044pub trait CompletionProvider {
27045 fn completions(
27046 &self,
27047 buffer: &Entity<Buffer>,
27048 buffer_position: text::Anchor,
27049 trigger: CompletionContext,
27050 window: &mut Window,
27051 cx: &mut Context<Editor>,
27052 ) -> Task<Result<Vec<CompletionResponse>>>;
27053
27054 fn resolve_completions(
27055 &self,
27056 _buffer: Entity<Buffer>,
27057 _completion_indices: Vec<usize>,
27058 _completions: Rc<RefCell<Box<[Completion]>>>,
27059 _cx: &mut Context<Editor>,
27060 ) -> Task<Result<bool>> {
27061 Task::ready(Ok(false))
27062 }
27063
27064 fn apply_additional_edits_for_completion(
27065 &self,
27066 _buffer: Entity<Buffer>,
27067 _completions: Rc<RefCell<Box<[Completion]>>>,
27068 _completion_index: usize,
27069 _push_to_history: bool,
27070 _all_commit_ranges: Vec<Range<language::Anchor>>,
27071 _cx: &mut Context<Editor>,
27072 ) -> Task<Result<Option<language::Transaction>>> {
27073 Task::ready(Ok(None))
27074 }
27075
27076 fn is_completion_trigger(
27077 &self,
27078 buffer: &Entity<Buffer>,
27079 position: language::Anchor,
27080 text: &str,
27081 trigger_in_words: bool,
27082 cx: &mut Context<Editor>,
27083 ) -> bool;
27084
27085 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
27086
27087 fn sort_completions(&self) -> bool {
27088 true
27089 }
27090
27091 fn filter_completions(&self) -> bool {
27092 true
27093 }
27094
27095 fn show_snippets(&self) -> bool {
27096 false
27097 }
27098}
27099
27100pub trait CodeActionProvider {
27101 fn id(&self) -> Arc<str>;
27102
27103 fn code_actions(
27104 &self,
27105 buffer: &Entity<Buffer>,
27106 range: Range<text::Anchor>,
27107 window: &mut Window,
27108 cx: &mut App,
27109 ) -> Task<Result<Vec<CodeAction>>>;
27110
27111 fn apply_code_action(
27112 &self,
27113 buffer_handle: Entity<Buffer>,
27114 action: CodeAction,
27115 push_to_history: bool,
27116 window: &mut Window,
27117 cx: &mut App,
27118 ) -> Task<Result<ProjectTransaction>>;
27119}
27120
27121impl CodeActionProvider for Entity<Project> {
27122 fn id(&self) -> Arc<str> {
27123 "project".into()
27124 }
27125
27126 fn code_actions(
27127 &self,
27128 buffer: &Entity<Buffer>,
27129 range: Range<text::Anchor>,
27130 _window: &mut Window,
27131 cx: &mut App,
27132 ) -> Task<Result<Vec<CodeAction>>> {
27133 self.update(cx, |project, cx| {
27134 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27135 let code_actions = project.code_actions(buffer, range, None, cx);
27136 cx.background_spawn(async move {
27137 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27138 Ok(code_lens_actions
27139 .context("code lens fetch")?
27140 .into_iter()
27141 .flatten()
27142 .chain(
27143 code_actions
27144 .context("code action fetch")?
27145 .into_iter()
27146 .flatten(),
27147 )
27148 .collect())
27149 })
27150 })
27151 }
27152
27153 fn apply_code_action(
27154 &self,
27155 buffer_handle: Entity<Buffer>,
27156 action: CodeAction,
27157 push_to_history: bool,
27158 _window: &mut Window,
27159 cx: &mut App,
27160 ) -> Task<Result<ProjectTransaction>> {
27161 self.update(cx, |project, cx| {
27162 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27163 })
27164 }
27165}
27166
27167fn snippet_completions(
27168 project: &Project,
27169 buffer: &Entity<Buffer>,
27170 buffer_anchor: text::Anchor,
27171 classifier: CharClassifier,
27172 cx: &mut App,
27173) -> Task<Result<CompletionResponse>> {
27174 let languages = buffer.read(cx).languages_at(buffer_anchor);
27175 let snippet_store = project.snippets().read(cx);
27176
27177 let scopes: Vec<_> = languages
27178 .iter()
27179 .filter_map(|language| {
27180 let language_name = language.lsp_id();
27181 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27182
27183 if snippets.is_empty() {
27184 None
27185 } else {
27186 Some((language.default_scope(), snippets))
27187 }
27188 })
27189 .collect();
27190
27191 if scopes.is_empty() {
27192 return Task::ready(Ok(CompletionResponse {
27193 completions: vec![],
27194 display_options: CompletionDisplayOptions::default(),
27195 is_incomplete: false,
27196 }));
27197 }
27198
27199 let snapshot = buffer.read(cx).text_snapshot();
27200 let executor = cx.background_executor().clone();
27201
27202 cx.background_spawn(async move {
27203 let is_word_char = |c| classifier.is_word(c);
27204
27205 let mut is_incomplete = false;
27206 let mut completions: Vec<Completion> = Vec::new();
27207
27208 const MAX_PREFIX_LEN: usize = 128;
27209 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27210 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27211 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27212
27213 let max_buffer_window: String = snapshot
27214 .text_for_range(window_start..buffer_offset)
27215 .collect();
27216
27217 if max_buffer_window.is_empty() {
27218 return Ok(CompletionResponse {
27219 completions: vec![],
27220 display_options: CompletionDisplayOptions::default(),
27221 is_incomplete: true,
27222 });
27223 }
27224
27225 for (_scope, snippets) in scopes.into_iter() {
27226 // Sort snippets by word count to match longer snippet prefixes first.
27227 let mut sorted_snippet_candidates = snippets
27228 .iter()
27229 .enumerate()
27230 .flat_map(|(snippet_ix, snippet)| {
27231 snippet
27232 .prefix
27233 .iter()
27234 .enumerate()
27235 .map(move |(prefix_ix, prefix)| {
27236 let word_count =
27237 snippet_candidate_suffixes(prefix, &is_word_char).count();
27238 ((snippet_ix, prefix_ix), prefix, word_count)
27239 })
27240 })
27241 .collect_vec();
27242 sorted_snippet_candidates
27243 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27244
27245 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27246
27247 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27248 .take(
27249 sorted_snippet_candidates
27250 .first()
27251 .map(|(_, _, word_count)| *word_count)
27252 .unwrap_or_default(),
27253 )
27254 .collect_vec();
27255
27256 const MAX_RESULTS: usize = 100;
27257 // Each match also remembers how many characters from the buffer it consumed
27258 let mut matches: Vec<(StringMatch, usize)> = vec![];
27259
27260 let mut snippet_list_cutoff_index = 0;
27261 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27262 let word_count = buffer_index + 1;
27263 // Increase `snippet_list_cutoff_index` until we have all of the
27264 // snippets with sufficiently many words.
27265 while sorted_snippet_candidates
27266 .get(snippet_list_cutoff_index)
27267 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27268 *snippet_word_count >= word_count
27269 })
27270 {
27271 snippet_list_cutoff_index += 1;
27272 }
27273
27274 // Take only the candidates with at least `word_count` many words
27275 let snippet_candidates_at_word_len =
27276 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27277
27278 let candidates = snippet_candidates_at_word_len
27279 .iter()
27280 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27281 .enumerate() // index in `sorted_snippet_candidates`
27282 // First char must match
27283 .filter(|(_ix, prefix)| {
27284 itertools::equal(
27285 prefix
27286 .chars()
27287 .next()
27288 .into_iter()
27289 .flat_map(|c| c.to_lowercase()),
27290 buffer_window
27291 .chars()
27292 .next()
27293 .into_iter()
27294 .flat_map(|c| c.to_lowercase()),
27295 )
27296 })
27297 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27298 .collect::<Vec<StringMatchCandidate>>();
27299
27300 matches.extend(
27301 fuzzy::match_strings(
27302 &candidates,
27303 &buffer_window,
27304 buffer_window.chars().any(|c| c.is_uppercase()),
27305 true,
27306 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27307 &Default::default(),
27308 executor.clone(),
27309 )
27310 .await
27311 .into_iter()
27312 .map(|string_match| (string_match, buffer_window.len())),
27313 );
27314
27315 if matches.len() >= MAX_RESULTS {
27316 break;
27317 }
27318 }
27319
27320 let to_lsp = |point: &text::Anchor| {
27321 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27322 point_to_lsp(end)
27323 };
27324 let lsp_end = to_lsp(&buffer_anchor);
27325
27326 if matches.len() >= MAX_RESULTS {
27327 is_incomplete = true;
27328 }
27329
27330 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27331 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27332 sorted_snippet_candidates[string_match.candidate_id];
27333 let snippet = &snippets[snippet_index];
27334 let start = buffer_offset - buffer_window_len;
27335 let start = snapshot.anchor_before(start);
27336 let range = start..buffer_anchor;
27337 let lsp_start = to_lsp(&start);
27338 let lsp_range = lsp::Range {
27339 start: lsp_start,
27340 end: lsp_end,
27341 };
27342 Completion {
27343 replace_range: range,
27344 new_text: snippet.body.clone(),
27345 source: CompletionSource::Lsp {
27346 insert_range: None,
27347 server_id: LanguageServerId(usize::MAX),
27348 resolved: true,
27349 lsp_completion: Box::new(lsp::CompletionItem {
27350 label: snippet.prefix.first().unwrap().clone(),
27351 kind: Some(CompletionItemKind::SNIPPET),
27352 label_details: snippet.description.as_ref().map(|description| {
27353 lsp::CompletionItemLabelDetails {
27354 detail: Some(description.clone()),
27355 description: None,
27356 }
27357 }),
27358 insert_text_format: Some(InsertTextFormat::SNIPPET),
27359 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27360 lsp::InsertReplaceEdit {
27361 new_text: snippet.body.clone(),
27362 insert: lsp_range,
27363 replace: lsp_range,
27364 },
27365 )),
27366 filter_text: Some(snippet.body.clone()),
27367 sort_text: Some(char::MAX.to_string()),
27368 ..lsp::CompletionItem::default()
27369 }),
27370 lsp_defaults: None,
27371 },
27372 label: CodeLabel {
27373 text: matching_prefix.clone(),
27374 runs: Vec::new(),
27375 filter_range: 0..matching_prefix.len(),
27376 },
27377 icon_path: None,
27378 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27379 single_line: snippet.name.clone().into(),
27380 plain_text: snippet
27381 .description
27382 .clone()
27383 .map(|description| description.into()),
27384 }),
27385 insert_text_mode: None,
27386 confirm: None,
27387 match_start: Some(start),
27388 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27389 }
27390 }));
27391 }
27392
27393 Ok(CompletionResponse {
27394 completions,
27395 display_options: CompletionDisplayOptions::default(),
27396 is_incomplete,
27397 })
27398 })
27399}
27400
27401impl CompletionProvider for Entity<Project> {
27402 fn completions(
27403 &self,
27404 buffer: &Entity<Buffer>,
27405 buffer_position: text::Anchor,
27406 options: CompletionContext,
27407 _window: &mut Window,
27408 cx: &mut Context<Editor>,
27409 ) -> Task<Result<Vec<CompletionResponse>>> {
27410 self.update(cx, |project, cx| {
27411 let task = project.completions(buffer, buffer_position, options, cx);
27412 cx.background_spawn(task)
27413 })
27414 }
27415
27416 fn resolve_completions(
27417 &self,
27418 buffer: Entity<Buffer>,
27419 completion_indices: Vec<usize>,
27420 completions: Rc<RefCell<Box<[Completion]>>>,
27421 cx: &mut Context<Editor>,
27422 ) -> Task<Result<bool>> {
27423 self.update(cx, |project, cx| {
27424 project.lsp_store().update(cx, |lsp_store, cx| {
27425 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27426 })
27427 })
27428 }
27429
27430 fn apply_additional_edits_for_completion(
27431 &self,
27432 buffer: Entity<Buffer>,
27433 completions: Rc<RefCell<Box<[Completion]>>>,
27434 completion_index: usize,
27435 push_to_history: bool,
27436 all_commit_ranges: Vec<Range<language::Anchor>>,
27437 cx: &mut Context<Editor>,
27438 ) -> Task<Result<Option<language::Transaction>>> {
27439 self.update(cx, |project, cx| {
27440 project.lsp_store().update(cx, |lsp_store, cx| {
27441 lsp_store.apply_additional_edits_for_completion(
27442 buffer,
27443 completions,
27444 completion_index,
27445 push_to_history,
27446 all_commit_ranges,
27447 cx,
27448 )
27449 })
27450 })
27451 }
27452
27453 fn is_completion_trigger(
27454 &self,
27455 buffer: &Entity<Buffer>,
27456 position: language::Anchor,
27457 text: &str,
27458 trigger_in_words: bool,
27459 cx: &mut Context<Editor>,
27460 ) -> bool {
27461 let mut chars = text.chars();
27462 let char = if let Some(char) = chars.next() {
27463 char
27464 } else {
27465 return false;
27466 };
27467 if chars.next().is_some() {
27468 return false;
27469 }
27470
27471 let buffer = buffer.read(cx);
27472 let snapshot = buffer.snapshot();
27473 let classifier = snapshot
27474 .char_classifier_at(position)
27475 .scope_context(Some(CharScopeContext::Completion));
27476 if trigger_in_words && classifier.is_word(char) {
27477 return true;
27478 }
27479
27480 buffer.completion_triggers().contains(text)
27481 }
27482
27483 fn show_snippets(&self) -> bool {
27484 true
27485 }
27486}
27487
27488impl SemanticsProvider for WeakEntity<Project> {
27489 fn hover(
27490 &self,
27491 buffer: &Entity<Buffer>,
27492 position: text::Anchor,
27493 cx: &mut App,
27494 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27495 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27496 .ok()
27497 }
27498
27499 fn document_highlights(
27500 &self,
27501 buffer: &Entity<Buffer>,
27502 position: text::Anchor,
27503 cx: &mut App,
27504 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27505 self.update(cx, |project, cx| {
27506 project.document_highlights(buffer, position, cx)
27507 })
27508 .ok()
27509 }
27510
27511 fn definitions(
27512 &self,
27513 buffer: &Entity<Buffer>,
27514 position: text::Anchor,
27515 kind: GotoDefinitionKind,
27516 cx: &mut App,
27517 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27518 self.update(cx, |project, cx| match kind {
27519 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27520 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27521 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27522 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27523 })
27524 .ok()
27525 }
27526
27527 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27528 self.update(cx, |project, cx| {
27529 if project
27530 .active_debug_session(cx)
27531 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27532 {
27533 return true;
27534 }
27535
27536 buffer.update(cx, |buffer, cx| {
27537 project.any_language_server_supports_inlay_hints(buffer, cx)
27538 })
27539 })
27540 .unwrap_or(false)
27541 }
27542
27543 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27544 self.update(cx, |project, cx| {
27545 buffer.update(cx, |buffer, cx| {
27546 project.any_language_server_supports_semantic_tokens(buffer, cx)
27547 })
27548 })
27549 .unwrap_or(false)
27550 }
27551
27552 fn inline_values(
27553 &self,
27554 buffer_handle: Entity<Buffer>,
27555 range: Range<text::Anchor>,
27556 cx: &mut App,
27557 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27558 self.update(cx, |project, cx| {
27559 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27560
27561 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27562 })
27563 .ok()
27564 .flatten()
27565 }
27566
27567 fn applicable_inlay_chunks(
27568 &self,
27569 buffer: &Entity<Buffer>,
27570 ranges: &[Range<text::Anchor>],
27571 cx: &mut App,
27572 ) -> Vec<Range<BufferRow>> {
27573 self.update(cx, |project, cx| {
27574 project.lsp_store().update(cx, |lsp_store, cx| {
27575 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27576 })
27577 })
27578 .unwrap_or_default()
27579 }
27580
27581 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27582 self.update(cx, |project, cx| {
27583 project.lsp_store().update(cx, |lsp_store, _| {
27584 lsp_store.invalidate_inlay_hints(for_buffers)
27585 })
27586 })
27587 .ok();
27588 }
27589
27590 fn inlay_hints(
27591 &self,
27592 invalidate: InvalidationStrategy,
27593 buffer: Entity<Buffer>,
27594 ranges: Vec<Range<text::Anchor>>,
27595 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27596 cx: &mut App,
27597 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27598 self.update(cx, |project, cx| {
27599 project.lsp_store().update(cx, |lsp_store, cx| {
27600 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27601 })
27602 })
27603 .ok()
27604 }
27605
27606 fn semantic_tokens(
27607 &self,
27608 buffer: Entity<Buffer>,
27609 refresh: Option<RefreshForServer>,
27610 cx: &mut App,
27611 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27612 self.update(cx, |this, cx| {
27613 this.lsp_store().update(cx, |lsp_store, cx| {
27614 lsp_store.semantic_tokens(buffer, refresh, cx)
27615 })
27616 })
27617 .ok()
27618 }
27619
27620 fn range_for_rename(
27621 &self,
27622 buffer: &Entity<Buffer>,
27623 position: text::Anchor,
27624 cx: &mut App,
27625 ) -> Task<Result<Option<Range<text::Anchor>>>> {
27626 let Some(this) = self.upgrade() else {
27627 return Task::ready(Ok(None));
27628 };
27629
27630 this.update(cx, |project, cx| {
27631 let buffer = buffer.clone();
27632 let task = project.prepare_rename(buffer.clone(), position, cx);
27633 cx.spawn(async move |_, cx| {
27634 Ok(match task.await? {
27635 PrepareRenameResponse::Success(range) => Some(range),
27636 PrepareRenameResponse::InvalidPosition => None,
27637 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27638 // Fallback on using TreeSitter info to determine identifier range
27639 buffer.read_with(cx, |buffer, _| {
27640 let snapshot = buffer.snapshot();
27641 let (range, kind) = snapshot.surrounding_word(position, None);
27642 if kind != Some(CharKind::Word) {
27643 return None;
27644 }
27645 Some(
27646 snapshot.anchor_before(range.start)
27647 ..snapshot.anchor_after(range.end),
27648 )
27649 })
27650 }
27651 })
27652 })
27653 })
27654 }
27655
27656 fn perform_rename(
27657 &self,
27658 buffer: &Entity<Buffer>,
27659 position: text::Anchor,
27660 new_name: String,
27661 cx: &mut App,
27662 ) -> Option<Task<Result<ProjectTransaction>>> {
27663 self.update(cx, |project, cx| {
27664 project.perform_rename(buffer.clone(), position, new_name, cx)
27665 })
27666 .ok()
27667 }
27668}
27669
27670fn consume_contiguous_rows(
27671 contiguous_row_selections: &mut Vec<Selection<Point>>,
27672 selection: &Selection<Point>,
27673 display_map: &DisplaySnapshot,
27674 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27675) -> (MultiBufferRow, MultiBufferRow) {
27676 contiguous_row_selections.push(selection.clone());
27677 let start_row = starting_row(selection, display_map);
27678 let mut end_row = ending_row(selection, display_map);
27679
27680 while let Some(next_selection) = selections.peek() {
27681 if next_selection.start.row <= end_row.0 {
27682 end_row = ending_row(next_selection, display_map);
27683 contiguous_row_selections.push(selections.next().unwrap().clone());
27684 } else {
27685 break;
27686 }
27687 }
27688 (start_row, end_row)
27689}
27690
27691fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27692 if selection.start.column > 0 {
27693 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27694 } else {
27695 MultiBufferRow(selection.start.row)
27696 }
27697}
27698
27699fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27700 if next_selection.end.column > 0 || next_selection.is_empty() {
27701 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27702 } else {
27703 MultiBufferRow(next_selection.end.row)
27704 }
27705}
27706
27707impl EditorSnapshot {
27708 pub fn remote_selections_in_range<'a>(
27709 &'a self,
27710 range: &'a Range<Anchor>,
27711 collaboration_hub: &dyn CollaborationHub,
27712 cx: &'a App,
27713 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27714 let participant_names = collaboration_hub.user_names(cx);
27715 let participant_indices = collaboration_hub.user_participant_indices(cx);
27716 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27717 let collaborators_by_replica_id = collaborators_by_peer_id
27718 .values()
27719 .map(|collaborator| (collaborator.replica_id, collaborator))
27720 .collect::<HashMap<_, _>>();
27721 self.buffer_snapshot()
27722 .selections_in_range(range, false)
27723 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27724 if replica_id == ReplicaId::AGENT {
27725 Some(RemoteSelection {
27726 replica_id,
27727 selection,
27728 cursor_shape,
27729 line_mode,
27730 collaborator_id: CollaboratorId::Agent,
27731 user_name: Some("Agent".into()),
27732 color: cx.theme().players().agent(),
27733 })
27734 } else {
27735 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27736 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27737 let user_name = participant_names.get(&collaborator.user_id).cloned();
27738 Some(RemoteSelection {
27739 replica_id,
27740 selection,
27741 cursor_shape,
27742 line_mode,
27743 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27744 user_name,
27745 color: if let Some(index) = participant_index {
27746 cx.theme().players().color_for_participant(index.0)
27747 } else {
27748 cx.theme().players().absent()
27749 },
27750 })
27751 }
27752 })
27753 }
27754
27755 pub fn hunks_for_ranges(
27756 &self,
27757 ranges: impl IntoIterator<Item = Range<Point>>,
27758 ) -> Vec<MultiBufferDiffHunk> {
27759 let mut hunks = Vec::new();
27760 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27761 HashMap::default();
27762 for query_range in ranges {
27763 let query_rows =
27764 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27765 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27766 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27767 ) {
27768 // Include deleted hunks that are adjacent to the query range, because
27769 // otherwise they would be missed.
27770 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27771 if hunk.status().is_deleted() {
27772 intersects_range |= hunk.row_range.start == query_rows.end;
27773 intersects_range |= hunk.row_range.end == query_rows.start;
27774 }
27775 if intersects_range {
27776 if !processed_buffer_rows
27777 .entry(hunk.buffer_id)
27778 .or_default()
27779 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27780 {
27781 continue;
27782 }
27783 hunks.push(hunk);
27784 }
27785 }
27786 }
27787
27788 hunks
27789 }
27790
27791 fn display_diff_hunks_for_rows<'a>(
27792 &'a self,
27793 display_rows: Range<DisplayRow>,
27794 folded_buffers: &'a HashSet<BufferId>,
27795 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27796 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27797 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27798
27799 self.buffer_snapshot()
27800 .diff_hunks_in_range(buffer_start..buffer_end)
27801 .filter_map(|hunk| {
27802 if folded_buffers.contains(&hunk.buffer_id)
27803 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27804 {
27805 return None;
27806 }
27807
27808 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27809 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27810 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27811 let line_len = self.buffer_snapshot().line_len(last_row);
27812 Point::new(last_row.0, line_len)
27813 } else {
27814 Point::new(hunk.row_range.end.0, 0)
27815 };
27816
27817 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27818 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27819
27820 let display_hunk = if hunk_display_start.column() != 0 {
27821 DisplayDiffHunk::Folded {
27822 display_row: hunk_display_start.row(),
27823 }
27824 } else {
27825 let mut end_row = hunk_display_end.row();
27826 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27827 end_row.0 += 1;
27828 }
27829 let is_created_file = hunk.is_created_file();
27830 let multi_buffer_range = hunk.multi_buffer_range.clone();
27831
27832 DisplayDiffHunk::Unfolded {
27833 status: hunk.status(),
27834 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27835 ..hunk.diff_base_byte_range.end.0,
27836 word_diffs: hunk.word_diffs,
27837 display_row_range: hunk_display_start.row()..end_row,
27838 multi_buffer_range,
27839 is_created_file,
27840 }
27841 };
27842
27843 Some(display_hunk)
27844 })
27845 }
27846
27847 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27848 self.display_snapshot
27849 .buffer_snapshot()
27850 .language_at(position)
27851 }
27852
27853 pub fn is_focused(&self) -> bool {
27854 self.is_focused
27855 }
27856
27857 pub fn placeholder_text(&self) -> Option<String> {
27858 self.placeholder_display_snapshot
27859 .as_ref()
27860 .map(|display_map| display_map.text())
27861 }
27862
27863 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27864 self.scroll_anchor.scroll_position(&self.display_snapshot)
27865 }
27866
27867 pub fn gutter_dimensions(
27868 &self,
27869 font_id: FontId,
27870 font_size: Pixels,
27871 style: &EditorStyle,
27872 window: &mut Window,
27873 cx: &App,
27874 ) -> GutterDimensions {
27875 if self.show_gutter
27876 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27877 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27878 {
27879 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27880 matches!(
27881 ProjectSettings::get_global(cx).git.git_gutter,
27882 GitGutterSetting::TrackedFiles
27883 )
27884 });
27885 let gutter_settings = EditorSettings::get_global(cx).gutter;
27886 let show_line_numbers = self
27887 .show_line_numbers
27888 .unwrap_or(gutter_settings.line_numbers);
27889 let line_gutter_width = if show_line_numbers {
27890 // Avoid flicker-like gutter resizes when the line number gains another digit by
27891 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27892 let min_width_for_number_on_gutter =
27893 ch_advance * gutter_settings.min_line_number_digits as f32;
27894 self.max_line_number_width(style, window)
27895 .max(min_width_for_number_on_gutter)
27896 } else {
27897 0.0.into()
27898 };
27899
27900 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27901 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27902
27903 let git_blame_entries_width =
27904 self.git_blame_gutter_max_author_length
27905 .map(|max_author_length| {
27906 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27907 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27908
27909 /// The number of characters to dedicate to gaps and margins.
27910 const SPACING_WIDTH: usize = 4;
27911
27912 let max_char_count = max_author_length.min(renderer.max_author_length())
27913 + ::git::SHORT_SHA_LENGTH
27914 + MAX_RELATIVE_TIMESTAMP.len()
27915 + SPACING_WIDTH;
27916
27917 ch_advance * max_char_count
27918 });
27919
27920 let is_singleton = self.buffer_snapshot().is_singleton();
27921
27922 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27923 left_padding += if !is_singleton {
27924 ch_width * 4.0
27925 } else if show_runnables || show_breakpoints {
27926 ch_width * 3.0
27927 } else if show_git_gutter && show_line_numbers {
27928 ch_width * 2.0
27929 } else if show_git_gutter || show_line_numbers {
27930 ch_width
27931 } else {
27932 px(0.)
27933 };
27934
27935 let shows_folds = is_singleton && gutter_settings.folds;
27936
27937 let right_padding = if shows_folds && show_line_numbers {
27938 ch_width * 4.0
27939 } else if shows_folds || (!is_singleton && show_line_numbers) {
27940 ch_width * 3.0
27941 } else if show_line_numbers {
27942 ch_width
27943 } else {
27944 px(0.)
27945 };
27946
27947 GutterDimensions {
27948 left_padding,
27949 right_padding,
27950 width: line_gutter_width + left_padding + right_padding,
27951 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27952 git_blame_entries_width,
27953 }
27954 } else if self.offset_content {
27955 GutterDimensions::default_with_margin(font_id, font_size, cx)
27956 } else {
27957 GutterDimensions::default()
27958 }
27959 }
27960
27961 pub fn render_crease_toggle(
27962 &self,
27963 buffer_row: MultiBufferRow,
27964 row_contains_cursor: bool,
27965 editor: Entity<Editor>,
27966 window: &mut Window,
27967 cx: &mut App,
27968 ) -> Option<AnyElement> {
27969 let folded = self.is_line_folded(buffer_row);
27970 let mut is_foldable = false;
27971
27972 if let Some(crease) = self
27973 .crease_snapshot
27974 .query_row(buffer_row, self.buffer_snapshot())
27975 {
27976 is_foldable = true;
27977 match crease {
27978 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27979 if let Some(render_toggle) = render_toggle {
27980 let toggle_callback =
27981 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27982 if folded {
27983 editor.update(cx, |editor, cx| {
27984 editor.fold_at(buffer_row, window, cx)
27985 });
27986 } else {
27987 editor.update(cx, |editor, cx| {
27988 editor.unfold_at(buffer_row, window, cx)
27989 });
27990 }
27991 });
27992 return Some((render_toggle)(
27993 buffer_row,
27994 folded,
27995 toggle_callback,
27996 window,
27997 cx,
27998 ));
27999 }
28000 }
28001 }
28002 }
28003
28004 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
28005
28006 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
28007 Some(
28008 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
28009 .toggle_state(folded)
28010 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
28011 if folded {
28012 this.unfold_at(buffer_row, window, cx);
28013 } else {
28014 this.fold_at(buffer_row, window, cx);
28015 }
28016 }))
28017 .into_any_element(),
28018 )
28019 } else {
28020 None
28021 }
28022 }
28023
28024 pub fn render_crease_trailer(
28025 &self,
28026 buffer_row: MultiBufferRow,
28027 window: &mut Window,
28028 cx: &mut App,
28029 ) -> Option<AnyElement> {
28030 let folded = self.is_line_folded(buffer_row);
28031 if let Crease::Inline { render_trailer, .. } = self
28032 .crease_snapshot
28033 .query_row(buffer_row, self.buffer_snapshot())?
28034 {
28035 let render_trailer = render_trailer.as_ref()?;
28036 Some(render_trailer(buffer_row, folded, window, cx))
28037 } else {
28038 None
28039 }
28040 }
28041
28042 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
28043 let digit_count = self.widest_line_number().ilog10() + 1;
28044 column_pixels(style, digit_count as usize, window)
28045 }
28046
28047 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
28048 ///
28049 /// This is positive if `base` is before `line`.
28050 fn relative_line_delta(
28051 &self,
28052 current_selection_head: DisplayRow,
28053 first_visible_row: DisplayRow,
28054 consider_wrapped_lines: bool,
28055 ) -> i64 {
28056 let current_selection_head = current_selection_head.as_display_point().to_point(self);
28057 let first_visible_row = first_visible_row.as_display_point().to_point(self);
28058
28059 if consider_wrapped_lines {
28060 let wrap_snapshot = self.wrap_snapshot();
28061 let base_wrap_row = wrap_snapshot
28062 .make_wrap_point(current_selection_head, Bias::Left)
28063 .row();
28064 let wrap_row = wrap_snapshot
28065 .make_wrap_point(first_visible_row, Bias::Left)
28066 .row();
28067
28068 wrap_row.0 as i64 - base_wrap_row.0 as i64
28069 } else {
28070 let fold_snapshot = self.fold_snapshot();
28071 let base_fold_row = fold_snapshot
28072 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
28073 .row();
28074 let fold_row = fold_snapshot
28075 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
28076 .row();
28077
28078 fold_row as i64 - base_fold_row as i64
28079 }
28080 }
28081
28082 /// Returns the unsigned relative line number to display for each row in `rows`.
28083 ///
28084 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
28085 pub fn calculate_relative_line_numbers(
28086 &self,
28087 rows: &Range<DisplayRow>,
28088 current_selection_head: DisplayRow,
28089 count_wrapped_lines: bool,
28090 ) -> HashMap<DisplayRow, u32> {
28091 let initial_offset =
28092 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
28093
28094 self.row_infos(rows.start)
28095 .take(rows.len())
28096 .enumerate()
28097 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
28098 .filter(|(_row, row_info)| {
28099 row_info.buffer_row.is_some()
28100 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
28101 })
28102 .enumerate()
28103 .filter_map(|(i, (row, row_info))| {
28104 // We want to ensure here that the current line has absolute
28105 // numbering, even if we are in a soft-wrapped line. With the
28106 // exception that if we are in a deleted line, we should number this
28107 // relative with 0, as otherwise it would have no line number at all
28108 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
28109
28110 (relative_line_number != 0
28111 || row_info
28112 .diff_status
28113 .is_some_and(|status| status.is_deleted()))
28114 .then_some((row, relative_line_number))
28115 })
28116 .collect()
28117 }
28118}
28119
28120pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28121 let font_size = style.text.font_size.to_pixels(window.rem_size());
28122 let layout = window.text_system().shape_line(
28123 SharedString::from(" ".repeat(column)),
28124 font_size,
28125 &[TextRun {
28126 len: column,
28127 font: style.text.font(),
28128 color: Hsla::default(),
28129 ..Default::default()
28130 }],
28131 None,
28132 );
28133
28134 layout.width
28135}
28136
28137impl Deref for EditorSnapshot {
28138 type Target = DisplaySnapshot;
28139
28140 fn deref(&self) -> &Self::Target {
28141 &self.display_snapshot
28142 }
28143}
28144
28145#[derive(Clone, Debug, PartialEq, Eq)]
28146pub enum EditorEvent {
28147 /// Emitted when the stored review comments change (added, removed, or updated).
28148 ReviewCommentsChanged {
28149 /// The new total count of review comments.
28150 total_count: usize,
28151 },
28152 InputIgnored {
28153 text: Arc<str>,
28154 },
28155 InputHandled {
28156 utf16_range_to_replace: Option<Range<isize>>,
28157 text: Arc<str>,
28158 },
28159 BufferRangesUpdated {
28160 buffer: Entity<Buffer>,
28161 path_key: PathKey,
28162 ranges: Vec<ExcerptRange<text::Anchor>>,
28163 },
28164 BuffersRemoved {
28165 removed_buffer_ids: Vec<BufferId>,
28166 },
28167 BuffersEdited {
28168 buffer_ids: Vec<BufferId>,
28169 },
28170 BufferFoldToggled {
28171 ids: Vec<BufferId>,
28172 folded: bool,
28173 },
28174 ExpandExcerptsRequested {
28175 excerpt_anchors: Vec<Anchor>,
28176 lines: u32,
28177 direction: ExpandExcerptDirection,
28178 },
28179 StageOrUnstageRequested {
28180 stage: bool,
28181 hunks: Vec<MultiBufferDiffHunk>,
28182 },
28183 OpenExcerptsRequested {
28184 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28185 split: bool,
28186 },
28187 RestoreRequested {
28188 hunks: Vec<MultiBufferDiffHunk>,
28189 },
28190 BufferEdited,
28191 Edited {
28192 transaction_id: clock::Lamport,
28193 },
28194 Reparsed(BufferId),
28195 Focused,
28196 FocusedIn,
28197 Blurred,
28198 DirtyChanged,
28199 Saved,
28200 TitleChanged,
28201 SelectionsChanged {
28202 local: bool,
28203 },
28204 ScrollPositionChanged {
28205 local: bool,
28206 autoscroll: bool,
28207 },
28208 TransactionUndone {
28209 transaction_id: clock::Lamport,
28210 },
28211 TransactionBegun {
28212 transaction_id: clock::Lamport,
28213 },
28214 CursorShapeChanged,
28215 BreadcrumbsChanged,
28216 OutlineSymbolsChanged,
28217 PushedToNavHistory {
28218 anchor: Anchor,
28219 is_deactivate: bool,
28220 },
28221}
28222
28223impl EventEmitter<EditorEvent> for Editor {}
28224
28225impl Focusable for Editor {
28226 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28227 self.focus_handle.clone()
28228 }
28229}
28230
28231impl Render for Editor {
28232 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28233 EditorElement::new(&cx.entity(), self.create_style(cx))
28234 }
28235}
28236
28237impl EntityInputHandler for Editor {
28238 fn text_for_range(
28239 &mut self,
28240 range_utf16: Range<usize>,
28241 adjusted_range: &mut Option<Range<usize>>,
28242 _: &mut Window,
28243 cx: &mut Context<Self>,
28244 ) -> Option<String> {
28245 let snapshot = self.buffer.read(cx).read(cx);
28246 let start = snapshot.clip_offset_utf16(
28247 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28248 Bias::Left,
28249 );
28250 let end = snapshot.clip_offset_utf16(
28251 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28252 Bias::Right,
28253 );
28254 if (start.0.0..end.0.0) != range_utf16 {
28255 adjusted_range.replace(start.0.0..end.0.0);
28256 }
28257 Some(snapshot.text_for_range(start..end).collect())
28258 }
28259
28260 fn selected_text_range(
28261 &mut self,
28262 ignore_disabled_input: bool,
28263 _: &mut Window,
28264 cx: &mut Context<Self>,
28265 ) -> Option<UTF16Selection> {
28266 // Prevent the IME menu from appearing when holding down an alphabetic key
28267 // while input is disabled.
28268 if !ignore_disabled_input && !self.input_enabled {
28269 return None;
28270 }
28271
28272 let selection = self
28273 .selections
28274 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28275 let range = selection.range();
28276
28277 Some(UTF16Selection {
28278 range: range.start.0.0..range.end.0.0,
28279 reversed: selection.reversed,
28280 })
28281 }
28282
28283 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28284 let snapshot = self.buffer.read(cx).read(cx);
28285 let range = self
28286 .text_highlights(HighlightKey::InputComposition, cx)?
28287 .1
28288 .first()?;
28289 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28290 }
28291
28292 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28293 self.clear_highlights(HighlightKey::InputComposition, cx);
28294 self.ime_transaction.take();
28295 }
28296
28297 fn replace_text_in_range(
28298 &mut self,
28299 range_utf16: Option<Range<usize>>,
28300 text: &str,
28301 window: &mut Window,
28302 cx: &mut Context<Self>,
28303 ) {
28304 if !self.input_enabled {
28305 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28306 return;
28307 }
28308
28309 self.transact(window, cx, |this, window, cx| {
28310 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28311 if let Some(marked_ranges) = this.marked_text_ranges(cx) {
28312 // During IME composition, macOS reports the replacement range
28313 // relative to the first marked region (the only one visible via
28314 // marked_text_range). The correct targets for replacement are the
28315 // marked ranges themselves — one per cursor — so use them directly.
28316 Some(marked_ranges)
28317 } else if range_utf16.start == range_utf16.end {
28318 // An empty replacement range means "insert at cursor" with no text
28319 // to replace. macOS reports the cursor position from its own
28320 // (single-cursor) view of the buffer, which diverges from our actual
28321 // cursor positions after multi-cursor edits have shifted offsets.
28322 // Treating this as range_utf16=None lets each cursor insert in place.
28323 None
28324 } else {
28325 // Outside of IME composition (e.g. Accessibility Keyboard word
28326 // completion), the range is an absolute document offset for the
28327 // newest cursor. Fan it out to all cursors via
28328 // selection_replacement_ranges, which applies the delta relative
28329 // to the newest selection to every cursor.
28330 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28331 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28332 Some(this.selection_replacement_ranges(range_utf16, cx))
28333 }
28334 } else {
28335 this.marked_text_ranges(cx)
28336 };
28337
28338 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28339 let newest_selection_id = this.selections.newest_anchor().id;
28340 this.selections
28341 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28342 .iter()
28343 .zip(ranges_to_replace.iter())
28344 .find_map(|(selection, range)| {
28345 if selection.id == newest_selection_id {
28346 Some(
28347 (range.start.0.0 as isize - selection.head().0.0 as isize)
28348 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28349 )
28350 } else {
28351 None
28352 }
28353 })
28354 });
28355
28356 cx.emit(EditorEvent::InputHandled {
28357 utf16_range_to_replace: range_to_replace,
28358 text: text.into(),
28359 });
28360
28361 if let Some(new_selected_ranges) = new_selected_ranges {
28362 // Only backspace if at least one range covers actual text. When all
28363 // ranges are empty (e.g. a trailing-space insertion from Accessibility
28364 // Keyboard sends replacementRange=cursor..cursor), backspace would
28365 // incorrectly delete the character just before the cursor.
28366 let should_backspace = new_selected_ranges.iter().any(|r| r.start != r.end);
28367 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28368 selections.select_ranges(new_selected_ranges)
28369 });
28370 if should_backspace {
28371 this.backspace(&Default::default(), window, cx);
28372 }
28373 }
28374
28375 this.handle_input(text, window, cx);
28376 });
28377
28378 if let Some(transaction) = self.ime_transaction {
28379 self.buffer.update(cx, |buffer, cx| {
28380 buffer.group_until_transaction(transaction, cx);
28381 });
28382 }
28383
28384 self.unmark_text(window, cx);
28385 }
28386
28387 fn replace_and_mark_text_in_range(
28388 &mut self,
28389 range_utf16: Option<Range<usize>>,
28390 text: &str,
28391 new_selected_range_utf16: Option<Range<usize>>,
28392 window: &mut Window,
28393 cx: &mut Context<Self>,
28394 ) {
28395 if !self.input_enabled {
28396 return;
28397 }
28398
28399 let transaction = self.transact(window, cx, |this, window, cx| {
28400 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28401 let snapshot = this.buffer.read(cx).read(cx);
28402 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28403 for marked_range in &mut marked_ranges {
28404 marked_range.end = marked_range.start + relative_range_utf16.end;
28405 marked_range.start += relative_range_utf16.start;
28406 marked_range.start =
28407 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28408 marked_range.end =
28409 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28410 }
28411 }
28412 Some(marked_ranges)
28413 } else if let Some(range_utf16) = range_utf16 {
28414 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28415 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28416 Some(this.selection_replacement_ranges(range_utf16, cx))
28417 } else {
28418 None
28419 };
28420
28421 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28422 let newest_selection_id = this.selections.newest_anchor().id;
28423 this.selections
28424 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28425 .iter()
28426 .zip(ranges_to_replace.iter())
28427 .find_map(|(selection, range)| {
28428 if selection.id == newest_selection_id {
28429 Some(
28430 (range.start.0.0 as isize - selection.head().0.0 as isize)
28431 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28432 )
28433 } else {
28434 None
28435 }
28436 })
28437 });
28438
28439 cx.emit(EditorEvent::InputHandled {
28440 utf16_range_to_replace: range_to_replace,
28441 text: text.into(),
28442 });
28443
28444 if let Some(ranges) = ranges_to_replace {
28445 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28446 s.select_ranges(ranges)
28447 });
28448 }
28449
28450 let marked_ranges = {
28451 let snapshot = this.buffer.read(cx).read(cx);
28452 this.selections
28453 .disjoint_anchors_arc()
28454 .iter()
28455 .map(|selection| {
28456 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28457 })
28458 .collect::<Vec<_>>()
28459 };
28460
28461 if text.is_empty() {
28462 this.unmark_text(window, cx);
28463 } else {
28464 this.highlight_text(
28465 HighlightKey::InputComposition,
28466 marked_ranges.clone(),
28467 HighlightStyle {
28468 underline: Some(UnderlineStyle {
28469 thickness: px(1.),
28470 color: None,
28471 wavy: false,
28472 }),
28473 ..Default::default()
28474 },
28475 cx,
28476 );
28477 }
28478
28479 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28480 let use_autoclose = this.use_autoclose;
28481 let use_auto_surround = this.use_auto_surround;
28482 this.set_use_autoclose(false);
28483 this.set_use_auto_surround(false);
28484 this.handle_input(text, window, cx);
28485 this.set_use_autoclose(use_autoclose);
28486 this.set_use_auto_surround(use_auto_surround);
28487
28488 if let Some(new_selected_range) = new_selected_range_utf16 {
28489 let snapshot = this.buffer.read(cx).read(cx);
28490 let new_selected_ranges = marked_ranges
28491 .into_iter()
28492 .map(|marked_range| {
28493 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28494 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28495 insertion_start.0 + new_selected_range.start,
28496 ));
28497 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28498 insertion_start.0 + new_selected_range.end,
28499 ));
28500 snapshot.clip_offset_utf16(new_start, Bias::Left)
28501 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28502 })
28503 .collect::<Vec<_>>();
28504
28505 drop(snapshot);
28506 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28507 selections.select_ranges(new_selected_ranges)
28508 });
28509 }
28510 });
28511
28512 self.ime_transaction = self.ime_transaction.or(transaction);
28513 if let Some(transaction) = self.ime_transaction {
28514 self.buffer.update(cx, |buffer, cx| {
28515 buffer.group_until_transaction(transaction, cx);
28516 });
28517 }
28518
28519 if self
28520 .text_highlights(HighlightKey::InputComposition, cx)
28521 .is_none()
28522 {
28523 self.ime_transaction.take();
28524 }
28525 }
28526
28527 fn bounds_for_range(
28528 &mut self,
28529 range_utf16: Range<usize>,
28530 element_bounds: gpui::Bounds<Pixels>,
28531 window: &mut Window,
28532 cx: &mut Context<Self>,
28533 ) -> Option<gpui::Bounds<Pixels>> {
28534 let text_layout_details = self.text_layout_details(window, cx);
28535 let CharacterDimensions {
28536 em_width,
28537 em_advance,
28538 line_height,
28539 } = self.character_dimensions(window, cx);
28540
28541 let snapshot = self.snapshot(window, cx);
28542 let scroll_position = snapshot.scroll_position();
28543 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28544
28545 let start =
28546 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28547 let x = Pixels::from(
28548 ScrollOffset::from(
28549 snapshot.x_for_display_point(start, &text_layout_details)
28550 + self.gutter_dimensions.full_width(),
28551 ) - scroll_left,
28552 );
28553 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28554
28555 Some(Bounds {
28556 origin: element_bounds.origin + point(x, y),
28557 size: size(em_width, line_height),
28558 })
28559 }
28560
28561 fn character_index_for_point(
28562 &mut self,
28563 point: gpui::Point<Pixels>,
28564 _window: &mut Window,
28565 _cx: &mut Context<Self>,
28566 ) -> Option<usize> {
28567 let position_map = self.last_position_map.as_ref()?;
28568 if !position_map.text_hitbox.contains(&point) {
28569 return None;
28570 }
28571 let display_point = position_map.point_for_position(point).previous_valid;
28572 let anchor = position_map
28573 .snapshot
28574 .display_point_to_anchor(display_point, Bias::Left);
28575 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28576 Some(utf16_offset.0.0)
28577 }
28578
28579 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28580 self.expects_character_input
28581 }
28582}
28583
28584trait SelectionExt {
28585 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28586 fn spanned_rows(
28587 &self,
28588 include_end_if_at_line_start: bool,
28589 map: &DisplaySnapshot,
28590 ) -> Range<MultiBufferRow>;
28591}
28592
28593impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28594 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28595 let start = self
28596 .start
28597 .to_point(map.buffer_snapshot())
28598 .to_display_point(map);
28599 let end = self
28600 .end
28601 .to_point(map.buffer_snapshot())
28602 .to_display_point(map);
28603 if self.reversed {
28604 end..start
28605 } else {
28606 start..end
28607 }
28608 }
28609
28610 fn spanned_rows(
28611 &self,
28612 include_end_if_at_line_start: bool,
28613 map: &DisplaySnapshot,
28614 ) -> Range<MultiBufferRow> {
28615 let start = self.start.to_point(map.buffer_snapshot());
28616 let mut end = self.end.to_point(map.buffer_snapshot());
28617 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28618 end.row -= 1;
28619 }
28620
28621 let buffer_start = map.prev_line_boundary(start).0;
28622 let buffer_end = map.next_line_boundary(end).0;
28623 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28624 }
28625}
28626
28627impl<T: InvalidationRegion> InvalidationStack<T> {
28628 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28629 where
28630 S: Clone + ToOffset,
28631 {
28632 while let Some(region) = self.last() {
28633 let all_selections_inside_invalidation_ranges =
28634 if selections.len() == region.ranges().len() {
28635 selections
28636 .iter()
28637 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28638 .all(|(selection, invalidation_range)| {
28639 let head = selection.head().to_offset(buffer);
28640 invalidation_range.start <= head && invalidation_range.end >= head
28641 })
28642 } else {
28643 false
28644 };
28645
28646 if all_selections_inside_invalidation_ranges {
28647 break;
28648 } else {
28649 self.pop();
28650 }
28651 }
28652 }
28653}
28654
28655#[derive(Clone)]
28656struct ErasedEditorImpl(Entity<Editor>);
28657
28658impl ui_input::ErasedEditor for ErasedEditorImpl {
28659 fn text(&self, cx: &App) -> String {
28660 self.0.read(cx).text(cx)
28661 }
28662
28663 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28664 self.0.update(cx, |this, cx| {
28665 this.set_text(text, window, cx);
28666 })
28667 }
28668
28669 fn clear(&self, window: &mut Window, cx: &mut App) {
28670 self.0.update(cx, |this, cx| this.clear(window, cx));
28671 }
28672
28673 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28674 self.0.update(cx, |this, cx| {
28675 this.set_placeholder_text(text, window, cx);
28676 });
28677 }
28678
28679 fn focus_handle(&self, cx: &App) -> FocusHandle {
28680 self.0.read(cx).focus_handle(cx)
28681 }
28682
28683 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28684 let settings = ThemeSettings::get_global(cx);
28685 let theme_color = cx.theme().colors();
28686
28687 let text_style = TextStyle {
28688 font_family: settings.ui_font.family.clone(),
28689 font_features: settings.ui_font.features.clone(),
28690 font_size: rems(0.875).into(),
28691 font_weight: settings.ui_font.weight,
28692 font_style: FontStyle::Normal,
28693 line_height: relative(1.2),
28694 color: theme_color.text,
28695 ..Default::default()
28696 };
28697 let editor_style = EditorStyle {
28698 background: theme_color.ghost_element_background,
28699 local_player: cx.theme().players().local(),
28700 syntax: cx.theme().syntax().clone(),
28701 text: text_style,
28702 ..Default::default()
28703 };
28704 EditorElement::new(&self.0, editor_style).into_any()
28705 }
28706
28707 fn as_any(&self) -> &dyn Any {
28708 &self.0
28709 }
28710
28711 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28712 self.0.update(cx, |editor, cx| {
28713 let editor_offset = editor.buffer().read(cx).len(cx);
28714 editor.change_selections(
28715 SelectionEffects::scroll(Autoscroll::Next),
28716 window,
28717 cx,
28718 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28719 );
28720 });
28721 }
28722
28723 fn subscribe(
28724 &self,
28725 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28726 window: &mut Window,
28727 cx: &mut App,
28728 ) -> Subscription {
28729 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28730 let event = match event {
28731 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28732 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28733 _ => return,
28734 };
28735 (callback)(event, window, cx);
28736 })
28737 }
28738
28739 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28740 self.0.update(cx, |editor, cx| {
28741 editor.set_masked(masked, cx);
28742 });
28743 }
28744}
28745impl<T> Default for InvalidationStack<T> {
28746 fn default() -> Self {
28747 Self(Default::default())
28748 }
28749}
28750
28751impl<T> Deref for InvalidationStack<T> {
28752 type Target = Vec<T>;
28753
28754 fn deref(&self) -> &Self::Target {
28755 &self.0
28756 }
28757}
28758
28759impl<T> DerefMut for InvalidationStack<T> {
28760 fn deref_mut(&mut self) -> &mut Self::Target {
28761 &mut self.0
28762 }
28763}
28764
28765impl InvalidationRegion for SnippetState {
28766 fn ranges(&self) -> &[Range<Anchor>] {
28767 &self.ranges[self.active_index]
28768 }
28769}
28770
28771fn edit_prediction_edit_text(
28772 current_snapshot: &BufferSnapshot,
28773 edits: &[(Range<Anchor>, impl AsRef<str>)],
28774 edit_preview: &EditPreview,
28775 include_deletions: bool,
28776 multibuffer_snapshot: &MultiBufferSnapshot,
28777 cx: &App,
28778) -> HighlightedText {
28779 let edits = edits
28780 .iter()
28781 .filter_map(|(anchor, text)| {
28782 Some((
28783 multibuffer_snapshot
28784 .anchor_range_to_buffer_anchor_range(anchor.clone())?
28785 .1,
28786 text,
28787 ))
28788 })
28789 .collect::<Vec<_>>();
28790
28791 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28792}
28793
28794fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28795 // Fallback for providers that don't provide edit_preview (like Copilot)
28796 // Just show the raw edit text with basic styling
28797 let mut text = String::new();
28798 let mut highlights = Vec::new();
28799
28800 let insertion_highlight_style = HighlightStyle {
28801 color: Some(cx.theme().colors().text),
28802 ..Default::default()
28803 };
28804
28805 for (_, edit_text) in edits {
28806 let start_offset = text.len();
28807 text.push_str(edit_text);
28808 let end_offset = text.len();
28809
28810 if start_offset < end_offset {
28811 highlights.push((start_offset..end_offset, insertion_highlight_style));
28812 }
28813 }
28814
28815 HighlightedText {
28816 text: text.into(),
28817 highlights,
28818 }
28819}
28820
28821pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28822 match severity {
28823 lsp::DiagnosticSeverity::ERROR => colors.error,
28824 lsp::DiagnosticSeverity::WARNING => colors.warning,
28825 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28826 lsp::DiagnosticSeverity::HINT => colors.info,
28827 _ => colors.ignored,
28828 }
28829}
28830
28831pub fn styled_runs_for_code_label<'a>(
28832 label: &'a CodeLabel,
28833 syntax_theme: &'a theme::SyntaxTheme,
28834 local_player: &'a theme::PlayerColor,
28835) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28836 let fade_out = HighlightStyle {
28837 fade_out: Some(0.35),
28838 ..Default::default()
28839 };
28840
28841 if label.runs.is_empty() {
28842 let desc_start = label.filter_range.end;
28843 let fade_run =
28844 (desc_start < label.text.len()).then(|| (desc_start..label.text.len(), fade_out));
28845 return Either::Left(fade_run.into_iter());
28846 }
28847
28848 let mut prev_end = label.filter_range.end;
28849 Either::Right(
28850 label
28851 .runs
28852 .iter()
28853 .enumerate()
28854 .flat_map(move |(ix, (range, highlight_id))| {
28855 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28856 HighlightStyle {
28857 color: Some(local_player.cursor),
28858 ..Default::default()
28859 }
28860 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28861 HighlightStyle {
28862 background_color: Some(local_player.selection),
28863 ..Default::default()
28864 }
28865 } else if let Some(style) = syntax_theme.get(*highlight_id).cloned() {
28866 style
28867 } else {
28868 return Default::default();
28869 };
28870
28871 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28872 let muted_style = style.highlight(fade_out);
28873 if range.start >= label.filter_range.end {
28874 if range.start > prev_end {
28875 runs.push((prev_end..range.start, fade_out));
28876 }
28877 runs.push((range.clone(), muted_style));
28878 } else if range.end <= label.filter_range.end {
28879 runs.push((range.clone(), style));
28880 } else {
28881 runs.push((range.start..label.filter_range.end, style));
28882 runs.push((label.filter_range.end..range.end, muted_style));
28883 }
28884 prev_end = cmp::max(prev_end, range.end);
28885
28886 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28887 runs.push((prev_end..label.text.len(), fade_out));
28888 }
28889
28890 runs
28891 }),
28892 )
28893}
28894
28895pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28896 let mut prev_index = 0;
28897 let mut prev_codepoint: Option<char> = None;
28898 text.char_indices()
28899 .chain([(text.len(), '\0')])
28900 .filter_map(move |(index, codepoint)| {
28901 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28902 let is_boundary = index == text.len()
28903 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28904 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28905 if is_boundary {
28906 let chunk = &text[prev_index..index];
28907 prev_index = index;
28908 Some(chunk)
28909 } else {
28910 None
28911 }
28912 })
28913}
28914
28915/// Given a string of text immediately before the cursor, iterates over possible
28916/// strings a snippet could match to. More precisely: returns an iterator over
28917/// suffixes of `text` created by splitting at word boundaries (before & after
28918/// every non-word character).
28919///
28920/// Shorter suffixes are returned first.
28921pub(crate) fn snippet_candidate_suffixes<'a>(
28922 text: &'a str,
28923 is_word_char: &'a dyn Fn(char) -> bool,
28924) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28925 let mut prev_index = text.len();
28926 let mut prev_codepoint = None;
28927 text.char_indices()
28928 .rev()
28929 .chain([(0, '\0')])
28930 .filter_map(move |(index, codepoint)| {
28931 let prev_index = std::mem::replace(&mut prev_index, index);
28932 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28933 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28934 None
28935 } else {
28936 let chunk = &text[prev_index..]; // go to end of string
28937 Some(chunk)
28938 }
28939 })
28940}
28941
28942pub trait RangeToAnchorExt: Sized {
28943 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28944
28945 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28946 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28947 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28948 }
28949}
28950
28951impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28952 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28953 let start_offset = self.start.to_offset(snapshot);
28954 let end_offset = self.end.to_offset(snapshot);
28955 if start_offset == end_offset {
28956 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28957 } else {
28958 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28959 }
28960 }
28961}
28962
28963pub trait RowExt {
28964 fn as_f64(&self) -> f64;
28965
28966 fn next_row(&self) -> Self;
28967
28968 fn previous_row(&self) -> Self;
28969
28970 fn minus(&self, other: Self) -> u32;
28971}
28972
28973impl RowExt for DisplayRow {
28974 fn as_f64(&self) -> f64 {
28975 self.0 as _
28976 }
28977
28978 fn next_row(&self) -> Self {
28979 Self(self.0 + 1)
28980 }
28981
28982 fn previous_row(&self) -> Self {
28983 Self(self.0.saturating_sub(1))
28984 }
28985
28986 fn minus(&self, other: Self) -> u32 {
28987 self.0 - other.0
28988 }
28989}
28990
28991impl RowExt for MultiBufferRow {
28992 fn as_f64(&self) -> f64 {
28993 self.0 as _
28994 }
28995
28996 fn next_row(&self) -> Self {
28997 Self(self.0 + 1)
28998 }
28999
29000 fn previous_row(&self) -> Self {
29001 Self(self.0.saturating_sub(1))
29002 }
29003
29004 fn minus(&self, other: Self) -> u32 {
29005 self.0 - other.0
29006 }
29007}
29008
29009trait RowRangeExt {
29010 type Row;
29011
29012 fn len(&self) -> usize;
29013
29014 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
29015}
29016
29017impl RowRangeExt for Range<MultiBufferRow> {
29018 type Row = MultiBufferRow;
29019
29020 fn len(&self) -> usize {
29021 (self.end.0 - self.start.0) as usize
29022 }
29023
29024 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
29025 (self.start.0..self.end.0).map(MultiBufferRow)
29026 }
29027}
29028
29029impl RowRangeExt for Range<DisplayRow> {
29030 type Row = DisplayRow;
29031
29032 fn len(&self) -> usize {
29033 (self.end.0 - self.start.0) as usize
29034 }
29035
29036 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
29037 (self.start.0..self.end.0).map(DisplayRow)
29038 }
29039}
29040
29041/// If select range has more than one line, we
29042/// just point the cursor to range.start.
29043fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
29044 if range.start.row == range.end.row {
29045 range
29046 } else {
29047 range.start..range.start
29048 }
29049}
29050pub struct KillRing(ClipboardItem);
29051impl Global for KillRing {}
29052
29053const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
29054
29055enum BreakpointPromptEditAction {
29056 Log,
29057 Condition,
29058 HitCondition,
29059}
29060
29061struct BreakpointPromptEditor {
29062 pub(crate) prompt: Entity<Editor>,
29063 editor: WeakEntity<Editor>,
29064 breakpoint_anchor: Anchor,
29065 breakpoint: Breakpoint,
29066 edit_action: BreakpointPromptEditAction,
29067 block_ids: HashSet<CustomBlockId>,
29068 editor_margins: Arc<Mutex<EditorMargins>>,
29069 _subscriptions: Vec<Subscription>,
29070}
29071
29072impl BreakpointPromptEditor {
29073 const MAX_LINES: u8 = 4;
29074
29075 fn new(
29076 editor: WeakEntity<Editor>,
29077 breakpoint_anchor: Anchor,
29078 breakpoint: Breakpoint,
29079 edit_action: BreakpointPromptEditAction,
29080 window: &mut Window,
29081 cx: &mut Context<Self>,
29082 ) -> Self {
29083 let base_text = match edit_action {
29084 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
29085 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
29086 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
29087 }
29088 .map(|msg| msg.to_string())
29089 .unwrap_or_default();
29090
29091 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
29092 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29093
29094 let prompt = cx.new(|cx| {
29095 let mut prompt = Editor::new(
29096 EditorMode::AutoHeight {
29097 min_lines: 1,
29098 max_lines: Some(Self::MAX_LINES as usize),
29099 },
29100 buffer,
29101 None,
29102 window,
29103 cx,
29104 );
29105 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
29106 prompt.set_show_cursor_when_unfocused(false, cx);
29107 prompt.set_placeholder_text(
29108 match edit_action {
29109 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
29110 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
29111 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
29112 },
29113 window,
29114 cx,
29115 );
29116
29117 prompt
29118 });
29119
29120 Self {
29121 prompt,
29122 editor,
29123 breakpoint_anchor,
29124 breakpoint,
29125 edit_action,
29126 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
29127 block_ids: Default::default(),
29128 _subscriptions: vec![],
29129 }
29130 }
29131
29132 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
29133 self.block_ids.extend(block_ids)
29134 }
29135
29136 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
29137 if let Some(editor) = self.editor.upgrade() {
29138 let message = self
29139 .prompt
29140 .read(cx)
29141 .buffer
29142 .read(cx)
29143 .as_singleton()
29144 .expect("A multi buffer in breakpoint prompt isn't possible")
29145 .read(cx)
29146 .as_rope()
29147 .to_string();
29148
29149 editor.update(cx, |editor, cx| {
29150 editor.edit_breakpoint_at_anchor(
29151 self.breakpoint_anchor,
29152 self.breakpoint.clone(),
29153 match self.edit_action {
29154 BreakpointPromptEditAction::Log => {
29155 BreakpointEditAction::EditLogMessage(message.into())
29156 }
29157 BreakpointPromptEditAction::Condition => {
29158 BreakpointEditAction::EditCondition(message.into())
29159 }
29160 BreakpointPromptEditAction::HitCondition => {
29161 BreakpointEditAction::EditHitCondition(message.into())
29162 }
29163 },
29164 cx,
29165 );
29166
29167 editor.remove_blocks(self.block_ids.clone(), None, cx);
29168 cx.focus_self(window);
29169 });
29170 }
29171 }
29172
29173 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29174 self.editor
29175 .update(cx, |editor, cx| {
29176 editor.remove_blocks(self.block_ids.clone(), None, cx);
29177 window.focus(&editor.focus_handle, cx);
29178 })
29179 .log_err();
29180 }
29181
29182 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29183 let settings = ThemeSettings::get_global(cx);
29184 let text_style = TextStyle {
29185 color: if self.prompt.read(cx).read_only(cx) {
29186 cx.theme().colors().text_disabled
29187 } else {
29188 cx.theme().colors().text
29189 },
29190 font_family: settings.buffer_font.family.clone(),
29191 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29192 font_size: settings.buffer_font_size(cx).into(),
29193 font_weight: settings.buffer_font.weight,
29194 line_height: relative(settings.buffer_line_height.value()),
29195 ..Default::default()
29196 };
29197 EditorElement::new(
29198 &self.prompt,
29199 EditorStyle {
29200 background: cx.theme().colors().editor_background,
29201 local_player: cx.theme().players().local(),
29202 text: text_style,
29203 ..Default::default()
29204 },
29205 )
29206 }
29207
29208 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29209 let focus_handle = self.prompt.focus_handle(cx);
29210 IconButton::new("cancel", IconName::Close)
29211 .icon_color(Color::Muted)
29212 .shape(IconButtonShape::Square)
29213 .tooltip(move |_window, cx| {
29214 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29215 })
29216 .on_click(cx.listener(|this, _, window, cx| {
29217 this.cancel(&menu::Cancel, window, cx);
29218 }))
29219 }
29220
29221 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29222 let focus_handle = self.prompt.focus_handle(cx);
29223 IconButton::new("confirm", IconName::Return)
29224 .icon_color(Color::Muted)
29225 .shape(IconButtonShape::Square)
29226 .tooltip(move |_window, cx| {
29227 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29228 })
29229 .on_click(cx.listener(|this, _, window, cx| {
29230 this.confirm(&menu::Confirm, window, cx);
29231 }))
29232 }
29233}
29234
29235impl Render for BreakpointPromptEditor {
29236 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29237 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29238 let editor_margins = *self.editor_margins.lock();
29239 let gutter_dimensions = editor_margins.gutter;
29240 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29241 let right_padding = editor_margins.right + px(9.);
29242 h_flex()
29243 .key_context("Editor")
29244 .bg(cx.theme().colors().editor_background)
29245 .border_y_1()
29246 .border_color(cx.theme().status().info_border)
29247 .size_full()
29248 .py(window.line_height() / 2.5)
29249 .pr(right_padding)
29250 .on_action(cx.listener(Self::confirm))
29251 .on_action(cx.listener(Self::cancel))
29252 .child(
29253 WithRemSize::new(ui_font_size)
29254 .h_full()
29255 .w(left_gutter_width)
29256 .flex()
29257 .flex_row()
29258 .flex_shrink_0()
29259 .items_center()
29260 .justify_center()
29261 .gap_1()
29262 .child(self.render_close_button(cx)),
29263 )
29264 .child(
29265 h_flex()
29266 .w_full()
29267 .justify_between()
29268 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29269 .child(
29270 WithRemSize::new(ui_font_size)
29271 .flex()
29272 .flex_row()
29273 .items_center()
29274 .child(self.render_confirm_button(cx)),
29275 ),
29276 )
29277 }
29278}
29279
29280impl Focusable for BreakpointPromptEditor {
29281 fn focus_handle(&self, cx: &App) -> FocusHandle {
29282 self.prompt.focus_handle(cx)
29283 }
29284}
29285
29286fn all_edits_insertions_or_deletions(
29287 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29288 snapshot: &MultiBufferSnapshot,
29289) -> bool {
29290 let mut all_insertions = true;
29291 let mut all_deletions = true;
29292
29293 for (range, new_text) in edits.iter() {
29294 let range_is_empty = range.to_offset(snapshot).is_empty();
29295 let text_is_empty = new_text.is_empty();
29296
29297 if range_is_empty != text_is_empty {
29298 if range_is_empty {
29299 all_deletions = false;
29300 } else {
29301 all_insertions = false;
29302 }
29303 } else {
29304 return false;
29305 }
29306
29307 if !all_insertions && !all_deletions {
29308 return false;
29309 }
29310 }
29311 all_insertions || all_deletions
29312}
29313
29314struct MissingEditPredictionKeybindingTooltip;
29315
29316impl Render for MissingEditPredictionKeybindingTooltip {
29317 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29318 ui::tooltip_container(cx, |container, cx| {
29319 container
29320 .flex_shrink_0()
29321 .max_w_80()
29322 .min_h(rems_from_px(124.))
29323 .justify_between()
29324 .child(
29325 v_flex()
29326 .flex_1()
29327 .text_ui_sm(cx)
29328 .child(Label::new("Conflict with Accept Keybinding"))
29329 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29330 )
29331 .child(
29332 h_flex()
29333 .pb_1()
29334 .gap_1()
29335 .items_end()
29336 .w_full()
29337 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29338 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29339 }))
29340 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29341 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29342 })),
29343 )
29344 })
29345 }
29346}
29347
29348#[derive(Debug, Clone, Copy, PartialEq)]
29349pub struct LineHighlight {
29350 pub background: Background,
29351 pub border: Option<gpui::Hsla>,
29352 pub include_gutter: bool,
29353 pub type_id: Option<TypeId>,
29354}
29355
29356struct LineManipulationResult {
29357 pub new_text: String,
29358 pub line_count_before: usize,
29359 pub line_count_after: usize,
29360}
29361
29362fn render_diff_hunk_controls(
29363 row: u32,
29364 status: &DiffHunkStatus,
29365 hunk_range: Range<Anchor>,
29366 is_created_file: bool,
29367 line_height: Pixels,
29368 editor: &Entity<Editor>,
29369 _window: &mut Window,
29370 cx: &mut App,
29371) -> AnyElement {
29372 h_flex()
29373 .h(line_height)
29374 .mr_1()
29375 .gap_1()
29376 .px_0p5()
29377 .pb_1()
29378 .border_x_1()
29379 .border_b_1()
29380 .border_color(cx.theme().colors().border_variant)
29381 .rounded_b_lg()
29382 .bg(cx.theme().colors().editor_background)
29383 .gap_1()
29384 .block_mouse_except_scroll()
29385 .shadow_md()
29386 .child(if status.has_secondary_hunk() {
29387 Button::new(("stage", row as u64), "Stage")
29388 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29389 .tooltip({
29390 let focus_handle = editor.focus_handle(cx);
29391 move |_window, cx| {
29392 Tooltip::for_action_in(
29393 "Stage Hunk",
29394 &::git::ToggleStaged,
29395 &focus_handle,
29396 cx,
29397 )
29398 }
29399 })
29400 .on_click({
29401 let editor = editor.clone();
29402 move |_event, _window, cx| {
29403 editor.update(cx, |editor, cx| {
29404 editor.stage_or_unstage_diff_hunks(
29405 true,
29406 vec![hunk_range.start..hunk_range.start],
29407 cx,
29408 );
29409 });
29410 }
29411 })
29412 } else {
29413 Button::new(("unstage", row as u64), "Unstage")
29414 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29415 .tooltip({
29416 let focus_handle = editor.focus_handle(cx);
29417 move |_window, cx| {
29418 Tooltip::for_action_in(
29419 "Unstage Hunk",
29420 &::git::ToggleStaged,
29421 &focus_handle,
29422 cx,
29423 )
29424 }
29425 })
29426 .on_click({
29427 let editor = editor.clone();
29428 move |_event, _window, cx| {
29429 editor.update(cx, |editor, cx| {
29430 editor.stage_or_unstage_diff_hunks(
29431 false,
29432 vec![hunk_range.start..hunk_range.start],
29433 cx,
29434 );
29435 });
29436 }
29437 })
29438 })
29439 .child(
29440 Button::new(("restore", row as u64), "Restore")
29441 .tooltip({
29442 let focus_handle = editor.focus_handle(cx);
29443 move |_window, cx| {
29444 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29445 }
29446 })
29447 .on_click({
29448 let editor = editor.clone();
29449 move |_event, window, cx| {
29450 editor.update(cx, |editor, cx| {
29451 let snapshot = editor.snapshot(window, cx);
29452 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29453 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29454 });
29455 }
29456 })
29457 .disabled(is_created_file),
29458 )
29459 .when(
29460 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29461 |el| {
29462 el.child(
29463 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29464 .shape(IconButtonShape::Square)
29465 .icon_size(IconSize::Small)
29466 // .disabled(!has_multiple_hunks)
29467 .tooltip({
29468 let focus_handle = editor.focus_handle(cx);
29469 move |_window, cx| {
29470 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29471 }
29472 })
29473 .on_click({
29474 let editor = editor.clone();
29475 move |_event, window, cx| {
29476 editor.update(cx, |editor, cx| {
29477 let snapshot = editor.snapshot(window, cx);
29478 let position =
29479 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29480 editor.go_to_hunk_before_or_after_position(
29481 &snapshot,
29482 position,
29483 Direction::Next,
29484 true,
29485 window,
29486 cx,
29487 );
29488 editor.expand_selected_diff_hunks(cx);
29489 });
29490 }
29491 }),
29492 )
29493 .child(
29494 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29495 .shape(IconButtonShape::Square)
29496 .icon_size(IconSize::Small)
29497 // .disabled(!has_multiple_hunks)
29498 .tooltip({
29499 let focus_handle = editor.focus_handle(cx);
29500 move |_window, cx| {
29501 Tooltip::for_action_in(
29502 "Previous Hunk",
29503 &GoToPreviousHunk,
29504 &focus_handle,
29505 cx,
29506 )
29507 }
29508 })
29509 .on_click({
29510 let editor = editor.clone();
29511 move |_event, window, cx| {
29512 editor.update(cx, |editor, cx| {
29513 let snapshot = editor.snapshot(window, cx);
29514 let point =
29515 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29516 editor.go_to_hunk_before_or_after_position(
29517 &snapshot,
29518 point,
29519 Direction::Prev,
29520 true,
29521 window,
29522 cx,
29523 );
29524 editor.expand_selected_diff_hunks(cx);
29525 });
29526 }
29527 }),
29528 )
29529 },
29530 )
29531 .into_any_element()
29532}
29533
29534pub fn multibuffer_context_lines(cx: &App) -> u32 {
29535 EditorSettings::try_get(cx)
29536 .map(|settings| settings.excerpt_context_lines)
29537 .unwrap_or(2)
29538 .min(32)
29539}