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 ranges = buffer_ids
11745 .into_iter()
11746 .flat_map(|buffer_id| snapshot.range_for_buffer(buffer_id))
11747 .collect::<Vec<_>>();
11748
11749 self.restore_hunks_in_ranges(ranges, window, cx);
11750 }
11751
11752 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11753 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11754 let selections = self
11755 .selections
11756 .all(&self.display_snapshot(cx))
11757 .into_iter()
11758 .map(|s| s.range())
11759 .collect();
11760 self.restore_hunks_in_ranges(selections, window, cx);
11761 }
11762
11763 /// Restores the diff hunks in the editor's selections and moves the cursor
11764 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11765 /// not all diff hunks are expanded.
11766 pub fn restore_and_next(
11767 &mut self,
11768 _: &::git::RestoreAndNext,
11769 window: &mut Window,
11770 cx: &mut Context<Self>,
11771 ) {
11772 let selections = self
11773 .selections
11774 .all(&self.display_snapshot(cx))
11775 .into_iter()
11776 .map(|selection| selection.range())
11777 .collect();
11778
11779 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11780 self.restore_hunks_in_ranges(selections, window, cx);
11781
11782 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11783 let wrap_around = !all_diff_hunks_expanded;
11784 let snapshot = self.snapshot(window, cx);
11785 let position = self
11786 .selections
11787 .newest::<Point>(&snapshot.display_snapshot)
11788 .head();
11789
11790 self.go_to_hunk_before_or_after_position(
11791 &snapshot,
11792 position,
11793 Direction::Next,
11794 wrap_around,
11795 window,
11796 cx,
11797 );
11798 }
11799
11800 pub fn restore_hunks_in_ranges(
11801 &mut self,
11802 ranges: Vec<Range<Point>>,
11803 window: &mut Window,
11804 cx: &mut Context<Editor>,
11805 ) {
11806 if self.delegate_stage_and_restore {
11807 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11808 if !hunks.is_empty() {
11809 cx.emit(EditorEvent::RestoreRequested { hunks });
11810 }
11811 return;
11812 }
11813 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11814 self.transact(window, cx, |editor, window, cx| {
11815 editor.restore_diff_hunks(hunks, cx);
11816 let selections = editor
11817 .selections
11818 .all::<MultiBufferOffset>(&editor.display_snapshot(cx));
11819 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11820 s.select(selections);
11821 });
11822 });
11823 }
11824
11825 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11826 let mut revert_changes = HashMap::default();
11827 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11828 for (buffer_id, hunks) in &chunk_by {
11829 let hunks = hunks.collect::<Vec<_>>();
11830 for hunk in &hunks {
11831 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11832 }
11833 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11834 }
11835 if !revert_changes.is_empty() {
11836 self.buffer().update(cx, |multi_buffer, cx| {
11837 for (buffer_id, changes) in revert_changes {
11838 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11839 buffer.update(cx, |buffer, cx| {
11840 buffer.edit(
11841 changes
11842 .into_iter()
11843 .map(|(range, text)| (range, text.to_string())),
11844 None,
11845 cx,
11846 );
11847 });
11848 }
11849 }
11850 });
11851 }
11852 }
11853
11854 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11855 if let Some(status) = self
11856 .addons
11857 .iter()
11858 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11859 {
11860 return Some(status);
11861 }
11862 self.project
11863 .as_ref()?
11864 .read(cx)
11865 .status_for_buffer_id(buffer_id, cx)
11866 }
11867
11868 pub fn open_active_item_in_terminal(
11869 &mut self,
11870 _: &OpenInTerminal,
11871 window: &mut Window,
11872 cx: &mut Context<Self>,
11873 ) {
11874 if let Some(working_directory) = self.active_buffer(cx).and_then(|buffer| {
11875 let project_path = buffer.read(cx).project_path(cx)?;
11876 let project = self.project()?.read(cx);
11877 let entry = project.entry_for_path(&project_path, cx)?;
11878 let parent = match &entry.canonical_path {
11879 Some(canonical_path) => canonical_path.to_path_buf(),
11880 None => project.absolute_path(&project_path, cx)?,
11881 }
11882 .parent()?
11883 .to_path_buf();
11884 Some(parent)
11885 }) {
11886 window.dispatch_action(
11887 OpenTerminal {
11888 working_directory,
11889 local: false,
11890 }
11891 .boxed_clone(),
11892 cx,
11893 );
11894 }
11895 }
11896
11897 fn set_breakpoint_context_menu(
11898 &mut self,
11899 display_row: DisplayRow,
11900 position: Option<Anchor>,
11901 clicked_point: gpui::Point<Pixels>,
11902 window: &mut Window,
11903 cx: &mut Context<Self>,
11904 ) {
11905 let source = self
11906 .buffer
11907 .read(cx)
11908 .snapshot(cx)
11909 .anchor_before(Point::new(display_row.0, 0u32));
11910
11911 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11912
11913 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11914 self,
11915 source,
11916 clicked_point,
11917 context_menu,
11918 window,
11919 cx,
11920 );
11921 }
11922
11923 fn add_edit_breakpoint_block(
11924 &mut self,
11925 anchor: Anchor,
11926 breakpoint: &Breakpoint,
11927 edit_action: BreakpointPromptEditAction,
11928 window: &mut Window,
11929 cx: &mut Context<Self>,
11930 ) {
11931 let weak_editor = cx.weak_entity();
11932 let bp_prompt = cx.new(|cx| {
11933 BreakpointPromptEditor::new(
11934 weak_editor,
11935 anchor,
11936 breakpoint.clone(),
11937 edit_action,
11938 window,
11939 cx,
11940 )
11941 });
11942
11943 let height = bp_prompt.update(cx, |this, cx| {
11944 this.prompt
11945 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11946 });
11947 let cloned_prompt = bp_prompt.clone();
11948 let blocks = vec![BlockProperties {
11949 style: BlockStyle::Sticky,
11950 placement: BlockPlacement::Above(anchor),
11951 height: Some(height),
11952 render: Arc::new(move |cx| {
11953 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11954 cloned_prompt.clone().into_any_element()
11955 }),
11956 priority: 0,
11957 }];
11958
11959 let focus_handle = bp_prompt.focus_handle(cx);
11960 window.focus(&focus_handle, cx);
11961
11962 let block_ids = self.insert_blocks(blocks, None, cx);
11963 bp_prompt.update(cx, |prompt, _| {
11964 prompt.add_block_ids(block_ids);
11965 });
11966 }
11967
11968 pub(crate) fn breakpoint_at_row(
11969 &self,
11970 row: u32,
11971 window: &mut Window,
11972 cx: &mut Context<Self>,
11973 ) -> Option<(Anchor, Breakpoint)> {
11974 let snapshot = self.snapshot(window, cx);
11975 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11976
11977 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11978 }
11979
11980 pub(crate) fn breakpoint_at_anchor(
11981 &self,
11982 breakpoint_position: Anchor,
11983 snapshot: &EditorSnapshot,
11984 cx: &mut Context<Self>,
11985 ) -> Option<(Anchor, Breakpoint)> {
11986 let (breakpoint_position, _) = snapshot
11987 .buffer_snapshot()
11988 .anchor_to_buffer_anchor(breakpoint_position)?;
11989 let buffer = self.buffer.read(cx).buffer(breakpoint_position.buffer_id)?;
11990
11991 let buffer_snapshot = buffer.read(cx).snapshot();
11992
11993 let row = buffer_snapshot
11994 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position)
11995 .row;
11996
11997 let line_len = buffer_snapshot.line_len(row);
11998 let anchor_end = buffer_snapshot.anchor_after(Point::new(row, line_len));
11999
12000 self.breakpoint_store
12001 .as_ref()?
12002 .read_with(cx, |breakpoint_store, cx| {
12003 breakpoint_store
12004 .breakpoints(
12005 &buffer,
12006 Some(breakpoint_position..anchor_end),
12007 &buffer_snapshot,
12008 cx,
12009 )
12010 .next()
12011 .and_then(|(bp, _)| {
12012 let breakpoint_row = buffer_snapshot
12013 .summary_for_anchor::<text::PointUtf16>(&bp.position)
12014 .row;
12015
12016 if breakpoint_row == row {
12017 snapshot
12018 .buffer_snapshot()
12019 .anchor_in_excerpt(bp.position)
12020 .map(|position| (position, bp.bp.clone()))
12021 } else {
12022 None
12023 }
12024 })
12025 })
12026 }
12027
12028 pub fn edit_log_breakpoint(
12029 &mut self,
12030 _: &EditLogBreakpoint,
12031 window: &mut Window,
12032 cx: &mut Context<Self>,
12033 ) {
12034 if self.breakpoint_store.is_none() {
12035 return;
12036 }
12037
12038 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12039 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
12040 message: None,
12041 state: BreakpointState::Enabled,
12042 condition: None,
12043 hit_condition: None,
12044 });
12045
12046 self.add_edit_breakpoint_block(
12047 anchor,
12048 &breakpoint,
12049 BreakpointPromptEditAction::Log,
12050 window,
12051 cx,
12052 );
12053 }
12054 }
12055
12056 fn breakpoints_at_cursors(
12057 &self,
12058 window: &mut Window,
12059 cx: &mut Context<Self>,
12060 ) -> Vec<(Anchor, Option<Breakpoint>)> {
12061 let snapshot = self.snapshot(window, cx);
12062 let cursors = self
12063 .selections
12064 .disjoint_anchors_arc()
12065 .iter()
12066 .map(|selection| {
12067 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
12068
12069 let breakpoint_position = self
12070 .breakpoint_at_row(cursor_position.row, window, cx)
12071 .map(|bp| bp.0)
12072 .unwrap_or_else(|| {
12073 snapshot
12074 .display_snapshot
12075 .buffer_snapshot()
12076 .anchor_after(Point::new(cursor_position.row, 0))
12077 });
12078
12079 let breakpoint = self
12080 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
12081 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
12082
12083 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
12084 })
12085 // 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.
12086 .collect::<HashMap<Anchor, _>>();
12087
12088 cursors.into_iter().collect()
12089 }
12090
12091 pub fn enable_breakpoint(
12092 &mut self,
12093 _: &crate::actions::EnableBreakpoint,
12094 window: &mut Window,
12095 cx: &mut Context<Self>,
12096 ) {
12097 if self.breakpoint_store.is_none() {
12098 return;
12099 }
12100
12101 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12102 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
12103 continue;
12104 };
12105 self.edit_breakpoint_at_anchor(
12106 anchor,
12107 breakpoint,
12108 BreakpointEditAction::InvertState,
12109 cx,
12110 );
12111 }
12112 }
12113
12114 pub fn align_selections(
12115 &mut self,
12116 _: &crate::actions::AlignSelections,
12117 window: &mut Window,
12118 cx: &mut Context<Self>,
12119 ) {
12120 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12121
12122 let display_snapshot = self.display_snapshot(cx);
12123
12124 struct CursorData {
12125 anchor: Anchor,
12126 point: Point,
12127 }
12128 let cursor_data: Vec<CursorData> = self
12129 .selections
12130 .disjoint_anchors()
12131 .iter()
12132 .map(|selection| {
12133 let anchor = if selection.reversed {
12134 selection.head()
12135 } else {
12136 selection.tail()
12137 };
12138 CursorData {
12139 anchor: anchor,
12140 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
12141 }
12142 })
12143 .collect();
12144
12145 let rows_anchors_count: Vec<usize> = cursor_data
12146 .iter()
12147 .map(|cursor| cursor.point.row)
12148 .chunk_by(|&row| row)
12149 .into_iter()
12150 .map(|(_, group)| group.count())
12151 .collect();
12152 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
12153 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
12154 let mut edits = Vec::new();
12155
12156 for column_idx in 0..max_columns {
12157 let mut cursor_index = 0;
12158
12159 // Calculate target_column => position that the selections will go
12160 let mut target_column = 0;
12161 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12162 // Skip rows that don't have this column
12163 if column_idx >= *cursor_count {
12164 cursor_index += cursor_count;
12165 continue;
12166 }
12167
12168 let point = &cursor_data[cursor_index + column_idx].point;
12169 let adjusted_column = point.column + rows_column_offset[row_idx];
12170 if adjusted_column > target_column {
12171 target_column = adjusted_column;
12172 }
12173 cursor_index += cursor_count;
12174 }
12175
12176 // Collect edits for this column
12177 cursor_index = 0;
12178 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12179 // Skip rows that don't have this column
12180 if column_idx >= *cursor_count {
12181 cursor_index += *cursor_count;
12182 continue;
12183 }
12184
12185 let point = &cursor_data[cursor_index + column_idx].point;
12186 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
12187 if spaces_needed > 0 {
12188 let anchor = cursor_data[cursor_index + column_idx]
12189 .anchor
12190 .bias_left(&display_snapshot);
12191 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
12192 }
12193 rows_column_offset[row_idx] += spaces_needed;
12194
12195 cursor_index += *cursor_count;
12196 }
12197 }
12198
12199 if !edits.is_empty() {
12200 self.transact(window, cx, |editor, _window, cx| {
12201 editor.edit(edits, cx);
12202 });
12203 }
12204 }
12205
12206 pub fn disable_breakpoint(
12207 &mut self,
12208 _: &crate::actions::DisableBreakpoint,
12209 window: &mut Window,
12210 cx: &mut Context<Self>,
12211 ) {
12212 if self.breakpoint_store.is_none() {
12213 return;
12214 }
12215
12216 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12217 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12218 continue;
12219 };
12220 self.edit_breakpoint_at_anchor(
12221 anchor,
12222 breakpoint,
12223 BreakpointEditAction::InvertState,
12224 cx,
12225 );
12226 }
12227 }
12228
12229 pub fn toggle_breakpoint(
12230 &mut self,
12231 _: &crate::actions::ToggleBreakpoint,
12232 window: &mut Window,
12233 cx: &mut Context<Self>,
12234 ) {
12235 if self.breakpoint_store.is_none() {
12236 return;
12237 }
12238
12239 let snapshot = self.snapshot(window, cx);
12240 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12241 if self.gutter_breakpoint_indicator.0.is_some() {
12242 let display_row = anchor
12243 .to_point(snapshot.buffer_snapshot())
12244 .to_display_point(&snapshot.display_snapshot)
12245 .row();
12246 self.update_breakpoint_collision_on_toggle(
12247 display_row,
12248 &BreakpointEditAction::Toggle,
12249 );
12250 }
12251
12252 if let Some(breakpoint) = breakpoint {
12253 self.edit_breakpoint_at_anchor(
12254 anchor,
12255 breakpoint,
12256 BreakpointEditAction::Toggle,
12257 cx,
12258 );
12259 } else {
12260 self.edit_breakpoint_at_anchor(
12261 anchor,
12262 Breakpoint::new_standard(),
12263 BreakpointEditAction::Toggle,
12264 cx,
12265 );
12266 }
12267 }
12268 }
12269
12270 fn update_breakpoint_collision_on_toggle(
12271 &mut self,
12272 display_row: DisplayRow,
12273 edit_action: &BreakpointEditAction,
12274 ) {
12275 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12276 if breakpoint_indicator.display_row == display_row
12277 && matches!(edit_action, BreakpointEditAction::Toggle)
12278 {
12279 breakpoint_indicator.collides_with_existing_breakpoint =
12280 !breakpoint_indicator.collides_with_existing_breakpoint;
12281 }
12282 }
12283 }
12284
12285 pub fn edit_breakpoint_at_anchor(
12286 &mut self,
12287 breakpoint_position: Anchor,
12288 breakpoint: Breakpoint,
12289 edit_action: BreakpointEditAction,
12290 cx: &mut Context<Self>,
12291 ) {
12292 let Some(breakpoint_store) = &self.breakpoint_store else {
12293 return;
12294 };
12295 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
12296 let Some((position, _)) = buffer_snapshot.anchor_to_buffer_anchor(breakpoint_position)
12297 else {
12298 return;
12299 };
12300 let Some(buffer) = self.buffer.read(cx).buffer(position.buffer_id) else {
12301 return;
12302 };
12303
12304 breakpoint_store.update(cx, |breakpoint_store, cx| {
12305 breakpoint_store.toggle_breakpoint(
12306 buffer,
12307 BreakpointWithPosition {
12308 position,
12309 bp: breakpoint,
12310 },
12311 edit_action,
12312 cx,
12313 );
12314 });
12315
12316 cx.notify();
12317 }
12318
12319 #[cfg(any(test, feature = "test-support"))]
12320 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12321 self.breakpoint_store.clone()
12322 }
12323
12324 pub fn prepare_restore_change(
12325 &self,
12326 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12327 hunk: &MultiBufferDiffHunk,
12328 cx: &mut App,
12329 ) -> Option<()> {
12330 if hunk.is_created_file() {
12331 return None;
12332 }
12333 let multi_buffer = self.buffer.read(cx);
12334 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
12335 let diff_snapshot = multi_buffer_snapshot.diff_for_buffer_id(hunk.buffer_id)?;
12336 let original_text = diff_snapshot
12337 .base_text()
12338 .as_rope()
12339 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12340 let buffer = multi_buffer.buffer(hunk.buffer_id)?;
12341 let buffer = buffer.read(cx);
12342 let buffer_snapshot = buffer.snapshot();
12343 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12344 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12345 probe
12346 .0
12347 .start
12348 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12349 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12350 }) {
12351 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12352 Some(())
12353 } else {
12354 None
12355 }
12356 }
12357
12358 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12359 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12360 }
12361
12362 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12363 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12364 }
12365
12366 pub fn rotate_selections_forward(
12367 &mut self,
12368 _: &RotateSelectionsForward,
12369 window: &mut Window,
12370 cx: &mut Context<Self>,
12371 ) {
12372 self.rotate_selections(window, cx, false)
12373 }
12374
12375 pub fn rotate_selections_backward(
12376 &mut self,
12377 _: &RotateSelectionsBackward,
12378 window: &mut Window,
12379 cx: &mut Context<Self>,
12380 ) {
12381 self.rotate_selections(window, cx, true)
12382 }
12383
12384 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12385 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12386 let display_snapshot = self.display_snapshot(cx);
12387 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12388
12389 if selections.len() < 2 {
12390 return;
12391 }
12392
12393 let (edits, new_selections) = {
12394 let buffer = self.buffer.read(cx).read(cx);
12395 let has_selections = selections.iter().any(|s| !s.is_empty());
12396 if has_selections {
12397 let mut selected_texts: Vec<String> = selections
12398 .iter()
12399 .map(|selection| {
12400 buffer
12401 .text_for_range(selection.start..selection.end)
12402 .collect()
12403 })
12404 .collect();
12405
12406 if reverse {
12407 selected_texts.rotate_left(1);
12408 } else {
12409 selected_texts.rotate_right(1);
12410 }
12411
12412 let mut offset_delta: i64 = 0;
12413 let mut new_selections = Vec::new();
12414 let edits: Vec<_> = selections
12415 .iter()
12416 .zip(selected_texts.iter())
12417 .map(|(selection, new_text)| {
12418 let old_len = (selection.end.0 - selection.start.0) as i64;
12419 let new_len = new_text.len() as i64;
12420 let adjusted_start =
12421 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12422 let adjusted_end =
12423 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12424
12425 new_selections.push(Selection {
12426 id: selection.id,
12427 start: adjusted_start,
12428 end: adjusted_end,
12429 reversed: selection.reversed,
12430 goal: selection.goal,
12431 });
12432
12433 offset_delta += new_len - old_len;
12434 (selection.start..selection.end, new_text.clone())
12435 })
12436 .collect();
12437 (edits, new_selections)
12438 } else {
12439 let mut all_rows: Vec<u32> = selections
12440 .iter()
12441 .map(|selection| buffer.offset_to_point(selection.start).row)
12442 .collect();
12443 all_rows.sort_unstable();
12444 all_rows.dedup();
12445
12446 if all_rows.len() < 2 {
12447 return;
12448 }
12449
12450 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12451 .iter()
12452 .map(|&row| {
12453 let start = Point::new(row, 0);
12454 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12455 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12456 })
12457 .collect();
12458
12459 let mut line_texts: Vec<String> = line_ranges
12460 .iter()
12461 .map(|range| buffer.text_for_range(range.clone()).collect())
12462 .collect();
12463
12464 if reverse {
12465 line_texts.rotate_left(1);
12466 } else {
12467 line_texts.rotate_right(1);
12468 }
12469
12470 let edits = line_ranges
12471 .iter()
12472 .zip(line_texts.iter())
12473 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12474 .collect();
12475
12476 let num_rows = all_rows.len();
12477 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12478 .iter()
12479 .enumerate()
12480 .map(|(i, &row)| (row, i))
12481 .collect();
12482
12483 // Compute new line start offsets after rotation (handles CRLF)
12484 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12485 let first_line_start = line_ranges[0].start.0;
12486 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12487 for text in line_texts.iter().take(num_rows - 1) {
12488 let prev_start = *new_line_starts.last().unwrap();
12489 new_line_starts.push(prev_start + text.len() + newline_len);
12490 }
12491
12492 let new_selections = selections
12493 .iter()
12494 .map(|selection| {
12495 let point = buffer.offset_to_point(selection.start);
12496 let old_index = row_to_index[&point.row];
12497 let new_index = if reverse {
12498 (old_index + num_rows - 1) % num_rows
12499 } else {
12500 (old_index + 1) % num_rows
12501 };
12502 let new_offset =
12503 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12504 Selection {
12505 id: selection.id,
12506 start: new_offset,
12507 end: new_offset,
12508 reversed: selection.reversed,
12509 goal: selection.goal,
12510 }
12511 })
12512 .collect();
12513
12514 (edits, new_selections)
12515 }
12516 };
12517
12518 self.transact(window, cx, |this, window, cx| {
12519 this.buffer.update(cx, |buffer, cx| {
12520 buffer.edit(edits, None, cx);
12521 });
12522 this.change_selections(Default::default(), window, cx, |s| {
12523 s.select(new_selections);
12524 });
12525 });
12526 }
12527
12528 fn manipulate_lines<M>(
12529 &mut self,
12530 window: &mut Window,
12531 cx: &mut Context<Self>,
12532 mut manipulate: M,
12533 ) where
12534 M: FnMut(&str) -> LineManipulationResult,
12535 {
12536 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12537
12538 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12539 let buffer = self.buffer.read(cx).snapshot(cx);
12540
12541 let mut edits = Vec::new();
12542
12543 let selections = self.selections.all::<Point>(&display_map);
12544 let mut selections = selections.iter().peekable();
12545 let mut contiguous_row_selections = Vec::new();
12546 let mut new_selections = Vec::new();
12547 let mut added_lines = 0;
12548 let mut removed_lines = 0;
12549
12550 while let Some(selection) = selections.next() {
12551 let (start_row, end_row) = consume_contiguous_rows(
12552 &mut contiguous_row_selections,
12553 selection,
12554 &display_map,
12555 &mut selections,
12556 );
12557
12558 let start_point = Point::new(start_row.0, 0);
12559 let end_point = Point::new(
12560 end_row.previous_row().0,
12561 buffer.line_len(end_row.previous_row()),
12562 );
12563 let text = buffer
12564 .text_for_range(start_point..end_point)
12565 .collect::<String>();
12566
12567 let LineManipulationResult {
12568 new_text,
12569 line_count_before,
12570 line_count_after,
12571 } = manipulate(&text);
12572
12573 edits.push((start_point..end_point, new_text));
12574
12575 // Selections must change based on added and removed line count
12576 let start_row =
12577 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12578 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12579 new_selections.push(Selection {
12580 id: selection.id,
12581 start: start_row,
12582 end: end_row,
12583 goal: SelectionGoal::None,
12584 reversed: selection.reversed,
12585 });
12586
12587 if line_count_after > line_count_before {
12588 added_lines += line_count_after - line_count_before;
12589 } else if line_count_before > line_count_after {
12590 removed_lines += line_count_before - line_count_after;
12591 }
12592 }
12593
12594 self.transact(window, cx, |this, window, cx| {
12595 let buffer = this.buffer.update(cx, |buffer, cx| {
12596 buffer.edit(edits, None, cx);
12597 buffer.snapshot(cx)
12598 });
12599
12600 // Recalculate offsets on newly edited buffer
12601 let new_selections = new_selections
12602 .iter()
12603 .map(|s| {
12604 let start_point = Point::new(s.start.0, 0);
12605 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12606 Selection {
12607 id: s.id,
12608 start: buffer.point_to_offset(start_point),
12609 end: buffer.point_to_offset(end_point),
12610 goal: s.goal,
12611 reversed: s.reversed,
12612 }
12613 })
12614 .collect();
12615
12616 this.change_selections(Default::default(), window, cx, |s| {
12617 s.select(new_selections);
12618 });
12619
12620 this.request_autoscroll(Autoscroll::fit(), cx);
12621 });
12622 }
12623
12624 fn manipulate_immutable_lines<Fn>(
12625 &mut self,
12626 window: &mut Window,
12627 cx: &mut Context<Self>,
12628 mut callback: Fn,
12629 ) where
12630 Fn: FnMut(&mut Vec<&str>),
12631 {
12632 self.manipulate_lines(window, cx, |text| {
12633 let mut lines: Vec<&str> = text.split('\n').collect();
12634 let line_count_before = lines.len();
12635
12636 callback(&mut lines);
12637
12638 LineManipulationResult {
12639 new_text: lines.join("\n"),
12640 line_count_before,
12641 line_count_after: lines.len(),
12642 }
12643 });
12644 }
12645
12646 fn manipulate_mutable_lines<Fn>(
12647 &mut self,
12648 window: &mut Window,
12649 cx: &mut Context<Self>,
12650 mut callback: Fn,
12651 ) where
12652 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12653 {
12654 self.manipulate_lines(window, cx, |text| {
12655 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12656 let line_count_before = lines.len();
12657
12658 callback(&mut lines);
12659
12660 LineManipulationResult {
12661 new_text: lines.join("\n"),
12662 line_count_before,
12663 line_count_after: lines.len(),
12664 }
12665 });
12666 }
12667
12668 pub fn convert_indentation_to_spaces(
12669 &mut self,
12670 _: &ConvertIndentationToSpaces,
12671 window: &mut Window,
12672 cx: &mut Context<Self>,
12673 ) {
12674 let settings = self.buffer.read(cx).language_settings(cx);
12675 let tab_size = settings.tab_size.get() as usize;
12676
12677 self.manipulate_mutable_lines(window, cx, |lines| {
12678 // Allocates a reasonably sized scratch buffer once for the whole loop
12679 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12680 // Avoids recomputing spaces that could be inserted many times
12681 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12682 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12683 .collect();
12684
12685 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12686 let mut chars = line.as_ref().chars();
12687 let mut col = 0;
12688 let mut changed = false;
12689
12690 for ch in chars.by_ref() {
12691 match ch {
12692 ' ' => {
12693 reindented_line.push(' ');
12694 col += 1;
12695 }
12696 '\t' => {
12697 // \t are converted to spaces depending on the current column
12698 let spaces_len = tab_size - (col % tab_size);
12699 reindented_line.extend(&space_cache[spaces_len - 1]);
12700 col += spaces_len;
12701 changed = true;
12702 }
12703 _ => {
12704 // If we dont append before break, the character is consumed
12705 reindented_line.push(ch);
12706 break;
12707 }
12708 }
12709 }
12710
12711 if !changed {
12712 reindented_line.clear();
12713 continue;
12714 }
12715 // Append the rest of the line and replace old reference with new one
12716 reindented_line.extend(chars);
12717 *line = Cow::Owned(reindented_line.clone());
12718 reindented_line.clear();
12719 }
12720 });
12721 }
12722
12723 pub fn convert_indentation_to_tabs(
12724 &mut self,
12725 _: &ConvertIndentationToTabs,
12726 window: &mut Window,
12727 cx: &mut Context<Self>,
12728 ) {
12729 let settings = self.buffer.read(cx).language_settings(cx);
12730 let tab_size = settings.tab_size.get() as usize;
12731
12732 self.manipulate_mutable_lines(window, cx, |lines| {
12733 // Allocates a reasonably sized buffer once for the whole loop
12734 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12735 // Avoids recomputing spaces that could be inserted many times
12736 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12737 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12738 .collect();
12739
12740 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12741 let mut chars = line.chars();
12742 let mut spaces_count = 0;
12743 let mut first_non_indent_char = None;
12744 let mut changed = false;
12745
12746 for ch in chars.by_ref() {
12747 match ch {
12748 ' ' => {
12749 // Keep track of spaces. Append \t when we reach tab_size
12750 spaces_count += 1;
12751 changed = true;
12752 if spaces_count == tab_size {
12753 reindented_line.push('\t');
12754 spaces_count = 0;
12755 }
12756 }
12757 '\t' => {
12758 reindented_line.push('\t');
12759 spaces_count = 0;
12760 }
12761 _ => {
12762 // Dont append it yet, we might have remaining spaces
12763 first_non_indent_char = Some(ch);
12764 break;
12765 }
12766 }
12767 }
12768
12769 if !changed {
12770 reindented_line.clear();
12771 continue;
12772 }
12773 // Remaining spaces that didn't make a full tab stop
12774 if spaces_count > 0 {
12775 reindented_line.extend(&space_cache[spaces_count - 1]);
12776 }
12777 // If we consume an extra character that was not indentation, add it back
12778 if let Some(extra_char) = first_non_indent_char {
12779 reindented_line.push(extra_char);
12780 }
12781 // Append the rest of the line and replace old reference with new one
12782 reindented_line.extend(chars);
12783 *line = Cow::Owned(reindented_line.clone());
12784 reindented_line.clear();
12785 }
12786 });
12787 }
12788
12789 pub fn convert_to_upper_case(
12790 &mut self,
12791 _: &ConvertToUpperCase,
12792 window: &mut Window,
12793 cx: &mut Context<Self>,
12794 ) {
12795 self.manipulate_text(window, cx, |text| text.to_uppercase())
12796 }
12797
12798 pub fn convert_to_lower_case(
12799 &mut self,
12800 _: &ConvertToLowerCase,
12801 window: &mut Window,
12802 cx: &mut Context<Self>,
12803 ) {
12804 self.manipulate_text(window, cx, |text| text.to_lowercase())
12805 }
12806
12807 pub fn convert_to_title_case(
12808 &mut self,
12809 _: &ConvertToTitleCase,
12810 window: &mut Window,
12811 cx: &mut Context<Self>,
12812 ) {
12813 self.manipulate_text(window, cx, |text| {
12814 Self::convert_text_case(text, Case::Title)
12815 })
12816 }
12817
12818 pub fn convert_to_snake_case(
12819 &mut self,
12820 _: &ConvertToSnakeCase,
12821 window: &mut Window,
12822 cx: &mut Context<Self>,
12823 ) {
12824 self.manipulate_text(window, cx, |text| {
12825 Self::convert_text_case(text, Case::Snake)
12826 })
12827 }
12828
12829 pub fn convert_to_kebab_case(
12830 &mut self,
12831 _: &ConvertToKebabCase,
12832 window: &mut Window,
12833 cx: &mut Context<Self>,
12834 ) {
12835 self.manipulate_text(window, cx, |text| {
12836 Self::convert_text_case(text, Case::Kebab)
12837 })
12838 }
12839
12840 pub fn convert_to_upper_camel_case(
12841 &mut self,
12842 _: &ConvertToUpperCamelCase,
12843 window: &mut Window,
12844 cx: &mut Context<Self>,
12845 ) {
12846 self.manipulate_text(window, cx, |text| {
12847 Self::convert_text_case(text, Case::UpperCamel)
12848 })
12849 }
12850
12851 pub fn convert_to_lower_camel_case(
12852 &mut self,
12853 _: &ConvertToLowerCamelCase,
12854 window: &mut Window,
12855 cx: &mut Context<Self>,
12856 ) {
12857 self.manipulate_text(window, cx, |text| {
12858 Self::convert_text_case(text, Case::Camel)
12859 })
12860 }
12861
12862 pub fn convert_to_opposite_case(
12863 &mut self,
12864 _: &ConvertToOppositeCase,
12865 window: &mut Window,
12866 cx: &mut Context<Self>,
12867 ) {
12868 self.manipulate_text(window, cx, |text| {
12869 text.chars()
12870 .fold(String::with_capacity(text.len()), |mut t, c| {
12871 if c.is_uppercase() {
12872 t.extend(c.to_lowercase());
12873 } else {
12874 t.extend(c.to_uppercase());
12875 }
12876 t
12877 })
12878 })
12879 }
12880
12881 pub fn convert_to_sentence_case(
12882 &mut self,
12883 _: &ConvertToSentenceCase,
12884 window: &mut Window,
12885 cx: &mut Context<Self>,
12886 ) {
12887 self.manipulate_text(window, cx, |text| {
12888 Self::convert_text_case(text, Case::Sentence)
12889 })
12890 }
12891
12892 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12893 self.manipulate_text(window, cx, |text| {
12894 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12895 if has_upper_case_characters {
12896 text.to_lowercase()
12897 } else {
12898 text.to_uppercase()
12899 }
12900 })
12901 }
12902
12903 pub fn convert_to_rot13(
12904 &mut self,
12905 _: &ConvertToRot13,
12906 window: &mut Window,
12907 cx: &mut Context<Self>,
12908 ) {
12909 self.manipulate_text(window, cx, |text| {
12910 text.chars()
12911 .map(|c| match c {
12912 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12913 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12914 _ => c,
12915 })
12916 .collect()
12917 })
12918 }
12919
12920 fn convert_text_case(text: &str, case: Case) -> String {
12921 text.lines()
12922 .map(|line| {
12923 let trimmed_start = line.trim_start();
12924 let leading = &line[..line.len() - trimmed_start.len()];
12925 let trimmed = trimmed_start.trim_end();
12926 let trailing = &trimmed_start[trimmed.len()..];
12927 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12928 })
12929 .join("\n")
12930 }
12931
12932 pub fn convert_to_rot47(
12933 &mut self,
12934 _: &ConvertToRot47,
12935 window: &mut Window,
12936 cx: &mut Context<Self>,
12937 ) {
12938 self.manipulate_text(window, cx, |text| {
12939 text.chars()
12940 .map(|c| {
12941 let code_point = c as u32;
12942 if code_point >= 33 && code_point <= 126 {
12943 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12944 }
12945 c
12946 })
12947 .collect()
12948 })
12949 }
12950
12951 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12952 where
12953 Fn: FnMut(&str) -> String,
12954 {
12955 let buffer = self.buffer.read(cx).snapshot(cx);
12956
12957 let mut new_selections = Vec::new();
12958 let mut edits = Vec::new();
12959 let mut selection_adjustment = 0isize;
12960
12961 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12962 let selection_is_empty = selection.is_empty();
12963
12964 let (start, end) = if selection_is_empty {
12965 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12966 (word_range.start, word_range.end)
12967 } else {
12968 (
12969 buffer.point_to_offset(selection.start),
12970 buffer.point_to_offset(selection.end),
12971 )
12972 };
12973
12974 let text = buffer.text_for_range(start..end).collect::<String>();
12975 let old_length = text.len() as isize;
12976 let text = callback(&text);
12977
12978 new_selections.push(Selection {
12979 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12980 end: MultiBufferOffset(
12981 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12982 ),
12983 goal: SelectionGoal::None,
12984 id: selection.id,
12985 reversed: selection.reversed,
12986 });
12987
12988 selection_adjustment += old_length - text.len() as isize;
12989
12990 edits.push((start..end, text));
12991 }
12992
12993 self.transact(window, cx, |this, window, cx| {
12994 this.buffer.update(cx, |buffer, cx| {
12995 buffer.edit(edits, None, cx);
12996 });
12997
12998 this.change_selections(Default::default(), window, cx, |s| {
12999 s.select(new_selections);
13000 });
13001
13002 this.request_autoscroll(Autoscroll::fit(), cx);
13003 });
13004 }
13005
13006 pub fn move_selection_on_drop(
13007 &mut self,
13008 selection: &Selection<Anchor>,
13009 target: DisplayPoint,
13010 is_cut: bool,
13011 window: &mut Window,
13012 cx: &mut Context<Self>,
13013 ) {
13014 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13015 let buffer = display_map.buffer_snapshot();
13016 let mut edits = Vec::new();
13017 let insert_point = display_map
13018 .clip_point(target, Bias::Left)
13019 .to_point(&display_map);
13020 let text = buffer
13021 .text_for_range(selection.start..selection.end)
13022 .collect::<String>();
13023 if is_cut {
13024 edits.push(((selection.start..selection.end), String::new()));
13025 }
13026 let insert_anchor = buffer.anchor_before(insert_point);
13027 edits.push(((insert_anchor..insert_anchor), text));
13028 let last_edit_start = insert_anchor.bias_left(buffer);
13029 let last_edit_end = insert_anchor.bias_right(buffer);
13030 self.transact(window, cx, |this, window, cx| {
13031 this.buffer.update(cx, |buffer, cx| {
13032 buffer.edit(edits, None, cx);
13033 });
13034 this.change_selections(Default::default(), window, cx, |s| {
13035 s.select_anchor_ranges([last_edit_start..last_edit_end]);
13036 });
13037 });
13038 }
13039
13040 pub fn clear_selection_drag_state(&mut self) {
13041 self.selection_drag_state = SelectionDragState::None;
13042 }
13043
13044 pub fn duplicate(
13045 &mut self,
13046 upwards: bool,
13047 whole_lines: bool,
13048 window: &mut Window,
13049 cx: &mut Context<Self>,
13050 ) {
13051 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13052
13053 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13054 let buffer = display_map.buffer_snapshot();
13055 let selections = self.selections.all::<Point>(&display_map);
13056
13057 let mut edits = Vec::new();
13058 let mut selections_iter = selections.iter().peekable();
13059 while let Some(selection) = selections_iter.next() {
13060 let mut rows = selection.spanned_rows(false, &display_map);
13061 // duplicate line-wise
13062 if whole_lines || selection.start == selection.end {
13063 // Avoid duplicating the same lines twice.
13064 while let Some(next_selection) = selections_iter.peek() {
13065 let next_rows = next_selection.spanned_rows(false, &display_map);
13066 if next_rows.start < rows.end {
13067 rows.end = next_rows.end;
13068 selections_iter.next().unwrap();
13069 } else {
13070 break;
13071 }
13072 }
13073
13074 // Copy the text from the selected row region and splice it either at the start
13075 // or end of the region.
13076 let start = Point::new(rows.start.0, 0);
13077 let end = Point::new(
13078 rows.end.previous_row().0,
13079 buffer.line_len(rows.end.previous_row()),
13080 );
13081
13082 let mut text = buffer.text_for_range(start..end).collect::<String>();
13083
13084 let insert_location = if upwards {
13085 // When duplicating upward, we need to insert before the current line.
13086 // If we're on the last line and it doesn't end with a newline,
13087 // we need to add a newline before the duplicated content.
13088 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
13089 && buffer.max_point().column > 0
13090 && !text.ends_with('\n');
13091
13092 if needs_leading_newline {
13093 text.insert(0, '\n');
13094 end
13095 } else {
13096 text.push('\n');
13097 Point::new(rows.start.0, 0)
13098 }
13099 } else {
13100 text.push('\n');
13101 start
13102 };
13103 edits.push((insert_location..insert_location, text));
13104 } else {
13105 // duplicate character-wise
13106 let start = selection.start;
13107 let end = selection.end;
13108 let text = buffer.text_for_range(start..end).collect::<String>();
13109 edits.push((selection.end..selection.end, text));
13110 }
13111 }
13112
13113 self.transact(window, cx, |this, window, cx| {
13114 this.buffer.update(cx, |buffer, cx| {
13115 buffer.edit(edits, None, cx);
13116 });
13117
13118 // When duplicating upward with whole lines, move the cursor to the duplicated line
13119 if upwards && whole_lines {
13120 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
13121
13122 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13123 let mut new_ranges = Vec::new();
13124 let selections = s.all::<Point>(&display_map);
13125 let mut selections_iter = selections.iter().peekable();
13126
13127 while let Some(first_selection) = selections_iter.next() {
13128 // Group contiguous selections together to find the total row span
13129 let mut group_selections = vec![first_selection];
13130 let mut rows = first_selection.spanned_rows(false, &display_map);
13131
13132 while let Some(next_selection) = selections_iter.peek() {
13133 let next_rows = next_selection.spanned_rows(false, &display_map);
13134 if next_rows.start < rows.end {
13135 rows.end = next_rows.end;
13136 group_selections.push(selections_iter.next().unwrap());
13137 } else {
13138 break;
13139 }
13140 }
13141
13142 let row_count = rows.end.0 - rows.start.0;
13143
13144 // Move all selections in this group up by the total number of duplicated rows
13145 for selection in group_selections {
13146 let new_start = Point::new(
13147 selection.start.row.saturating_sub(row_count),
13148 selection.start.column,
13149 );
13150
13151 let new_end = Point::new(
13152 selection.end.row.saturating_sub(row_count),
13153 selection.end.column,
13154 );
13155
13156 new_ranges.push(new_start..new_end);
13157 }
13158 }
13159
13160 s.select_ranges(new_ranges);
13161 });
13162 }
13163
13164 this.request_autoscroll(Autoscroll::fit(), cx);
13165 });
13166 }
13167
13168 pub fn duplicate_line_up(
13169 &mut self,
13170 _: &DuplicateLineUp,
13171 window: &mut Window,
13172 cx: &mut Context<Self>,
13173 ) {
13174 self.duplicate(true, true, window, cx);
13175 }
13176
13177 pub fn duplicate_line_down(
13178 &mut self,
13179 _: &DuplicateLineDown,
13180 window: &mut Window,
13181 cx: &mut Context<Self>,
13182 ) {
13183 self.duplicate(false, true, window, cx);
13184 }
13185
13186 pub fn duplicate_selection(
13187 &mut self,
13188 _: &DuplicateSelection,
13189 window: &mut Window,
13190 cx: &mut Context<Self>,
13191 ) {
13192 self.duplicate(false, false, window, cx);
13193 }
13194
13195 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13196 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13197 if self.mode.is_single_line() {
13198 cx.propagate();
13199 return;
13200 }
13201
13202 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13203 let buffer = self.buffer.read(cx).snapshot(cx);
13204
13205 let mut edits = Vec::new();
13206 let mut unfold_ranges = Vec::new();
13207 let mut refold_creases = Vec::new();
13208
13209 let selections = self.selections.all::<Point>(&display_map);
13210 let mut selections = selections.iter().peekable();
13211 let mut contiguous_row_selections = Vec::new();
13212 let mut new_selections = Vec::new();
13213
13214 while let Some(selection) = selections.next() {
13215 // Find all the selections that span a contiguous row range
13216 let (start_row, end_row) = consume_contiguous_rows(
13217 &mut contiguous_row_selections,
13218 selection,
13219 &display_map,
13220 &mut selections,
13221 );
13222
13223 // Move the text spanned by the row range to be before the line preceding the row range
13224 if start_row.0 > 0 {
13225 let range_to_move = Point::new(
13226 start_row.previous_row().0,
13227 buffer.line_len(start_row.previous_row()),
13228 )
13229 ..Point::new(
13230 end_row.previous_row().0,
13231 buffer.line_len(end_row.previous_row()),
13232 );
13233 let insertion_point = display_map
13234 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13235 .0;
13236
13237 // Don't move lines across excerpts
13238 if buffer
13239 .excerpt_containing(insertion_point..range_to_move.end)
13240 .is_some()
13241 {
13242 let text = buffer
13243 .text_for_range(range_to_move.clone())
13244 .flat_map(|s| s.chars())
13245 .skip(1)
13246 .chain(['\n'])
13247 .collect::<String>();
13248
13249 edits.push((
13250 buffer.anchor_after(range_to_move.start)
13251 ..buffer.anchor_before(range_to_move.end),
13252 String::new(),
13253 ));
13254 let insertion_anchor = buffer.anchor_after(insertion_point);
13255 edits.push((insertion_anchor..insertion_anchor, text));
13256
13257 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13258
13259 // Move selections up
13260 new_selections.extend(contiguous_row_selections.drain(..).map(
13261 |mut selection| {
13262 selection.start.row -= row_delta;
13263 selection.end.row -= row_delta;
13264 selection
13265 },
13266 ));
13267
13268 // Move folds up
13269 unfold_ranges.push(range_to_move.clone());
13270 for fold in display_map.folds_in_range(
13271 buffer.anchor_before(range_to_move.start)
13272 ..buffer.anchor_after(range_to_move.end),
13273 ) {
13274 let mut start = fold.range.start.to_point(&buffer);
13275 let mut end = fold.range.end.to_point(&buffer);
13276 start.row -= row_delta;
13277 end.row -= row_delta;
13278 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13279 }
13280 }
13281 }
13282
13283 // If we didn't move line(s), preserve the existing selections
13284 new_selections.append(&mut contiguous_row_selections);
13285 }
13286
13287 self.transact(window, cx, |this, window, cx| {
13288 this.unfold_ranges(&unfold_ranges, true, true, cx);
13289 this.buffer.update(cx, |buffer, cx| {
13290 for (range, text) in edits {
13291 buffer.edit([(range, text)], None, cx);
13292 }
13293 });
13294 this.fold_creases(refold_creases, true, window, cx);
13295 this.change_selections(Default::default(), window, cx, |s| {
13296 s.select(new_selections);
13297 })
13298 });
13299 }
13300
13301 pub fn move_line_down(
13302 &mut self,
13303 _: &MoveLineDown,
13304 window: &mut Window,
13305 cx: &mut Context<Self>,
13306 ) {
13307 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13308 if self.mode.is_single_line() {
13309 cx.propagate();
13310 return;
13311 }
13312
13313 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13314 let buffer = self.buffer.read(cx).snapshot(cx);
13315
13316 let mut edits = Vec::new();
13317 let mut unfold_ranges = Vec::new();
13318 let mut refold_creases = Vec::new();
13319
13320 let selections = self.selections.all::<Point>(&display_map);
13321 let mut selections = selections.iter().peekable();
13322 let mut contiguous_row_selections = Vec::new();
13323 let mut new_selections = Vec::new();
13324
13325 while let Some(selection) = selections.next() {
13326 // Find all the selections that span a contiguous row range
13327 let (start_row, end_row) = consume_contiguous_rows(
13328 &mut contiguous_row_selections,
13329 selection,
13330 &display_map,
13331 &mut selections,
13332 );
13333
13334 // Move the text spanned by the row range to be after the last line of the row range
13335 if end_row.0 <= buffer.max_point().row {
13336 let range_to_move =
13337 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13338 let insertion_point = display_map
13339 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13340 .0;
13341
13342 // Don't move lines across excerpt boundaries
13343 if buffer
13344 .excerpt_containing(range_to_move.start..insertion_point)
13345 .is_some()
13346 {
13347 let mut text = String::from("\n");
13348 text.extend(buffer.text_for_range(range_to_move.clone()));
13349 text.pop(); // Drop trailing newline
13350 edits.push((
13351 buffer.anchor_after(range_to_move.start)
13352 ..buffer.anchor_before(range_to_move.end),
13353 String::new(),
13354 ));
13355 let insertion_anchor = buffer.anchor_after(insertion_point);
13356 edits.push((insertion_anchor..insertion_anchor, text));
13357
13358 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13359
13360 // Move selections down
13361 new_selections.extend(contiguous_row_selections.drain(..).map(
13362 |mut selection| {
13363 selection.start.row += row_delta;
13364 selection.end.row += row_delta;
13365 selection
13366 },
13367 ));
13368
13369 // Move folds down
13370 unfold_ranges.push(range_to_move.clone());
13371 for fold in display_map.folds_in_range(
13372 buffer.anchor_before(range_to_move.start)
13373 ..buffer.anchor_after(range_to_move.end),
13374 ) {
13375 let mut start = fold.range.start.to_point(&buffer);
13376 let mut end = fold.range.end.to_point(&buffer);
13377 start.row += row_delta;
13378 end.row += row_delta;
13379 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13380 }
13381 }
13382 }
13383
13384 // If we didn't move line(s), preserve the existing selections
13385 new_selections.append(&mut contiguous_row_selections);
13386 }
13387
13388 self.transact(window, cx, |this, window, cx| {
13389 this.unfold_ranges(&unfold_ranges, true, true, cx);
13390 this.buffer.update(cx, |buffer, cx| {
13391 for (range, text) in edits {
13392 buffer.edit([(range, text)], None, cx);
13393 }
13394 });
13395 this.fold_creases(refold_creases, true, window, cx);
13396 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13397 });
13398 }
13399
13400 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13401 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13402 let text_layout_details = &self.text_layout_details(window, cx);
13403 self.transact(window, cx, |this, window, cx| {
13404 let edits = this.change_selections(Default::default(), window, cx, |s| {
13405 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13406 s.move_with(&mut |display_map, selection| {
13407 if !selection.is_empty() {
13408 return;
13409 }
13410
13411 let mut head = selection.head();
13412 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13413 if head.column() == display_map.line_len(head.row()) {
13414 transpose_offset = display_map
13415 .buffer_snapshot()
13416 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13417 }
13418
13419 if transpose_offset == MultiBufferOffset(0) {
13420 return;
13421 }
13422
13423 *head.column_mut() += 1;
13424 head = display_map.clip_point(head, Bias::Right);
13425 let goal = SelectionGoal::HorizontalPosition(
13426 display_map
13427 .x_for_display_point(head, text_layout_details)
13428 .into(),
13429 );
13430 selection.collapse_to(head, goal);
13431
13432 let transpose_start = display_map
13433 .buffer_snapshot()
13434 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13435 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13436 let transpose_end = display_map
13437 .buffer_snapshot()
13438 .clip_offset(transpose_offset + 1usize, Bias::Right);
13439 if let Some(ch) = display_map
13440 .buffer_snapshot()
13441 .chars_at(transpose_start)
13442 .next()
13443 {
13444 edits.push((transpose_start..transpose_offset, String::new()));
13445 edits.push((transpose_end..transpose_end, ch.to_string()));
13446 }
13447 }
13448 });
13449 edits
13450 });
13451 this.buffer
13452 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13453 let selections = this
13454 .selections
13455 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13456 this.change_selections(Default::default(), window, cx, |s| {
13457 s.select(selections);
13458 });
13459 });
13460 }
13461
13462 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13463 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13464 if self.mode.is_single_line() {
13465 cx.propagate();
13466 return;
13467 }
13468
13469 self.rewrap_impl(RewrapOptions::default(), cx)
13470 }
13471
13472 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13473 let buffer = self.buffer.read(cx).snapshot(cx);
13474 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13475
13476 #[derive(Clone, Debug, PartialEq)]
13477 enum CommentFormat {
13478 /// single line comment, with prefix for line
13479 Line(String),
13480 /// single line within a block comment, with prefix for line
13481 BlockLine(String),
13482 /// a single line of a block comment that includes the initial delimiter
13483 BlockCommentWithStart(BlockCommentConfig),
13484 /// a single line of a block comment that includes the ending delimiter
13485 BlockCommentWithEnd(BlockCommentConfig),
13486 }
13487
13488 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13489 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13490 let language_settings = buffer.language_settings_at(selection.head(), cx);
13491 let language_scope = buffer.language_scope_at(selection.head());
13492
13493 let indent_and_prefix_for_row =
13494 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13495 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13496 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13497 &language_scope
13498 {
13499 let indent_end = Point::new(row, indent.len);
13500 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13501 let line_text_after_indent = buffer
13502 .text_for_range(indent_end..line_end)
13503 .collect::<String>();
13504
13505 let is_within_comment_override = buffer
13506 .language_scope_at(indent_end)
13507 .is_some_and(|scope| scope.override_name() == Some("comment"));
13508 let comment_delimiters = if is_within_comment_override {
13509 // we are within a comment syntax node, but we don't
13510 // yet know what kind of comment: block, doc or line
13511 match (
13512 language_scope.documentation_comment(),
13513 language_scope.block_comment(),
13514 ) {
13515 (Some(config), _) | (_, Some(config))
13516 if buffer.contains_str_at(indent_end, &config.start) =>
13517 {
13518 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13519 }
13520 (Some(config), _) | (_, Some(config))
13521 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13522 {
13523 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13524 }
13525 (Some(config), _) | (_, Some(config))
13526 if buffer.contains_str_at(indent_end, &config.prefix) =>
13527 {
13528 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13529 }
13530 (_, _) => language_scope
13531 .line_comment_prefixes()
13532 .iter()
13533 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13534 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13535 }
13536 } else {
13537 // we not in an overridden comment node, but we may
13538 // be within a non-overridden line comment node
13539 language_scope
13540 .line_comment_prefixes()
13541 .iter()
13542 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13543 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13544 };
13545
13546 let rewrap_prefix = language_scope
13547 .rewrap_prefixes()
13548 .iter()
13549 .find_map(|prefix_regex| {
13550 prefix_regex.find(&line_text_after_indent).map(|mat| {
13551 if mat.start() == 0 {
13552 Some(mat.as_str().to_string())
13553 } else {
13554 None
13555 }
13556 })
13557 })
13558 .flatten();
13559 (comment_delimiters, rewrap_prefix)
13560 } else {
13561 (None, None)
13562 };
13563 (indent, comment_prefix, rewrap_prefix)
13564 };
13565
13566 let mut start_row = selection.start.row;
13567 let mut end_row = selection.end.row;
13568
13569 if selection.is_empty() {
13570 let cursor_row = selection.start.row;
13571
13572 let (mut indent_size, comment_prefix, _) = indent_and_prefix_for_row(cursor_row);
13573 let line_prefix = match &comment_prefix {
13574 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13575 Some(prefix.as_str())
13576 }
13577 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13578 prefix, ..
13579 })) => Some(prefix.as_ref()),
13580 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13581 start: _,
13582 end: _,
13583 prefix,
13584 tab_size,
13585 })) => {
13586 indent_size.len += tab_size;
13587 Some(prefix.as_ref())
13588 }
13589 None => None,
13590 };
13591 let indent_prefix = indent_size.chars().collect::<String>();
13592 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13593
13594 'expand_upwards: while start_row > 0 {
13595 let prev_row = start_row - 1;
13596 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13597 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13598 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13599 {
13600 start_row = prev_row;
13601 } else {
13602 break 'expand_upwards;
13603 }
13604 }
13605
13606 'expand_downwards: while end_row < buffer.max_point().row {
13607 let next_row = end_row + 1;
13608 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13609 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13610 && !buffer.is_line_blank(MultiBufferRow(next_row))
13611 {
13612 end_row = next_row;
13613 } else {
13614 break 'expand_downwards;
13615 }
13616 }
13617 }
13618
13619 let mut non_blank_rows_iter = (start_row..=end_row)
13620 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13621 .peekable();
13622
13623 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13624 row
13625 } else {
13626 return Vec::new();
13627 };
13628
13629 let mut ranges = Vec::new();
13630
13631 let mut current_range_start = first_row;
13632 let mut prev_row = first_row;
13633 let (
13634 mut current_range_indent,
13635 mut current_range_comment_delimiters,
13636 mut current_range_rewrap_prefix,
13637 ) = indent_and_prefix_for_row(first_row);
13638
13639 for row in non_blank_rows_iter.skip(1) {
13640 let has_paragraph_break = row > prev_row + 1;
13641
13642 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13643 indent_and_prefix_for_row(row);
13644
13645 let has_indent_change = row_indent != current_range_indent;
13646 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13647
13648 let has_boundary_change = has_comment_change
13649 || row_rewrap_prefix.is_some()
13650 || (has_indent_change && current_range_comment_delimiters.is_some());
13651
13652 if has_paragraph_break || has_boundary_change {
13653 ranges.push((
13654 language_settings.clone(),
13655 Point::new(current_range_start, 0)
13656 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13657 current_range_indent,
13658 current_range_comment_delimiters.clone(),
13659 current_range_rewrap_prefix.clone(),
13660 ));
13661 current_range_start = row;
13662 current_range_indent = row_indent;
13663 current_range_comment_delimiters = row_comment_delimiters;
13664 current_range_rewrap_prefix = row_rewrap_prefix;
13665 }
13666 prev_row = row;
13667 }
13668
13669 ranges.push((
13670 language_settings.clone(),
13671 Point::new(current_range_start, 0)
13672 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13673 current_range_indent,
13674 current_range_comment_delimiters,
13675 current_range_rewrap_prefix,
13676 ));
13677
13678 ranges
13679 });
13680
13681 let mut edits = Vec::new();
13682 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13683
13684 for (language_settings, wrap_range, mut indent_size, comment_prefix, rewrap_prefix) in
13685 wrap_ranges
13686 {
13687 let start_row = wrap_range.start.row;
13688 let end_row = wrap_range.end.row;
13689
13690 // Skip selections that overlap with a range that has already been rewrapped.
13691 let selection_range = start_row..end_row;
13692 if rewrapped_row_ranges
13693 .iter()
13694 .any(|range| range.overlaps(&selection_range))
13695 {
13696 continue;
13697 }
13698
13699 let tab_size = language_settings.tab_size;
13700
13701 let (line_prefix, inside_comment) = match &comment_prefix {
13702 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13703 (Some(prefix.as_str()), true)
13704 }
13705 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13706 (Some(prefix.as_ref()), true)
13707 }
13708 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13709 start: _,
13710 end: _,
13711 prefix,
13712 tab_size,
13713 })) => {
13714 indent_size.len += tab_size;
13715 (Some(prefix.as_ref()), true)
13716 }
13717 None => (None, false),
13718 };
13719 let indent_prefix = indent_size.chars().collect::<String>();
13720 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13721
13722 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13723 RewrapBehavior::InComments => inside_comment,
13724 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13725 RewrapBehavior::Anywhere => true,
13726 };
13727
13728 let should_rewrap = options.override_language_settings
13729 || allow_rewrap_based_on_language
13730 || self.hard_wrap.is_some();
13731 if !should_rewrap {
13732 continue;
13733 }
13734
13735 let start = Point::new(start_row, 0);
13736 let start_offset = ToOffset::to_offset(&start, &buffer);
13737 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13738 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13739 let mut first_line_delimiter = None;
13740 let mut last_line_delimiter = None;
13741 let Some(lines_without_prefixes) = selection_text
13742 .lines()
13743 .enumerate()
13744 .map(|(ix, line)| {
13745 let line_trimmed = line.trim_start();
13746 if rewrap_prefix.is_some() && ix > 0 {
13747 Ok(line_trimmed)
13748 } else if let Some(
13749 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13750 start,
13751 prefix,
13752 end,
13753 tab_size,
13754 })
13755 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13756 start,
13757 prefix,
13758 end,
13759 tab_size,
13760 }),
13761 ) = &comment_prefix
13762 {
13763 let line_trimmed = line_trimmed
13764 .strip_prefix(start.as_ref())
13765 .map(|s| {
13766 let mut indent_size = indent_size;
13767 indent_size.len -= tab_size;
13768 let indent_prefix: String = indent_size.chars().collect();
13769 first_line_delimiter = Some((indent_prefix, start));
13770 s.trim_start()
13771 })
13772 .unwrap_or(line_trimmed);
13773 let line_trimmed = line_trimmed
13774 .strip_suffix(end.as_ref())
13775 .map(|s| {
13776 last_line_delimiter = Some(end);
13777 s.trim_end()
13778 })
13779 .unwrap_or(line_trimmed);
13780 let line_trimmed = line_trimmed
13781 .strip_prefix(prefix.as_ref())
13782 .unwrap_or(line_trimmed);
13783 Ok(line_trimmed)
13784 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13785 line_trimmed.strip_prefix(prefix).with_context(|| {
13786 format!("line did not start with prefix {prefix:?}: {line:?}")
13787 })
13788 } else {
13789 line_trimmed
13790 .strip_prefix(&line_prefix.trim_start())
13791 .with_context(|| {
13792 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13793 })
13794 }
13795 })
13796 .collect::<Result<Vec<_>, _>>()
13797 .log_err()
13798 else {
13799 continue;
13800 };
13801
13802 let wrap_column = options.line_length.or(self.hard_wrap).unwrap_or_else(|| {
13803 buffer
13804 .language_settings_at(Point::new(start_row, 0), cx)
13805 .preferred_line_length as usize
13806 });
13807
13808 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13809 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13810 } else {
13811 line_prefix.clone()
13812 };
13813
13814 let wrapped_text = {
13815 let mut wrapped_text = wrap_with_prefix(
13816 line_prefix,
13817 subsequent_lines_prefix,
13818 lines_without_prefixes.join("\n"),
13819 wrap_column,
13820 tab_size,
13821 options.preserve_existing_whitespace,
13822 );
13823
13824 if let Some((indent, delimiter)) = first_line_delimiter {
13825 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13826 }
13827 if let Some(last_line) = last_line_delimiter {
13828 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13829 }
13830
13831 wrapped_text
13832 };
13833
13834 // TODO: should always use char-based diff while still supporting cursor behavior that
13835 // matches vim.
13836 let mut diff_options = DiffOptions::default();
13837 if options.override_language_settings {
13838 diff_options.max_word_diff_len = 0;
13839 diff_options.max_word_diff_line_count = 0;
13840 } else {
13841 diff_options.max_word_diff_len = usize::MAX;
13842 diff_options.max_word_diff_line_count = usize::MAX;
13843 }
13844
13845 for (old_range, new_text) in
13846 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13847 {
13848 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13849 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13850 edits.push((edit_start..edit_end, new_text));
13851 }
13852
13853 rewrapped_row_ranges.push(start_row..=end_row);
13854 }
13855
13856 self.buffer
13857 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13858 }
13859
13860 pub fn cut_common(
13861 &mut self,
13862 cut_no_selection_line: bool,
13863 window: &mut Window,
13864 cx: &mut Context<Self>,
13865 ) -> ClipboardItem {
13866 let mut text = String::new();
13867 let buffer = self.buffer.read(cx).snapshot(cx);
13868 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13869 let mut clipboard_selections = Vec::with_capacity(selections.len());
13870 {
13871 let max_point = buffer.max_point();
13872 let mut is_first = true;
13873 let mut prev_selection_was_entire_line = false;
13874 for selection in &mut selections {
13875 let is_entire_line =
13876 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13877 if is_entire_line {
13878 selection.start = Point::new(selection.start.row, 0);
13879 if !selection.is_empty() && selection.end.column == 0 {
13880 selection.end = cmp::min(max_point, selection.end);
13881 } else {
13882 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13883 }
13884 selection.goal = SelectionGoal::None;
13885 }
13886 if is_first {
13887 is_first = false;
13888 } else if !prev_selection_was_entire_line {
13889 text += "\n";
13890 }
13891 prev_selection_was_entire_line = is_entire_line;
13892 let mut len = 0;
13893 for chunk in buffer.text_for_range(selection.start..selection.end) {
13894 text.push_str(chunk);
13895 len += chunk.len();
13896 }
13897
13898 clipboard_selections.push(ClipboardSelection::for_buffer(
13899 len,
13900 is_entire_line,
13901 selection.range(),
13902 &buffer,
13903 self.project.as_ref(),
13904 cx,
13905 ));
13906 }
13907 }
13908
13909 self.transact(window, cx, |this, window, cx| {
13910 this.change_selections(Default::default(), window, cx, |s| {
13911 s.select(selections);
13912 });
13913 this.insert("", window, cx);
13914 });
13915 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13916 }
13917
13918 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13920 let item = self.cut_common(true, window, cx);
13921 cx.write_to_clipboard(item);
13922 }
13923
13924 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13925 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13926 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13927 s.move_with(&mut |snapshot, sel| {
13928 if sel.is_empty() {
13929 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13930 }
13931 if sel.is_empty() {
13932 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13933 }
13934 });
13935 });
13936 let item = self.cut_common(false, window, cx);
13937 cx.set_global(KillRing(item))
13938 }
13939
13940 pub fn kill_ring_yank(
13941 &mut self,
13942 _: &KillRingYank,
13943 window: &mut Window,
13944 cx: &mut Context<Self>,
13945 ) {
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13947 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13948 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13949 (kill_ring.text().to_string(), kill_ring.metadata_json())
13950 } else {
13951 return;
13952 }
13953 } else {
13954 return;
13955 };
13956 self.do_paste(&text, metadata, false, window, cx);
13957 }
13958
13959 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13960 self.do_copy(true, cx);
13961 }
13962
13963 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13964 self.do_copy(false, cx);
13965 }
13966
13967 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13968 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13969 let buffer = self.buffer.read(cx).read(cx);
13970 let mut text = String::new();
13971 let mut clipboard_selections = Vec::with_capacity(selections.len());
13972
13973 let max_point = buffer.max_point();
13974 let mut is_first = true;
13975 for selection in &selections {
13976 let mut start = selection.start;
13977 let mut end = selection.end;
13978 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13979 let mut add_trailing_newline = false;
13980 if is_entire_line {
13981 start = Point::new(start.row, 0);
13982 let next_line_start = Point::new(end.row + 1, 0);
13983 if next_line_start <= max_point {
13984 end = next_line_start;
13985 } else {
13986 // We're on the last line without a trailing newline.
13987 // Copy to the end of the line and add a newline afterwards.
13988 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13989 add_trailing_newline = true;
13990 }
13991 }
13992
13993 let mut trimmed_selections = Vec::new();
13994 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13995 let row = MultiBufferRow(start.row);
13996 let first_indent = buffer.indent_size_for_line(row);
13997 if first_indent.len == 0 || start.column > first_indent.len {
13998 trimmed_selections.push(start..end);
13999 } else {
14000 trimmed_selections.push(
14001 Point::new(row.0, first_indent.len)
14002 ..Point::new(row.0, buffer.line_len(row)),
14003 );
14004 for row in start.row + 1..=end.row {
14005 let mut line_len = buffer.line_len(MultiBufferRow(row));
14006 if row == end.row {
14007 line_len = end.column;
14008 }
14009 if line_len == 0 {
14010 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
14011 continue;
14012 }
14013 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
14014 if row_indent_size.len >= first_indent.len {
14015 trimmed_selections
14016 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
14017 } else {
14018 trimmed_selections.clear();
14019 trimmed_selections.push(start..end);
14020 break;
14021 }
14022 }
14023 }
14024 } else {
14025 trimmed_selections.push(start..end);
14026 }
14027
14028 let is_multiline_trim = trimmed_selections.len() > 1;
14029 let mut selection_len: usize = 0;
14030 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
14031
14032 for trimmed_range in trimmed_selections {
14033 if is_first {
14034 is_first = false;
14035 } else if is_multiline_trim || !prev_selection_was_entire_line {
14036 text.push('\n');
14037 if is_multiline_trim {
14038 selection_len += 1;
14039 }
14040 }
14041 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
14042 text.push_str(chunk);
14043 selection_len += chunk.len();
14044 }
14045 if add_trailing_newline {
14046 text.push('\n');
14047 selection_len += 1;
14048 }
14049 }
14050
14051 clipboard_selections.push(ClipboardSelection::for_buffer(
14052 selection_len,
14053 is_entire_line,
14054 start..end,
14055 &buffer,
14056 self.project.as_ref(),
14057 cx,
14058 ));
14059 }
14060
14061 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
14062 text,
14063 clipboard_selections,
14064 ));
14065 }
14066
14067 pub fn do_paste(
14068 &mut self,
14069 text: &String,
14070 clipboard_selections: Option<Vec<ClipboardSelection>>,
14071 handle_entire_lines: bool,
14072 window: &mut Window,
14073 cx: &mut Context<Self>,
14074 ) {
14075 if self.read_only(cx) {
14076 return;
14077 }
14078
14079 self.finalize_last_transaction(cx);
14080
14081 let clipboard_text = Cow::Borrowed(text.as_str());
14082
14083 self.transact(window, cx, |this, window, cx| {
14084 let had_active_edit_prediction = this.has_active_edit_prediction();
14085 let display_map = this.display_snapshot(cx);
14086 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
14087 let cursor_offset = this
14088 .selections
14089 .last::<MultiBufferOffset>(&display_map)
14090 .head();
14091
14092 if let Some(mut clipboard_selections) = clipboard_selections {
14093 let all_selections_were_entire_line =
14094 clipboard_selections.iter().all(|s| s.is_entire_line);
14095 let first_selection_indent_column =
14096 clipboard_selections.first().map(|s| s.first_line_indent);
14097 if clipboard_selections.len() != old_selections.len() {
14098 clipboard_selections.drain(..);
14099 }
14100 let mut auto_indent_on_paste = true;
14101
14102 this.buffer.update(cx, |buffer, cx| {
14103 let snapshot = buffer.read(cx);
14104 auto_indent_on_paste = snapshot
14105 .language_settings_at(cursor_offset, cx)
14106 .auto_indent_on_paste;
14107
14108 let mut start_offset = 0;
14109 let mut edits = Vec::new();
14110 let mut original_indent_columns = Vec::new();
14111 for (ix, selection) in old_selections.iter().enumerate() {
14112 let to_insert;
14113 let entire_line;
14114 let original_indent_column;
14115 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
14116 let end_offset = start_offset + clipboard_selection.len;
14117 to_insert = &clipboard_text[start_offset..end_offset];
14118 entire_line = clipboard_selection.is_entire_line;
14119 start_offset = if entire_line {
14120 end_offset
14121 } else {
14122 end_offset + 1
14123 };
14124 original_indent_column = Some(clipboard_selection.first_line_indent);
14125 } else {
14126 to_insert = &*clipboard_text;
14127 entire_line = all_selections_were_entire_line;
14128 original_indent_column = first_selection_indent_column
14129 }
14130
14131 let (range, to_insert) =
14132 if selection.is_empty() && handle_entire_lines && entire_line {
14133 // If the corresponding selection was empty when this slice of the
14134 // clipboard text was written, then the entire line containing the
14135 // selection was copied. If this selection is also currently empty,
14136 // then paste the line before the current line of the buffer.
14137 let column = selection.start.to_point(&snapshot).column as usize;
14138 let line_start = selection.start - column;
14139 (line_start..line_start, Cow::Borrowed(to_insert))
14140 } else {
14141 let language = snapshot.language_at(selection.head());
14142 let range = selection.range();
14143 if let Some(language) = language
14144 && language.name() == "Markdown"
14145 {
14146 edit_for_markdown_paste(
14147 &snapshot,
14148 range,
14149 to_insert,
14150 url::Url::parse(to_insert).ok(),
14151 )
14152 } else {
14153 (range, Cow::Borrowed(to_insert))
14154 }
14155 };
14156
14157 edits.push((range, to_insert));
14158 original_indent_columns.push(original_indent_column);
14159 }
14160 drop(snapshot);
14161
14162 buffer.edit(
14163 edits,
14164 if auto_indent_on_paste {
14165 Some(AutoindentMode::Block {
14166 original_indent_columns,
14167 })
14168 } else {
14169 None
14170 },
14171 cx,
14172 );
14173 });
14174
14175 let selections = this
14176 .selections
14177 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
14178 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14179 } else {
14180 let url = url::Url::parse(&clipboard_text).ok();
14181
14182 let auto_indent_mode = if !clipboard_text.is_empty() {
14183 Some(AutoindentMode::Block {
14184 original_indent_columns: Vec::new(),
14185 })
14186 } else {
14187 None
14188 };
14189
14190 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
14191 let snapshot = buffer.snapshot(cx);
14192
14193 let anchors = old_selections
14194 .iter()
14195 .map(|s| {
14196 let anchor = snapshot.anchor_after(s.head());
14197 s.map(|_| anchor)
14198 })
14199 .collect::<Vec<_>>();
14200
14201 let mut edits = Vec::new();
14202
14203 // When pasting text without metadata (e.g. copied from an
14204 // external editor using multiple cursors) and the number of
14205 // lines matches the number of selections, distribute one
14206 // line per cursor instead of pasting the whole text at each.
14207 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14208 let distribute_lines =
14209 old_selections.len() > 1 && lines.len() == old_selections.len();
14210
14211 for (ix, selection) in old_selections.iter().enumerate() {
14212 let language = snapshot.language_at(selection.head());
14213 let range = selection.range();
14214
14215 let text_for_cursor: &str = if distribute_lines {
14216 lines[ix]
14217 } else {
14218 &clipboard_text
14219 };
14220
14221 let (edit_range, edit_text) = if let Some(language) = language
14222 && language.name() == "Markdown"
14223 {
14224 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14225 } else {
14226 (range, Cow::Borrowed(text_for_cursor))
14227 };
14228
14229 edits.push((edit_range, edit_text));
14230 }
14231
14232 drop(snapshot);
14233 buffer.edit(edits, auto_indent_mode, cx);
14234
14235 anchors
14236 });
14237
14238 this.change_selections(Default::default(), window, cx, |s| {
14239 s.select_anchors(selection_anchors);
14240 });
14241 }
14242
14243 // 🤔 | .. | show_in_menu |
14244 // | .. | true true
14245 // | had_edit_prediction | false true
14246
14247 let trigger_in_words =
14248 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14249
14250 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14251 });
14252 }
14253
14254 pub fn diff_clipboard_with_selection(
14255 &mut self,
14256 _: &DiffClipboardWithSelection,
14257 window: &mut Window,
14258 cx: &mut Context<Self>,
14259 ) {
14260 let selections = self
14261 .selections
14262 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14263
14264 if selections.is_empty() {
14265 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14266 return;
14267 };
14268
14269 let clipboard_text = cx.read_from_clipboard().and_then(|item| {
14270 item.entries().iter().find_map(|entry| match entry {
14271 ClipboardEntry::String(text) => Some(text.text().to_string()),
14272 _ => None,
14273 })
14274 });
14275
14276 let Some(clipboard_text) = clipboard_text else {
14277 log::warn!("Clipboard doesn't contain text.");
14278 return;
14279 };
14280
14281 window.dispatch_action(
14282 Box::new(DiffClipboardWithSelectionData {
14283 clipboard_text,
14284 editor: cx.entity(),
14285 }),
14286 cx,
14287 );
14288 }
14289
14290 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14291 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14292 if let Some(item) = cx.read_from_clipboard() {
14293 let clipboard_string = item.entries().iter().find_map(|entry| match entry {
14294 ClipboardEntry::String(s) => Some(s),
14295 _ => None,
14296 });
14297 match clipboard_string {
14298 Some(clipboard_string) => self.do_paste(
14299 clipboard_string.text(),
14300 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14301 true,
14302 window,
14303 cx,
14304 ),
14305 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14306 }
14307 }
14308 }
14309
14310 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14311 if self.read_only(cx) {
14312 return;
14313 }
14314
14315 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14316
14317 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14318 if let Some((selections, _)) =
14319 self.selection_history.transaction(transaction_id).cloned()
14320 {
14321 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14322 s.select_anchors(selections.to_vec());
14323 });
14324 } else {
14325 log::error!(
14326 "No entry in selection_history found for undo. \
14327 This may correspond to a bug where undo does not update the selection. \
14328 If this is occurring, please add details to \
14329 https://github.com/zed-industries/zed/issues/22692"
14330 );
14331 }
14332 self.request_autoscroll(Autoscroll::fit(), cx);
14333 self.unmark_text(window, cx);
14334 self.refresh_edit_prediction(true, false, window, cx);
14335 cx.emit(EditorEvent::Edited { transaction_id });
14336 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14337 }
14338 }
14339
14340 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14341 if self.read_only(cx) {
14342 return;
14343 }
14344
14345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14346
14347 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14348 if let Some((_, Some(selections))) =
14349 self.selection_history.transaction(transaction_id).cloned()
14350 {
14351 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14352 s.select_anchors(selections.to_vec());
14353 });
14354 } else {
14355 log::error!(
14356 "No entry in selection_history found for redo. \
14357 This may correspond to a bug where undo does not update the selection. \
14358 If this is occurring, please add details to \
14359 https://github.com/zed-industries/zed/issues/22692"
14360 );
14361 }
14362 self.request_autoscroll(Autoscroll::fit(), cx);
14363 self.unmark_text(window, cx);
14364 self.refresh_edit_prediction(true, false, window, cx);
14365 cx.emit(EditorEvent::Edited { transaction_id });
14366 }
14367 }
14368
14369 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14370 self.buffer
14371 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14372 }
14373
14374 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14375 self.buffer
14376 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14377 }
14378
14379 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14380 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14381 self.change_selections(Default::default(), window, cx, |s| {
14382 s.move_with(&mut |map, selection| {
14383 let cursor = if selection.is_empty() {
14384 movement::left(map, selection.start)
14385 } else {
14386 selection.start
14387 };
14388 selection.collapse_to(cursor, SelectionGoal::None);
14389 });
14390 })
14391 }
14392
14393 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14394 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14395 self.change_selections(Default::default(), window, cx, |s| {
14396 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14397 })
14398 }
14399
14400 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14402 self.change_selections(Default::default(), window, cx, |s| {
14403 s.move_with(&mut |map, selection| {
14404 let cursor = if selection.is_empty() {
14405 movement::right(map, selection.end)
14406 } else {
14407 selection.end
14408 };
14409 selection.collapse_to(cursor, SelectionGoal::None)
14410 });
14411 })
14412 }
14413
14414 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14415 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14416 self.change_selections(Default::default(), window, cx, |s| {
14417 s.move_heads_with(&mut |map, head, _| {
14418 (movement::right(map, head), SelectionGoal::None)
14419 });
14420 });
14421 }
14422
14423 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14424 if self.take_rename(true, window, cx).is_some() {
14425 return;
14426 }
14427
14428 if self.mode.is_single_line() {
14429 cx.propagate();
14430 return;
14431 }
14432
14433 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14434
14435 let text_layout_details = &self.text_layout_details(window, cx);
14436 let selection_count = self.selections.count();
14437 let first_selection = self.selections.first_anchor();
14438
14439 self.change_selections(Default::default(), window, cx, |s| {
14440 s.move_with(&mut |map, selection| {
14441 if !selection.is_empty() {
14442 selection.goal = SelectionGoal::None;
14443 }
14444 let (cursor, goal) = movement::up(
14445 map,
14446 selection.start,
14447 selection.goal,
14448 false,
14449 text_layout_details,
14450 );
14451 selection.collapse_to(cursor, goal);
14452 });
14453 });
14454
14455 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14456 {
14457 cx.propagate();
14458 }
14459 }
14460
14461 pub fn move_up_by_lines(
14462 &mut self,
14463 action: &MoveUpByLines,
14464 window: &mut Window,
14465 cx: &mut Context<Self>,
14466 ) {
14467 if self.take_rename(true, window, cx).is_some() {
14468 return;
14469 }
14470
14471 if self.mode.is_single_line() {
14472 cx.propagate();
14473 return;
14474 }
14475
14476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14477
14478 let text_layout_details = &self.text_layout_details(window, cx);
14479
14480 self.change_selections(Default::default(), window, cx, |s| {
14481 s.move_with(&mut |map, selection| {
14482 if !selection.is_empty() {
14483 selection.goal = SelectionGoal::None;
14484 }
14485 let (cursor, goal) = movement::up_by_rows(
14486 map,
14487 selection.start,
14488 action.lines,
14489 selection.goal,
14490 false,
14491 text_layout_details,
14492 );
14493 selection.collapse_to(cursor, goal);
14494 });
14495 })
14496 }
14497
14498 pub fn move_down_by_lines(
14499 &mut self,
14500 action: &MoveDownByLines,
14501 window: &mut Window,
14502 cx: &mut Context<Self>,
14503 ) {
14504 if self.take_rename(true, window, cx).is_some() {
14505 return;
14506 }
14507
14508 if self.mode.is_single_line() {
14509 cx.propagate();
14510 return;
14511 }
14512
14513 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14514
14515 let text_layout_details = &self.text_layout_details(window, cx);
14516
14517 self.change_selections(Default::default(), window, cx, |s| {
14518 s.move_with(&mut |map, selection| {
14519 if !selection.is_empty() {
14520 selection.goal = SelectionGoal::None;
14521 }
14522 let (cursor, goal) = movement::down_by_rows(
14523 map,
14524 selection.start,
14525 action.lines,
14526 selection.goal,
14527 false,
14528 text_layout_details,
14529 );
14530 selection.collapse_to(cursor, goal);
14531 });
14532 })
14533 }
14534
14535 pub fn select_down_by_lines(
14536 &mut self,
14537 action: &SelectDownByLines,
14538 window: &mut Window,
14539 cx: &mut Context<Self>,
14540 ) {
14541 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14542 let text_layout_details = &self.text_layout_details(window, cx);
14543 self.change_selections(Default::default(), window, cx, |s| {
14544 s.move_heads_with(&mut |map, head, goal| {
14545 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14546 })
14547 })
14548 }
14549
14550 pub fn select_up_by_lines(
14551 &mut self,
14552 action: &SelectUpByLines,
14553 window: &mut Window,
14554 cx: &mut Context<Self>,
14555 ) {
14556 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14557 let text_layout_details = &self.text_layout_details(window, cx);
14558 self.change_selections(Default::default(), window, cx, |s| {
14559 s.move_heads_with(&mut |map, head, goal| {
14560 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14561 })
14562 })
14563 }
14564
14565 pub fn select_page_up(
14566 &mut self,
14567 _: &SelectPageUp,
14568 window: &mut Window,
14569 cx: &mut Context<Self>,
14570 ) {
14571 let Some(row_count) = self.visible_row_count() else {
14572 return;
14573 };
14574
14575 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14576
14577 let text_layout_details = &self.text_layout_details(window, cx);
14578
14579 self.change_selections(Default::default(), window, cx, |s| {
14580 s.move_heads_with(&mut |map, head, goal| {
14581 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14582 })
14583 })
14584 }
14585
14586 pub fn move_page_up(
14587 &mut self,
14588 action: &MovePageUp,
14589 window: &mut Window,
14590 cx: &mut Context<Self>,
14591 ) {
14592 if self.take_rename(true, window, cx).is_some() {
14593 return;
14594 }
14595
14596 if self
14597 .context_menu
14598 .borrow_mut()
14599 .as_mut()
14600 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14601 .unwrap_or(false)
14602 {
14603 return;
14604 }
14605
14606 if matches!(self.mode, EditorMode::SingleLine) {
14607 cx.propagate();
14608 return;
14609 }
14610
14611 let Some(row_count) = self.visible_row_count() else {
14612 return;
14613 };
14614
14615 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14616
14617 let effects = if action.center_cursor {
14618 SelectionEffects::scroll(Autoscroll::center())
14619 } else {
14620 SelectionEffects::default()
14621 };
14622
14623 let text_layout_details = &self.text_layout_details(window, cx);
14624
14625 self.change_selections(effects, window, cx, |s| {
14626 s.move_with(&mut |map, selection| {
14627 if !selection.is_empty() {
14628 selection.goal = SelectionGoal::None;
14629 }
14630 let (cursor, goal) = movement::up_by_rows(
14631 map,
14632 selection.end,
14633 row_count,
14634 selection.goal,
14635 false,
14636 text_layout_details,
14637 );
14638 selection.collapse_to(cursor, goal);
14639 });
14640 });
14641 }
14642
14643 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14644 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14645 let text_layout_details = &self.text_layout_details(window, cx);
14646 self.change_selections(Default::default(), window, cx, |s| {
14647 s.move_heads_with(&mut |map, head, goal| {
14648 movement::up(map, head, goal, false, text_layout_details)
14649 })
14650 })
14651 }
14652
14653 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14654 self.take_rename(true, window, cx);
14655
14656 if self.mode.is_single_line() {
14657 cx.propagate();
14658 return;
14659 }
14660
14661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14662
14663 let text_layout_details = &self.text_layout_details(window, cx);
14664 let selection_count = self.selections.count();
14665 let first_selection = self.selections.first_anchor();
14666
14667 self.change_selections(Default::default(), window, cx, |s| {
14668 s.move_with(&mut |map, selection| {
14669 if !selection.is_empty() {
14670 selection.goal = SelectionGoal::None;
14671 }
14672 let (cursor, goal) = movement::down(
14673 map,
14674 selection.end,
14675 selection.goal,
14676 false,
14677 text_layout_details,
14678 );
14679 selection.collapse_to(cursor, goal);
14680 });
14681 });
14682
14683 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14684 {
14685 cx.propagate();
14686 }
14687 }
14688
14689 pub fn select_page_down(
14690 &mut self,
14691 _: &SelectPageDown,
14692 window: &mut Window,
14693 cx: &mut Context<Self>,
14694 ) {
14695 let Some(row_count) = self.visible_row_count() else {
14696 return;
14697 };
14698
14699 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14700
14701 let text_layout_details = &self.text_layout_details(window, cx);
14702
14703 self.change_selections(Default::default(), window, cx, |s| {
14704 s.move_heads_with(&mut |map, head, goal| {
14705 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14706 })
14707 })
14708 }
14709
14710 pub fn move_page_down(
14711 &mut self,
14712 action: &MovePageDown,
14713 window: &mut Window,
14714 cx: &mut Context<Self>,
14715 ) {
14716 if self.take_rename(true, window, cx).is_some() {
14717 return;
14718 }
14719
14720 if self
14721 .context_menu
14722 .borrow_mut()
14723 .as_mut()
14724 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14725 .unwrap_or(false)
14726 {
14727 return;
14728 }
14729
14730 if matches!(self.mode, EditorMode::SingleLine) {
14731 cx.propagate();
14732 return;
14733 }
14734
14735 let Some(row_count) = self.visible_row_count() else {
14736 return;
14737 };
14738
14739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14740
14741 let effects = if action.center_cursor {
14742 SelectionEffects::scroll(Autoscroll::center())
14743 } else {
14744 SelectionEffects::default()
14745 };
14746
14747 let text_layout_details = &self.text_layout_details(window, cx);
14748 self.change_selections(effects, window, cx, |s| {
14749 s.move_with(&mut |map, selection| {
14750 if !selection.is_empty() {
14751 selection.goal = SelectionGoal::None;
14752 }
14753 let (cursor, goal) = movement::down_by_rows(
14754 map,
14755 selection.end,
14756 row_count,
14757 selection.goal,
14758 false,
14759 text_layout_details,
14760 );
14761 selection.collapse_to(cursor, goal);
14762 });
14763 });
14764 }
14765
14766 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14767 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14768 let text_layout_details = &self.text_layout_details(window, cx);
14769 self.change_selections(Default::default(), window, cx, |s| {
14770 s.move_heads_with(&mut |map, head, goal| {
14771 movement::down(map, head, goal, false, text_layout_details)
14772 })
14773 });
14774 }
14775
14776 pub fn context_menu_first(
14777 &mut self,
14778 _: &ContextMenuFirst,
14779 window: &mut Window,
14780 cx: &mut Context<Self>,
14781 ) {
14782 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14783 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14784 }
14785 }
14786
14787 pub fn context_menu_prev(
14788 &mut self,
14789 _: &ContextMenuPrevious,
14790 window: &mut Window,
14791 cx: &mut Context<Self>,
14792 ) {
14793 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14794 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14795 }
14796 }
14797
14798 pub fn context_menu_next(
14799 &mut self,
14800 _: &ContextMenuNext,
14801 window: &mut Window,
14802 cx: &mut Context<Self>,
14803 ) {
14804 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14805 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14806 }
14807 }
14808
14809 pub fn context_menu_last(
14810 &mut self,
14811 _: &ContextMenuLast,
14812 window: &mut Window,
14813 cx: &mut Context<Self>,
14814 ) {
14815 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14816 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14817 }
14818 }
14819
14820 pub fn signature_help_prev(
14821 &mut self,
14822 _: &SignatureHelpPrevious,
14823 _: &mut Window,
14824 cx: &mut Context<Self>,
14825 ) {
14826 if let Some(popover) = self.signature_help_state.popover_mut() {
14827 if popover.current_signature == 0 {
14828 popover.current_signature = popover.signatures.len() - 1;
14829 } else {
14830 popover.current_signature -= 1;
14831 }
14832 cx.notify();
14833 }
14834 }
14835
14836 pub fn signature_help_next(
14837 &mut self,
14838 _: &SignatureHelpNext,
14839 _: &mut Window,
14840 cx: &mut Context<Self>,
14841 ) {
14842 if let Some(popover) = self.signature_help_state.popover_mut() {
14843 if popover.current_signature + 1 == popover.signatures.len() {
14844 popover.current_signature = 0;
14845 } else {
14846 popover.current_signature += 1;
14847 }
14848 cx.notify();
14849 }
14850 }
14851
14852 pub fn move_to_previous_word_start(
14853 &mut self,
14854 _: &MoveToPreviousWordStart,
14855 window: &mut Window,
14856 cx: &mut Context<Self>,
14857 ) {
14858 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14859 self.change_selections(Default::default(), window, cx, |s| {
14860 s.move_cursors_with(&mut |map, head, _| {
14861 (
14862 movement::previous_word_start(map, head),
14863 SelectionGoal::None,
14864 )
14865 });
14866 })
14867 }
14868
14869 pub fn move_to_previous_subword_start(
14870 &mut self,
14871 _: &MoveToPreviousSubwordStart,
14872 window: &mut Window,
14873 cx: &mut Context<Self>,
14874 ) {
14875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14876 self.change_selections(Default::default(), window, cx, |s| {
14877 s.move_cursors_with(&mut |map, head, _| {
14878 (
14879 movement::previous_subword_start(map, head),
14880 SelectionGoal::None,
14881 )
14882 });
14883 })
14884 }
14885
14886 pub fn select_to_previous_word_start(
14887 &mut self,
14888 _: &SelectToPreviousWordStart,
14889 window: &mut Window,
14890 cx: &mut Context<Self>,
14891 ) {
14892 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14893 self.change_selections(Default::default(), window, cx, |s| {
14894 s.move_heads_with(&mut |map, head, _| {
14895 (
14896 movement::previous_word_start(map, head),
14897 SelectionGoal::None,
14898 )
14899 });
14900 })
14901 }
14902
14903 pub fn select_to_previous_subword_start(
14904 &mut self,
14905 _: &SelectToPreviousSubwordStart,
14906 window: &mut Window,
14907 cx: &mut Context<Self>,
14908 ) {
14909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14910 self.change_selections(Default::default(), window, cx, |s| {
14911 s.move_heads_with(&mut |map, head, _| {
14912 (
14913 movement::previous_subword_start(map, head),
14914 SelectionGoal::None,
14915 )
14916 });
14917 })
14918 }
14919
14920 pub fn delete_to_previous_word_start(
14921 &mut self,
14922 action: &DeleteToPreviousWordStart,
14923 window: &mut Window,
14924 cx: &mut Context<Self>,
14925 ) {
14926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14927 self.transact(window, cx, |this, window, cx| {
14928 this.select_autoclose_pair(window, cx);
14929 this.change_selections(Default::default(), window, cx, |s| {
14930 s.move_with(&mut |map, selection| {
14931 if selection.is_empty() {
14932 let mut cursor = if action.ignore_newlines {
14933 movement::previous_word_start(map, selection.head())
14934 } else {
14935 movement::previous_word_start_or_newline(map, selection.head())
14936 };
14937 cursor = movement::adjust_greedy_deletion(
14938 map,
14939 selection.head(),
14940 cursor,
14941 action.ignore_brackets,
14942 );
14943 selection.set_head(cursor, SelectionGoal::None);
14944 }
14945 });
14946 });
14947 this.insert("", window, cx);
14948 });
14949 }
14950
14951 pub fn delete_to_previous_subword_start(
14952 &mut self,
14953 action: &DeleteToPreviousSubwordStart,
14954 window: &mut Window,
14955 cx: &mut Context<Self>,
14956 ) {
14957 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14958 self.transact(window, cx, |this, window, cx| {
14959 this.select_autoclose_pair(window, cx);
14960 this.change_selections(Default::default(), window, cx, |s| {
14961 s.move_with(&mut |map, selection| {
14962 if selection.is_empty() {
14963 let mut cursor = if action.ignore_newlines {
14964 movement::previous_subword_start(map, selection.head())
14965 } else {
14966 movement::previous_subword_start_or_newline(map, selection.head())
14967 };
14968 cursor = movement::adjust_greedy_deletion(
14969 map,
14970 selection.head(),
14971 cursor,
14972 action.ignore_brackets,
14973 );
14974 selection.set_head(cursor, SelectionGoal::None);
14975 }
14976 });
14977 });
14978 this.insert("", window, cx);
14979 });
14980 }
14981
14982 pub fn move_to_next_word_end(
14983 &mut self,
14984 _: &MoveToNextWordEnd,
14985 window: &mut Window,
14986 cx: &mut Context<Self>,
14987 ) {
14988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14989 self.change_selections(Default::default(), window, cx, |s| {
14990 s.move_cursors_with(&mut |map, head, _| {
14991 (movement::next_word_end(map, head), SelectionGoal::None)
14992 });
14993 })
14994 }
14995
14996 pub fn move_to_next_subword_end(
14997 &mut self,
14998 _: &MoveToNextSubwordEnd,
14999 window: &mut Window,
15000 cx: &mut Context<Self>,
15001 ) {
15002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15003 self.change_selections(Default::default(), window, cx, |s| {
15004 s.move_cursors_with(&mut |map, head, _| {
15005 (movement::next_subword_end(map, head), SelectionGoal::None)
15006 });
15007 })
15008 }
15009
15010 pub fn select_to_next_word_end(
15011 &mut self,
15012 _: &SelectToNextWordEnd,
15013 window: &mut Window,
15014 cx: &mut Context<Self>,
15015 ) {
15016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15017 self.change_selections(Default::default(), window, cx, |s| {
15018 s.move_heads_with(&mut |map, head, _| {
15019 (movement::next_word_end(map, head), SelectionGoal::None)
15020 });
15021 })
15022 }
15023
15024 pub fn select_to_next_subword_end(
15025 &mut self,
15026 _: &SelectToNextSubwordEnd,
15027 window: &mut Window,
15028 cx: &mut Context<Self>,
15029 ) {
15030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15031 self.change_selections(Default::default(), window, cx, |s| {
15032 s.move_heads_with(&mut |map, head, _| {
15033 (movement::next_subword_end(map, head), SelectionGoal::None)
15034 });
15035 })
15036 }
15037
15038 pub fn delete_to_next_word_end(
15039 &mut self,
15040 action: &DeleteToNextWordEnd,
15041 window: &mut Window,
15042 cx: &mut Context<Self>,
15043 ) {
15044 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15045 self.transact(window, cx, |this, window, cx| {
15046 this.change_selections(Default::default(), window, cx, |s| {
15047 s.move_with(&mut |map, selection| {
15048 if selection.is_empty() {
15049 let mut cursor = if action.ignore_newlines {
15050 movement::next_word_end(map, selection.head())
15051 } else {
15052 movement::next_word_end_or_newline(map, selection.head())
15053 };
15054 cursor = movement::adjust_greedy_deletion(
15055 map,
15056 selection.head(),
15057 cursor,
15058 action.ignore_brackets,
15059 );
15060 selection.set_head(cursor, SelectionGoal::None);
15061 }
15062 });
15063 });
15064 this.insert("", window, cx);
15065 });
15066 }
15067
15068 pub fn delete_to_next_subword_end(
15069 &mut self,
15070 action: &DeleteToNextSubwordEnd,
15071 window: &mut Window,
15072 cx: &mut Context<Self>,
15073 ) {
15074 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15075 self.transact(window, cx, |this, window, cx| {
15076 this.change_selections(Default::default(), window, cx, |s| {
15077 s.move_with(&mut |map, selection| {
15078 if selection.is_empty() {
15079 let mut cursor = if action.ignore_newlines {
15080 movement::next_subword_end(map, selection.head())
15081 } else {
15082 movement::next_subword_end_or_newline(map, selection.head())
15083 };
15084 cursor = movement::adjust_greedy_deletion(
15085 map,
15086 selection.head(),
15087 cursor,
15088 action.ignore_brackets,
15089 );
15090 selection.set_head(cursor, SelectionGoal::None);
15091 }
15092 });
15093 });
15094 this.insert("", window, cx);
15095 });
15096 }
15097
15098 pub fn move_to_beginning_of_line(
15099 &mut self,
15100 action: &MoveToBeginningOfLine,
15101 window: &mut Window,
15102 cx: &mut Context<Self>,
15103 ) {
15104 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15105 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15106 self.change_selections(Default::default(), window, cx, |s| {
15107 s.move_cursors_with(&mut |map, head, _| {
15108 (
15109 movement::indented_line_beginning(
15110 map,
15111 head,
15112 action.stop_at_soft_wraps,
15113 stop_at_indent,
15114 ),
15115 SelectionGoal::None,
15116 )
15117 });
15118 })
15119 }
15120
15121 pub fn select_to_beginning_of_line(
15122 &mut self,
15123 action: &SelectToBeginningOfLine,
15124 window: &mut Window,
15125 cx: &mut Context<Self>,
15126 ) {
15127 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15129 self.change_selections(Default::default(), window, cx, |s| {
15130 s.move_heads_with(&mut |map, head, _| {
15131 (
15132 movement::indented_line_beginning(
15133 map,
15134 head,
15135 action.stop_at_soft_wraps,
15136 stop_at_indent,
15137 ),
15138 SelectionGoal::None,
15139 )
15140 });
15141 });
15142 }
15143
15144 pub fn delete_to_beginning_of_line(
15145 &mut self,
15146 action: &DeleteToBeginningOfLine,
15147 window: &mut Window,
15148 cx: &mut Context<Self>,
15149 ) {
15150 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15151 self.transact(window, cx, |this, window, cx| {
15152 this.change_selections(Default::default(), window, cx, |s| {
15153 s.move_with(&mut |_, selection| {
15154 selection.reversed = true;
15155 });
15156 });
15157
15158 this.select_to_beginning_of_line(
15159 &SelectToBeginningOfLine {
15160 stop_at_soft_wraps: false,
15161 stop_at_indent: action.stop_at_indent,
15162 },
15163 window,
15164 cx,
15165 );
15166 this.backspace(&Backspace, window, cx);
15167 });
15168 }
15169
15170 pub fn move_to_end_of_line(
15171 &mut self,
15172 action: &MoveToEndOfLine,
15173 window: &mut Window,
15174 cx: &mut Context<Self>,
15175 ) {
15176 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15177 self.change_selections(Default::default(), window, cx, |s| {
15178 s.move_cursors_with(&mut |map, head, _| {
15179 (
15180 movement::line_end(map, head, action.stop_at_soft_wraps),
15181 SelectionGoal::None,
15182 )
15183 });
15184 })
15185 }
15186
15187 pub fn select_to_end_of_line(
15188 &mut self,
15189 action: &SelectToEndOfLine,
15190 window: &mut Window,
15191 cx: &mut Context<Self>,
15192 ) {
15193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15194 self.change_selections(Default::default(), window, cx, |s| {
15195 s.move_heads_with(&mut |map, head, _| {
15196 (
15197 movement::line_end(map, head, action.stop_at_soft_wraps),
15198 SelectionGoal::None,
15199 )
15200 });
15201 })
15202 }
15203
15204 pub fn delete_to_end_of_line(
15205 &mut self,
15206 _: &DeleteToEndOfLine,
15207 window: &mut Window,
15208 cx: &mut Context<Self>,
15209 ) {
15210 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15211 self.transact(window, cx, |this, window, cx| {
15212 this.select_to_end_of_line(
15213 &SelectToEndOfLine {
15214 stop_at_soft_wraps: false,
15215 },
15216 window,
15217 cx,
15218 );
15219 this.delete(&Delete, window, cx);
15220 });
15221 }
15222
15223 pub fn cut_to_end_of_line(
15224 &mut self,
15225 action: &CutToEndOfLine,
15226 window: &mut Window,
15227 cx: &mut Context<Self>,
15228 ) {
15229 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15230 self.transact(window, cx, |this, window, cx| {
15231 this.select_to_end_of_line(
15232 &SelectToEndOfLine {
15233 stop_at_soft_wraps: false,
15234 },
15235 window,
15236 cx,
15237 );
15238 if !action.stop_at_newlines {
15239 this.change_selections(Default::default(), window, cx, |s| {
15240 s.move_with(&mut |_, sel| {
15241 if sel.is_empty() {
15242 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15243 }
15244 });
15245 });
15246 }
15247 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15248 let item = this.cut_common(false, window, cx);
15249 cx.write_to_clipboard(item);
15250 });
15251 }
15252
15253 pub fn move_to_start_of_paragraph(
15254 &mut self,
15255 _: &MoveToStartOfParagraph,
15256 window: &mut Window,
15257 cx: &mut Context<Self>,
15258 ) {
15259 if matches!(self.mode, EditorMode::SingleLine) {
15260 cx.propagate();
15261 return;
15262 }
15263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15264 self.change_selections(Default::default(), window, cx, |s| {
15265 s.move_with(&mut |map, selection| {
15266 selection.collapse_to(
15267 movement::start_of_paragraph(map, selection.head(), 1),
15268 SelectionGoal::None,
15269 )
15270 });
15271 })
15272 }
15273
15274 pub fn move_to_end_of_paragraph(
15275 &mut self,
15276 _: &MoveToEndOfParagraph,
15277 window: &mut Window,
15278 cx: &mut Context<Self>,
15279 ) {
15280 if matches!(self.mode, EditorMode::SingleLine) {
15281 cx.propagate();
15282 return;
15283 }
15284 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15285 self.change_selections(Default::default(), window, cx, |s| {
15286 s.move_with(&mut |map, selection| {
15287 selection.collapse_to(
15288 movement::end_of_paragraph(map, selection.head(), 1),
15289 SelectionGoal::None,
15290 )
15291 });
15292 })
15293 }
15294
15295 pub fn select_to_start_of_paragraph(
15296 &mut self,
15297 _: &SelectToStartOfParagraph,
15298 window: &mut Window,
15299 cx: &mut Context<Self>,
15300 ) {
15301 if matches!(self.mode, EditorMode::SingleLine) {
15302 cx.propagate();
15303 return;
15304 }
15305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15306 self.change_selections(Default::default(), window, cx, |s| {
15307 s.move_heads_with(&mut |map, head, _| {
15308 (
15309 movement::start_of_paragraph(map, head, 1),
15310 SelectionGoal::None,
15311 )
15312 });
15313 })
15314 }
15315
15316 pub fn select_to_end_of_paragraph(
15317 &mut self,
15318 _: &SelectToEndOfParagraph,
15319 window: &mut Window,
15320 cx: &mut Context<Self>,
15321 ) {
15322 if matches!(self.mode, EditorMode::SingleLine) {
15323 cx.propagate();
15324 return;
15325 }
15326 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15327 self.change_selections(Default::default(), window, cx, |s| {
15328 s.move_heads_with(&mut |map, head, _| {
15329 (
15330 movement::end_of_paragraph(map, head, 1),
15331 SelectionGoal::None,
15332 )
15333 });
15334 })
15335 }
15336
15337 pub fn move_to_start_of_excerpt(
15338 &mut self,
15339 _: &MoveToStartOfExcerpt,
15340 window: &mut Window,
15341 cx: &mut Context<Self>,
15342 ) {
15343 if matches!(self.mode, EditorMode::SingleLine) {
15344 cx.propagate();
15345 return;
15346 }
15347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15348 self.change_selections(Default::default(), window, cx, |s| {
15349 s.move_with(&mut |map, selection| {
15350 selection.collapse_to(
15351 movement::start_of_excerpt(
15352 map,
15353 selection.head(),
15354 workspace::searchable::Direction::Prev,
15355 ),
15356 SelectionGoal::None,
15357 )
15358 });
15359 })
15360 }
15361
15362 pub fn move_to_start_of_next_excerpt(
15363 &mut self,
15364 _: &MoveToStartOfNextExcerpt,
15365 window: &mut Window,
15366 cx: &mut Context<Self>,
15367 ) {
15368 if matches!(self.mode, EditorMode::SingleLine) {
15369 cx.propagate();
15370 return;
15371 }
15372
15373 self.change_selections(Default::default(), window, cx, |s| {
15374 s.move_with(&mut |map, selection| {
15375 selection.collapse_to(
15376 movement::start_of_excerpt(
15377 map,
15378 selection.head(),
15379 workspace::searchable::Direction::Next,
15380 ),
15381 SelectionGoal::None,
15382 )
15383 });
15384 })
15385 }
15386
15387 pub fn move_to_end_of_excerpt(
15388 &mut self,
15389 _: &MoveToEndOfExcerpt,
15390 window: &mut Window,
15391 cx: &mut Context<Self>,
15392 ) {
15393 if matches!(self.mode, EditorMode::SingleLine) {
15394 cx.propagate();
15395 return;
15396 }
15397 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15398 self.change_selections(Default::default(), window, cx, |s| {
15399 s.move_with(&mut |map, selection| {
15400 selection.collapse_to(
15401 movement::end_of_excerpt(
15402 map,
15403 selection.head(),
15404 workspace::searchable::Direction::Next,
15405 ),
15406 SelectionGoal::None,
15407 )
15408 });
15409 })
15410 }
15411
15412 pub fn move_to_end_of_previous_excerpt(
15413 &mut self,
15414 _: &MoveToEndOfPreviousExcerpt,
15415 window: &mut Window,
15416 cx: &mut Context<Self>,
15417 ) {
15418 if matches!(self.mode, EditorMode::SingleLine) {
15419 cx.propagate();
15420 return;
15421 }
15422 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15423 self.change_selections(Default::default(), window, cx, |s| {
15424 s.move_with(&mut |map, selection| {
15425 selection.collapse_to(
15426 movement::end_of_excerpt(
15427 map,
15428 selection.head(),
15429 workspace::searchable::Direction::Prev,
15430 ),
15431 SelectionGoal::None,
15432 )
15433 });
15434 })
15435 }
15436
15437 pub fn select_to_start_of_excerpt(
15438 &mut self,
15439 _: &SelectToStartOfExcerpt,
15440 window: &mut Window,
15441 cx: &mut Context<Self>,
15442 ) {
15443 if matches!(self.mode, EditorMode::SingleLine) {
15444 cx.propagate();
15445 return;
15446 }
15447 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15448 self.change_selections(Default::default(), window, cx, |s| {
15449 s.move_heads_with(&mut |map, head, _| {
15450 (
15451 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15452 SelectionGoal::None,
15453 )
15454 });
15455 })
15456 }
15457
15458 pub fn select_to_start_of_next_excerpt(
15459 &mut self,
15460 _: &SelectToStartOfNextExcerpt,
15461 window: &mut Window,
15462 cx: &mut Context<Self>,
15463 ) {
15464 if matches!(self.mode, EditorMode::SingleLine) {
15465 cx.propagate();
15466 return;
15467 }
15468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15469 self.change_selections(Default::default(), window, cx, |s| {
15470 s.move_heads_with(&mut |map, head, _| {
15471 (
15472 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15473 SelectionGoal::None,
15474 )
15475 });
15476 })
15477 }
15478
15479 pub fn select_to_end_of_excerpt(
15480 &mut self,
15481 _: &SelectToEndOfExcerpt,
15482 window: &mut Window,
15483 cx: &mut Context<Self>,
15484 ) {
15485 if matches!(self.mode, EditorMode::SingleLine) {
15486 cx.propagate();
15487 return;
15488 }
15489 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15490 self.change_selections(Default::default(), window, cx, |s| {
15491 s.move_heads_with(&mut |map, head, _| {
15492 (
15493 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15494 SelectionGoal::None,
15495 )
15496 });
15497 })
15498 }
15499
15500 pub fn select_to_end_of_previous_excerpt(
15501 &mut self,
15502 _: &SelectToEndOfPreviousExcerpt,
15503 window: &mut Window,
15504 cx: &mut Context<Self>,
15505 ) {
15506 if matches!(self.mode, EditorMode::SingleLine) {
15507 cx.propagate();
15508 return;
15509 }
15510 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15511 self.change_selections(Default::default(), window, cx, |s| {
15512 s.move_heads_with(&mut |map, head, _| {
15513 (
15514 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15515 SelectionGoal::None,
15516 )
15517 });
15518 })
15519 }
15520
15521 pub fn move_to_beginning(
15522 &mut self,
15523 _: &MoveToBeginning,
15524 window: &mut Window,
15525 cx: &mut Context<Self>,
15526 ) {
15527 if matches!(self.mode, EditorMode::SingleLine) {
15528 cx.propagate();
15529 return;
15530 }
15531 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15532 self.change_selections(Default::default(), window, cx, |s| {
15533 s.select_ranges(vec![Anchor::Min..Anchor::Min]);
15534 });
15535 }
15536
15537 pub fn select_to_beginning(
15538 &mut self,
15539 _: &SelectToBeginning,
15540 window: &mut Window,
15541 cx: &mut Context<Self>,
15542 ) {
15543 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15544 selection.set_head(Point::zero(), SelectionGoal::None);
15545 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15546 self.change_selections(Default::default(), window, cx, |s| {
15547 s.select(vec![selection]);
15548 });
15549 }
15550
15551 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15552 if matches!(self.mode, EditorMode::SingleLine) {
15553 cx.propagate();
15554 return;
15555 }
15556 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15557 let cursor = self.buffer.read(cx).read(cx).len();
15558 self.change_selections(Default::default(), window, cx, |s| {
15559 s.select_ranges(vec![cursor..cursor])
15560 });
15561 }
15562
15563 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15564 self.nav_history = nav_history;
15565 }
15566
15567 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15568 self.nav_history.as_ref()
15569 }
15570
15571 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15572 self.push_to_nav_history(
15573 self.selections.newest_anchor().head(),
15574 None,
15575 false,
15576 true,
15577 cx,
15578 );
15579 }
15580
15581 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15582 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15583 let buffer = self.buffer.read(cx).read(cx);
15584 let cursor_position = cursor_anchor.to_point(&buffer);
15585 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15586 let scroll_top_row = scroll_anchor.top_row(&buffer);
15587 drop(buffer);
15588
15589 NavigationData {
15590 cursor_anchor,
15591 cursor_position,
15592 scroll_anchor,
15593 scroll_top_row,
15594 }
15595 }
15596
15597 fn navigation_entry(
15598 &self,
15599 cursor_anchor: Anchor,
15600 cx: &mut Context<Self>,
15601 ) -> Option<NavigationEntry> {
15602 let Some(history) = self.nav_history.clone() else {
15603 return None;
15604 };
15605 let data = self.navigation_data(cursor_anchor, cx);
15606 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15607 }
15608
15609 fn push_to_nav_history(
15610 &mut self,
15611 cursor_anchor: Anchor,
15612 new_position: Option<Point>,
15613 is_deactivate: bool,
15614 always: bool,
15615 cx: &mut Context<Self>,
15616 ) {
15617 let data = self.navigation_data(cursor_anchor, cx);
15618 if let Some(nav_history) = self.nav_history.as_mut() {
15619 if let Some(new_position) = new_position {
15620 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15621 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15622 return;
15623 }
15624 }
15625
15626 let cursor_row = data.cursor_position.row;
15627 nav_history.push(Some(data), Some(cursor_row), cx);
15628 cx.emit(EditorEvent::PushedToNavHistory {
15629 anchor: cursor_anchor,
15630 is_deactivate,
15631 })
15632 }
15633 }
15634
15635 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15636 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15637 let buffer = self.buffer.read(cx).snapshot(cx);
15638 let mut selection = self
15639 .selections
15640 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15641 selection.set_head(buffer.len(), SelectionGoal::None);
15642 self.change_selections(Default::default(), window, cx, |s| {
15643 s.select(vec![selection]);
15644 });
15645 }
15646
15647 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15648 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15649 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15650 s.select_ranges(vec![Anchor::Min..Anchor::Max]);
15651 });
15652 }
15653
15654 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15655 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15656 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15657 let mut selections = self.selections.all::<Point>(&display_map);
15658 let max_point = display_map.buffer_snapshot().max_point();
15659 for selection in &mut selections {
15660 let rows = selection.spanned_rows(true, &display_map);
15661 selection.start = Point::new(rows.start.0, 0);
15662 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15663 selection.reversed = false;
15664 }
15665 self.change_selections(Default::default(), window, cx, |s| {
15666 s.select(selections);
15667 });
15668 }
15669
15670 pub fn split_selection_into_lines(
15671 &mut self,
15672 action: &SplitSelectionIntoLines,
15673 window: &mut Window,
15674 cx: &mut Context<Self>,
15675 ) {
15676 let selections = self
15677 .selections
15678 .all::<Point>(&self.display_snapshot(cx))
15679 .into_iter()
15680 .map(|selection| selection.start..selection.end)
15681 .collect::<Vec<_>>();
15682 self.unfold_ranges(&selections, true, false, cx);
15683
15684 let mut new_selection_ranges = Vec::new();
15685 {
15686 let buffer = self.buffer.read(cx).read(cx);
15687 for selection in selections {
15688 for row in selection.start.row..selection.end.row {
15689 let line_start = Point::new(row, 0);
15690 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15691
15692 if action.keep_selections {
15693 // Keep the selection range for each line
15694 let selection_start = if row == selection.start.row {
15695 selection.start
15696 } else {
15697 line_start
15698 };
15699 new_selection_ranges.push(selection_start..line_end);
15700 } else {
15701 // Collapse to cursor at end of line
15702 new_selection_ranges.push(line_end..line_end);
15703 }
15704 }
15705
15706 let is_multiline_selection = selection.start.row != selection.end.row;
15707 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15708 // so this action feels more ergonomic when paired with other selection operations
15709 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15710 if !should_skip_last {
15711 if action.keep_selections {
15712 if is_multiline_selection {
15713 let line_start = Point::new(selection.end.row, 0);
15714 new_selection_ranges.push(line_start..selection.end);
15715 } else {
15716 new_selection_ranges.push(selection.start..selection.end);
15717 }
15718 } else {
15719 new_selection_ranges.push(selection.end..selection.end);
15720 }
15721 }
15722 }
15723 }
15724 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15725 s.select_ranges(new_selection_ranges);
15726 });
15727 }
15728
15729 pub fn add_selection_above(
15730 &mut self,
15731 action: &AddSelectionAbove,
15732 window: &mut Window,
15733 cx: &mut Context<Self>,
15734 ) {
15735 self.add_selection(true, action.skip_soft_wrap, window, cx);
15736 }
15737
15738 pub fn add_selection_below(
15739 &mut self,
15740 action: &AddSelectionBelow,
15741 window: &mut Window,
15742 cx: &mut Context<Self>,
15743 ) {
15744 self.add_selection(false, action.skip_soft_wrap, window, cx);
15745 }
15746
15747 fn add_selection(
15748 &mut self,
15749 above: bool,
15750 skip_soft_wrap: bool,
15751 window: &mut Window,
15752 cx: &mut Context<Self>,
15753 ) {
15754 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15755
15756 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15757 let all_selections = self.selections.all::<Point>(&display_map);
15758 let text_layout_details = self.text_layout_details(window, cx);
15759
15760 let (mut columnar_selections, new_selections_to_columnarize) = {
15761 if let Some(state) = self.add_selections_state.as_ref() {
15762 let columnar_selection_ids: HashSet<_> = state
15763 .groups
15764 .iter()
15765 .flat_map(|group| group.stack.iter())
15766 .copied()
15767 .collect();
15768
15769 all_selections
15770 .into_iter()
15771 .partition(|s| columnar_selection_ids.contains(&s.id))
15772 } else {
15773 (Vec::new(), all_selections)
15774 }
15775 };
15776
15777 let mut state = self
15778 .add_selections_state
15779 .take()
15780 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15781
15782 for selection in new_selections_to_columnarize {
15783 let range = selection.display_range(&display_map).sorted();
15784 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15785 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15786 let positions = start_x.min(end_x)..start_x.max(end_x);
15787 let mut stack = Vec::new();
15788 for row in range.start.row().0..=range.end.row().0 {
15789 if let Some(selection) = self.selections.build_columnar_selection(
15790 &display_map,
15791 DisplayRow(row),
15792 &positions,
15793 selection.reversed,
15794 &text_layout_details,
15795 ) {
15796 stack.push(selection.id);
15797 columnar_selections.push(selection);
15798 }
15799 }
15800 if !stack.is_empty() {
15801 if above {
15802 stack.reverse();
15803 }
15804 state.groups.push(AddSelectionsGroup { above, stack });
15805 }
15806 }
15807
15808 let mut final_selections = Vec::new();
15809 let end_row = if above {
15810 DisplayRow(0)
15811 } else {
15812 display_map.max_point().row()
15813 };
15814
15815 // When `skip_soft_wrap` is true, we use UTF-16 columns instead of pixel
15816 // positions to place new selections, so we need to keep track of the
15817 // column range of the oldest selection in each group, because
15818 // intermediate selections may have been clamped to shorter lines.
15819 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15820 let mut map = HashMap::default();
15821 for group in state.groups.iter() {
15822 if let Some(oldest_id) = group.stack.first() {
15823 if let Some(oldest_selection) =
15824 columnar_selections.iter().find(|s| s.id == *oldest_id)
15825 {
15826 let snapshot = display_map.buffer_snapshot();
15827 let start_col =
15828 snapshot.point_to_point_utf16(oldest_selection.start).column;
15829 let end_col = snapshot.point_to_point_utf16(oldest_selection.end).column;
15830 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15831 for id in &group.stack {
15832 map.insert(*id, goal_columns.clone());
15833 }
15834 }
15835 }
15836 }
15837 map
15838 } else {
15839 HashMap::default()
15840 };
15841
15842 let mut last_added_item_per_group = HashMap::default();
15843 for group in state.groups.iter_mut() {
15844 if let Some(last_id) = group.stack.last() {
15845 last_added_item_per_group.insert(*last_id, group);
15846 }
15847 }
15848
15849 for selection in columnar_selections {
15850 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15851 if above == group.above {
15852 let range = selection.display_range(&display_map).sorted();
15853 debug_assert_eq!(range.start.row(), range.end.row());
15854 let row = range.start.row();
15855 let positions =
15856 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15857 Pixels::from(start)..Pixels::from(end)
15858 } else {
15859 let start_x =
15860 display_map.x_for_display_point(range.start, &text_layout_details);
15861 let end_x =
15862 display_map.x_for_display_point(range.end, &text_layout_details);
15863 start_x.min(end_x)..start_x.max(end_x)
15864 };
15865
15866 let maybe_new_selection = if skip_soft_wrap {
15867 let goal_columns = goal_columns_by_selection_id
15868 .remove(&selection.id)
15869 .unwrap_or_else(|| {
15870 let snapshot = display_map.buffer_snapshot();
15871 let start_col =
15872 snapshot.point_to_point_utf16(selection.start).column;
15873 let end_col = snapshot.point_to_point_utf16(selection.end).column;
15874 start_col.min(end_col)..start_col.max(end_col)
15875 });
15876 self.selections.find_next_columnar_selection_by_buffer_row(
15877 &display_map,
15878 row,
15879 end_row,
15880 above,
15881 &goal_columns,
15882 selection.reversed,
15883 &text_layout_details,
15884 )
15885 } else {
15886 self.selections.find_next_columnar_selection_by_display_row(
15887 &display_map,
15888 row,
15889 end_row,
15890 above,
15891 &positions,
15892 selection.reversed,
15893 &text_layout_details,
15894 )
15895 };
15896
15897 if let Some(new_selection) = maybe_new_selection {
15898 group.stack.push(new_selection.id);
15899 if above {
15900 final_selections.push(new_selection);
15901 final_selections.push(selection);
15902 } else {
15903 final_selections.push(selection);
15904 final_selections.push(new_selection);
15905 }
15906 } else {
15907 final_selections.push(selection);
15908 }
15909 } else {
15910 group.stack.pop();
15911 }
15912 } else {
15913 final_selections.push(selection);
15914 }
15915 }
15916
15917 self.change_selections(Default::default(), window, cx, |s| {
15918 s.select(final_selections);
15919 });
15920
15921 let final_selection_ids: HashSet<_> = self
15922 .selections
15923 .all::<Point>(&display_map)
15924 .iter()
15925 .map(|s| s.id)
15926 .collect();
15927 state.groups.retain_mut(|group| {
15928 // selections might get merged above so we remove invalid items from stacks
15929 group.stack.retain(|id| final_selection_ids.contains(id));
15930
15931 // single selection in stack can be treated as initial state
15932 group.stack.len() > 1
15933 });
15934
15935 if !state.groups.is_empty() {
15936 self.add_selections_state = Some(state);
15937 }
15938 }
15939
15940 pub fn insert_snippet_at_selections(
15941 &mut self,
15942 action: &InsertSnippet,
15943 window: &mut Window,
15944 cx: &mut Context<Self>,
15945 ) {
15946 self.try_insert_snippet_at_selections(action, window, cx)
15947 .log_err();
15948 }
15949
15950 fn try_insert_snippet_at_selections(
15951 &mut self,
15952 action: &InsertSnippet,
15953 window: &mut Window,
15954 cx: &mut Context<Self>,
15955 ) -> Result<()> {
15956 let insertion_ranges = self
15957 .selections
15958 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15959 .into_iter()
15960 .map(|selection| selection.range())
15961 .collect_vec();
15962
15963 let snippet = if let Some(snippet_body) = &action.snippet {
15964 if action.language.is_none() && action.name.is_none() {
15965 Snippet::parse(snippet_body)?
15966 } else {
15967 bail!("`snippet` is mutually exclusive with `language` and `name`")
15968 }
15969 } else if let Some(name) = &action.name {
15970 let project = self.project().context("no project")?;
15971 let snippet_store = project.read(cx).snippets().read(cx);
15972 let snippet = snippet_store
15973 .snippets_for(action.language.clone(), cx)
15974 .into_iter()
15975 .find(|snippet| snippet.name == *name)
15976 .context("snippet not found")?;
15977 Snippet::parse(&snippet.body)?
15978 } else {
15979 // todo(andrew): open modal to select snippet
15980 bail!("`name` or `snippet` is required")
15981 };
15982
15983 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15984 }
15985
15986 fn select_match_ranges(
15987 &mut self,
15988 range: Range<MultiBufferOffset>,
15989 reversed: bool,
15990 replace_newest: bool,
15991 auto_scroll: Option<Autoscroll>,
15992 window: &mut Window,
15993 cx: &mut Context<Editor>,
15994 ) {
15995 self.unfold_ranges(
15996 std::slice::from_ref(&range),
15997 false,
15998 auto_scroll.is_some(),
15999 cx,
16000 );
16001 let effects = if let Some(scroll) = auto_scroll {
16002 SelectionEffects::scroll(scroll)
16003 } else {
16004 SelectionEffects::no_scroll()
16005 };
16006 self.change_selections(effects, window, cx, |s| {
16007 if replace_newest {
16008 s.delete(s.newest_anchor().id);
16009 }
16010 if reversed {
16011 s.insert_range(range.end..range.start);
16012 } else {
16013 s.insert_range(range);
16014 }
16015 });
16016 }
16017
16018 pub fn select_next_match_internal(
16019 &mut self,
16020 display_map: &DisplaySnapshot,
16021 replace_newest: bool,
16022 autoscroll: Option<Autoscroll>,
16023 window: &mut Window,
16024 cx: &mut Context<Self>,
16025 ) -> Result<()> {
16026 let buffer = display_map.buffer_snapshot();
16027 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16028 if let Some(mut select_next_state) = self.select_next_state.take() {
16029 let query = &select_next_state.query;
16030 if !select_next_state.done {
16031 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16032 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16033 let mut next_selected_range = None;
16034
16035 let bytes_after_last_selection =
16036 buffer.bytes_in_range(last_selection.end..buffer.len());
16037 let bytes_before_first_selection =
16038 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
16039 let query_matches = query
16040 .stream_find_iter(bytes_after_last_selection)
16041 .map(|result| (last_selection.end, result))
16042 .chain(
16043 query
16044 .stream_find_iter(bytes_before_first_selection)
16045 .map(|result| (MultiBufferOffset(0), result)),
16046 );
16047
16048 for (start_offset, query_match) in query_matches {
16049 let query_match = query_match.unwrap(); // can only fail due to I/O
16050 let offset_range =
16051 start_offset + query_match.start()..start_offset + query_match.end();
16052
16053 if !select_next_state.wordwise
16054 || (!buffer.is_inside_word(offset_range.start, None)
16055 && !buffer.is_inside_word(offset_range.end, None))
16056 {
16057 let idx = selections
16058 .partition_point(|selection| selection.end <= offset_range.start);
16059 let overlaps = selections
16060 .get(idx)
16061 .map_or(false, |selection| selection.start < offset_range.end);
16062
16063 if !overlaps {
16064 next_selected_range = Some(offset_range);
16065 break;
16066 }
16067 }
16068 }
16069
16070 if let Some(next_selected_range) = next_selected_range {
16071 self.select_match_ranges(
16072 next_selected_range,
16073 last_selection.reversed,
16074 replace_newest,
16075 autoscroll,
16076 window,
16077 cx,
16078 );
16079 } else {
16080 select_next_state.done = true;
16081 }
16082 }
16083
16084 self.select_next_state = Some(select_next_state);
16085 } else {
16086 let mut only_carets = true;
16087 let mut same_text_selected = true;
16088 let mut selected_text = None;
16089
16090 let mut selections_iter = selections.iter().peekable();
16091 while let Some(selection) = selections_iter.next() {
16092 if selection.start != selection.end {
16093 only_carets = false;
16094 }
16095
16096 if same_text_selected {
16097 if selected_text.is_none() {
16098 selected_text =
16099 Some(buffer.text_for_range(selection.range()).collect::<String>());
16100 }
16101
16102 if let Some(next_selection) = selections_iter.peek() {
16103 if next_selection.len() == selection.len() {
16104 let next_selected_text = buffer
16105 .text_for_range(next_selection.range())
16106 .collect::<String>();
16107 if Some(next_selected_text) != selected_text {
16108 same_text_selected = false;
16109 selected_text = None;
16110 }
16111 } else {
16112 same_text_selected = false;
16113 selected_text = None;
16114 }
16115 }
16116 }
16117 }
16118
16119 if only_carets {
16120 for selection in &mut selections {
16121 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16122 selection.start = word_range.start;
16123 selection.end = word_range.end;
16124 selection.goal = SelectionGoal::None;
16125 selection.reversed = false;
16126 self.select_match_ranges(
16127 selection.start..selection.end,
16128 selection.reversed,
16129 replace_newest,
16130 autoscroll,
16131 window,
16132 cx,
16133 );
16134 }
16135
16136 if selections.len() == 1 {
16137 let selection = selections
16138 .last()
16139 .expect("ensured that there's only one selection");
16140 let query = buffer
16141 .text_for_range(selection.start..selection.end)
16142 .collect::<String>();
16143 let is_empty = query.is_empty();
16144 let select_state = SelectNextState {
16145 query: self.build_query(&[query], cx)?,
16146 wordwise: true,
16147 done: is_empty,
16148 };
16149 self.select_next_state = Some(select_state);
16150 } else {
16151 self.select_next_state = None;
16152 }
16153 } else if let Some(selected_text) = selected_text {
16154 self.select_next_state = Some(SelectNextState {
16155 query: self.build_query(&[selected_text], cx)?,
16156 wordwise: false,
16157 done: false,
16158 });
16159 self.select_next_match_internal(
16160 display_map,
16161 replace_newest,
16162 autoscroll,
16163 window,
16164 cx,
16165 )?;
16166 }
16167 }
16168 Ok(())
16169 }
16170
16171 pub fn select_all_matches(
16172 &mut self,
16173 _action: &SelectAllMatches,
16174 window: &mut Window,
16175 cx: &mut Context<Self>,
16176 ) -> Result<()> {
16177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16178
16179 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16180
16181 self.select_next_match_internal(&display_map, false, None, window, cx)?;
16182 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
16183 else {
16184 return Ok(());
16185 };
16186
16187 let mut new_selections = Vec::new();
16188 let initial_selection = self.selections.oldest::<MultiBufferOffset>(&display_map);
16189 let reversed = initial_selection.reversed;
16190 let buffer = display_map.buffer_snapshot();
16191 let query_matches = select_next_state
16192 .query
16193 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
16194
16195 for query_match in query_matches.into_iter() {
16196 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
16197 let offset_range = if reversed {
16198 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
16199 } else {
16200 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
16201 };
16202
16203 let is_partial_word_match = select_next_state.wordwise
16204 && (buffer.is_inside_word(offset_range.start, None)
16205 || buffer.is_inside_word(offset_range.end, None));
16206
16207 let is_initial_selection = MultiBufferOffset(query_match.start())
16208 == initial_selection.start
16209 && MultiBufferOffset(query_match.end()) == initial_selection.end;
16210
16211 if !is_partial_word_match && !is_initial_selection {
16212 new_selections.push(offset_range);
16213 }
16214 }
16215
16216 // Ensure that the initial range is the last selection, as
16217 // `MutableSelectionsCollection::select_ranges` makes the last selection
16218 // the newest selection, which the editor then relies on as the primary
16219 // cursor for scroll targeting. Without this, the last match would then
16220 // be automatically focused when the user started editing the selected
16221 // matches.
16222 let initial_directed_range = if reversed {
16223 initial_selection.end..initial_selection.start
16224 } else {
16225 initial_selection.start..initial_selection.end
16226 };
16227 new_selections.push(initial_directed_range);
16228
16229 select_next_state.done = true;
16230 self.unfold_ranges(&new_selections, false, false, cx);
16231 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16232 selections.select_ranges(new_selections)
16233 });
16234
16235 Ok(())
16236 }
16237
16238 pub fn select_next(
16239 &mut self,
16240 action: &SelectNext,
16241 window: &mut Window,
16242 cx: &mut Context<Self>,
16243 ) -> Result<()> {
16244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16246 self.select_next_match_internal(
16247 &display_map,
16248 action.replace_newest,
16249 Some(Autoscroll::newest()),
16250 window,
16251 cx,
16252 )
16253 }
16254
16255 pub fn select_previous(
16256 &mut self,
16257 action: &SelectPrevious,
16258 window: &mut Window,
16259 cx: &mut Context<Self>,
16260 ) -> Result<()> {
16261 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16262 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16263 let buffer = display_map.buffer_snapshot();
16264 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16265 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16266 let query = &select_prev_state.query;
16267 if !select_prev_state.done {
16268 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16269 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16270 let mut next_selected_range = None;
16271 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16272 let bytes_before_last_selection =
16273 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16274 let bytes_after_first_selection =
16275 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16276 let query_matches = query
16277 .stream_find_iter(bytes_before_last_selection)
16278 .map(|result| (last_selection.start, result))
16279 .chain(
16280 query
16281 .stream_find_iter(bytes_after_first_selection)
16282 .map(|result| (buffer.len(), result)),
16283 );
16284 for (end_offset, query_match) in query_matches {
16285 let query_match = query_match.unwrap(); // can only fail due to I/O
16286 let offset_range =
16287 end_offset - query_match.end()..end_offset - query_match.start();
16288
16289 if !select_prev_state.wordwise
16290 || (!buffer.is_inside_word(offset_range.start, None)
16291 && !buffer.is_inside_word(offset_range.end, None))
16292 {
16293 next_selected_range = Some(offset_range);
16294 break;
16295 }
16296 }
16297
16298 if let Some(next_selected_range) = next_selected_range {
16299 self.select_match_ranges(
16300 next_selected_range,
16301 last_selection.reversed,
16302 action.replace_newest,
16303 Some(Autoscroll::newest()),
16304 window,
16305 cx,
16306 );
16307 } else {
16308 select_prev_state.done = true;
16309 }
16310 }
16311
16312 self.select_prev_state = Some(select_prev_state);
16313 } else {
16314 let mut only_carets = true;
16315 let mut same_text_selected = true;
16316 let mut selected_text = None;
16317
16318 let mut selections_iter = selections.iter().peekable();
16319 while let Some(selection) = selections_iter.next() {
16320 if selection.start != selection.end {
16321 only_carets = false;
16322 }
16323
16324 if same_text_selected {
16325 if selected_text.is_none() {
16326 selected_text =
16327 Some(buffer.text_for_range(selection.range()).collect::<String>());
16328 }
16329
16330 if let Some(next_selection) = selections_iter.peek() {
16331 if next_selection.len() == selection.len() {
16332 let next_selected_text = buffer
16333 .text_for_range(next_selection.range())
16334 .collect::<String>();
16335 if Some(next_selected_text) != selected_text {
16336 same_text_selected = false;
16337 selected_text = None;
16338 }
16339 } else {
16340 same_text_selected = false;
16341 selected_text = None;
16342 }
16343 }
16344 }
16345 }
16346
16347 if only_carets {
16348 for selection in &mut selections {
16349 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16350 selection.start = word_range.start;
16351 selection.end = word_range.end;
16352 selection.goal = SelectionGoal::None;
16353 selection.reversed = false;
16354 self.select_match_ranges(
16355 selection.start..selection.end,
16356 selection.reversed,
16357 action.replace_newest,
16358 Some(Autoscroll::newest()),
16359 window,
16360 cx,
16361 );
16362 }
16363 if selections.len() == 1 {
16364 let selection = selections
16365 .last()
16366 .expect("ensured that there's only one selection");
16367 let query = buffer
16368 .text_for_range(selection.start..selection.end)
16369 .collect::<String>();
16370 let is_empty = query.is_empty();
16371 let select_state = SelectNextState {
16372 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16373 wordwise: true,
16374 done: is_empty,
16375 };
16376 self.select_prev_state = Some(select_state);
16377 } else {
16378 self.select_prev_state = None;
16379 }
16380 } else if let Some(selected_text) = selected_text {
16381 self.select_prev_state = Some(SelectNextState {
16382 query: self
16383 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16384 wordwise: false,
16385 done: false,
16386 });
16387 self.select_previous(action, window, cx)?;
16388 }
16389 }
16390 Ok(())
16391 }
16392
16393 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16394 /// setting the case sensitivity based on the global
16395 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16396 /// editor's settings.
16397 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16398 where
16399 I: IntoIterator<Item = P>,
16400 P: AsRef<[u8]>,
16401 {
16402 let case_sensitive = self
16403 .select_next_is_case_sensitive
16404 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16405
16406 let mut builder = AhoCorasickBuilder::new();
16407 builder.ascii_case_insensitive(!case_sensitive);
16408 builder.build(patterns)
16409 }
16410
16411 pub fn find_next_match(
16412 &mut self,
16413 _: &FindNextMatch,
16414 window: &mut Window,
16415 cx: &mut Context<Self>,
16416 ) -> Result<()> {
16417 let selections = self.selections.disjoint_anchors_arc();
16418 match selections.first() {
16419 Some(first) if selections.len() >= 2 => {
16420 self.change_selections(Default::default(), window, cx, |s| {
16421 s.select_ranges([first.range()]);
16422 });
16423 }
16424 _ => self.select_next(
16425 &SelectNext {
16426 replace_newest: true,
16427 },
16428 window,
16429 cx,
16430 )?,
16431 }
16432 Ok(())
16433 }
16434
16435 pub fn find_previous_match(
16436 &mut self,
16437 _: &FindPreviousMatch,
16438 window: &mut Window,
16439 cx: &mut Context<Self>,
16440 ) -> Result<()> {
16441 let selections = self.selections.disjoint_anchors_arc();
16442 match selections.last() {
16443 Some(last) if selections.len() >= 2 => {
16444 self.change_selections(Default::default(), window, cx, |s| {
16445 s.select_ranges([last.range()]);
16446 });
16447 }
16448 _ => self.select_previous(
16449 &SelectPrevious {
16450 replace_newest: true,
16451 },
16452 window,
16453 cx,
16454 )?,
16455 }
16456 Ok(())
16457 }
16458
16459 pub fn toggle_comments(
16460 &mut self,
16461 action: &ToggleComments,
16462 window: &mut Window,
16463 cx: &mut Context<Self>,
16464 ) {
16465 if self.read_only(cx) {
16466 return;
16467 }
16468 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16469 let text_layout_details = &self.text_layout_details(window, cx);
16470 self.transact(window, cx, |this, window, cx| {
16471 let mut selections = this
16472 .selections
16473 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16474 let mut edits = Vec::new();
16475 let mut selection_edit_ranges = Vec::new();
16476 let mut last_toggled_row = None;
16477 let snapshot = this.buffer.read(cx).read(cx);
16478 let empty_str: Arc<str> = Arc::default();
16479 let mut suffixes_inserted = Vec::new();
16480 let ignore_indent = action.ignore_indent;
16481
16482 fn comment_prefix_range(
16483 snapshot: &MultiBufferSnapshot,
16484 row: MultiBufferRow,
16485 comment_prefix: &str,
16486 comment_prefix_whitespace: &str,
16487 ignore_indent: bool,
16488 ) -> Range<Point> {
16489 let indent_size = if ignore_indent {
16490 0
16491 } else {
16492 snapshot.indent_size_for_line(row).len
16493 };
16494
16495 let start = Point::new(row.0, indent_size);
16496
16497 let mut line_bytes = snapshot
16498 .bytes_in_range(start..snapshot.max_point())
16499 .flatten()
16500 .copied();
16501
16502 // If this line currently begins with the line comment prefix, then record
16503 // the range containing the prefix.
16504 if line_bytes
16505 .by_ref()
16506 .take(comment_prefix.len())
16507 .eq(comment_prefix.bytes())
16508 {
16509 // Include any whitespace that matches the comment prefix.
16510 let matching_whitespace_len = line_bytes
16511 .zip(comment_prefix_whitespace.bytes())
16512 .take_while(|(a, b)| a == b)
16513 .count() as u32;
16514 let end = Point::new(
16515 start.row,
16516 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16517 );
16518 start..end
16519 } else {
16520 start..start
16521 }
16522 }
16523
16524 fn comment_suffix_range(
16525 snapshot: &MultiBufferSnapshot,
16526 row: MultiBufferRow,
16527 comment_suffix: &str,
16528 comment_suffix_has_leading_space: bool,
16529 ) -> Range<Point> {
16530 let end = Point::new(row.0, snapshot.line_len(row));
16531 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16532
16533 let mut line_end_bytes = snapshot
16534 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16535 .flatten()
16536 .copied();
16537
16538 let leading_space_len = if suffix_start_column > 0
16539 && line_end_bytes.next() == Some(b' ')
16540 && comment_suffix_has_leading_space
16541 {
16542 1
16543 } else {
16544 0
16545 };
16546
16547 // If this line currently begins with the line comment prefix, then record
16548 // the range containing the prefix.
16549 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16550 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16551 start..end
16552 } else {
16553 end..end
16554 }
16555 }
16556
16557 // TODO: Handle selections that cross excerpts
16558 for selection in &mut selections {
16559 let start_column = snapshot
16560 .indent_size_for_line(MultiBufferRow(selection.start.row))
16561 .len;
16562 let language = if let Some(language) =
16563 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16564 {
16565 language
16566 } else {
16567 continue;
16568 };
16569
16570 selection_edit_ranges.clear();
16571
16572 // If multiple selections contain a given row, avoid processing that
16573 // row more than once.
16574 let mut start_row = MultiBufferRow(selection.start.row);
16575 if last_toggled_row == Some(start_row) {
16576 start_row = start_row.next_row();
16577 }
16578 let end_row =
16579 if selection.end.row > selection.start.row && selection.end.column == 0 {
16580 MultiBufferRow(selection.end.row - 1)
16581 } else {
16582 MultiBufferRow(selection.end.row)
16583 };
16584 last_toggled_row = Some(end_row);
16585
16586 if start_row > end_row {
16587 continue;
16588 }
16589
16590 // If the language has line comments, toggle those.
16591 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16592
16593 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16594 if ignore_indent {
16595 full_comment_prefixes = full_comment_prefixes
16596 .into_iter()
16597 .map(|s| Arc::from(s.trim_end()))
16598 .collect();
16599 }
16600
16601 if !full_comment_prefixes.is_empty() {
16602 let first_prefix = full_comment_prefixes
16603 .first()
16604 .expect("prefixes is non-empty");
16605 let prefix_trimmed_lengths = full_comment_prefixes
16606 .iter()
16607 .map(|p| p.trim_end_matches(' ').len())
16608 .collect::<SmallVec<[usize; 4]>>();
16609
16610 let mut all_selection_lines_are_comments = true;
16611
16612 for row in start_row.0..=end_row.0 {
16613 let row = MultiBufferRow(row);
16614 if start_row < end_row && snapshot.is_line_blank(row) {
16615 continue;
16616 }
16617
16618 let prefix_range = full_comment_prefixes
16619 .iter()
16620 .zip(prefix_trimmed_lengths.iter().copied())
16621 .map(|(prefix, trimmed_prefix_len)| {
16622 comment_prefix_range(
16623 snapshot.deref(),
16624 row,
16625 &prefix[..trimmed_prefix_len],
16626 &prefix[trimmed_prefix_len..],
16627 ignore_indent,
16628 )
16629 })
16630 .max_by_key(|range| range.end.column - range.start.column)
16631 .expect("prefixes is non-empty");
16632
16633 if prefix_range.is_empty() {
16634 all_selection_lines_are_comments = false;
16635 }
16636
16637 selection_edit_ranges.push(prefix_range);
16638 }
16639
16640 if all_selection_lines_are_comments {
16641 edits.extend(
16642 selection_edit_ranges
16643 .iter()
16644 .cloned()
16645 .map(|range| (range, empty_str.clone())),
16646 );
16647 } else {
16648 let min_column = selection_edit_ranges
16649 .iter()
16650 .map(|range| range.start.column)
16651 .min()
16652 .unwrap_or(0);
16653 edits.extend(selection_edit_ranges.iter().map(|range| {
16654 let position = Point::new(range.start.row, min_column);
16655 (position..position, first_prefix.clone())
16656 }));
16657 }
16658 } else if let Some(BlockCommentConfig {
16659 start: full_comment_prefix,
16660 end: comment_suffix,
16661 ..
16662 }) = language.block_comment()
16663 {
16664 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16665 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16666 let prefix_range = comment_prefix_range(
16667 snapshot.deref(),
16668 start_row,
16669 comment_prefix,
16670 comment_prefix_whitespace,
16671 ignore_indent,
16672 );
16673 let suffix_range = comment_suffix_range(
16674 snapshot.deref(),
16675 end_row,
16676 comment_suffix.trim_start_matches(' '),
16677 comment_suffix.starts_with(' '),
16678 );
16679
16680 if prefix_range.is_empty() || suffix_range.is_empty() {
16681 edits.push((
16682 prefix_range.start..prefix_range.start,
16683 full_comment_prefix.clone(),
16684 ));
16685 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16686 suffixes_inserted.push((end_row, comment_suffix.len()));
16687 } else {
16688 edits.push((prefix_range, empty_str.clone()));
16689 edits.push((suffix_range, empty_str.clone()));
16690 }
16691 } else {
16692 continue;
16693 }
16694 }
16695
16696 drop(snapshot);
16697 this.buffer.update(cx, |buffer, cx| {
16698 buffer.edit(edits, None, cx);
16699 });
16700
16701 // Adjust selections so that they end before any comment suffixes that
16702 // were inserted.
16703 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16704 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16705 let snapshot = this.buffer.read(cx).read(cx);
16706 for selection in &mut selections {
16707 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16708 match row.cmp(&MultiBufferRow(selection.end.row)) {
16709 Ordering::Less => {
16710 suffixes_inserted.next();
16711 continue;
16712 }
16713 Ordering::Greater => break,
16714 Ordering::Equal => {
16715 if selection.end.column == snapshot.line_len(row) {
16716 if selection.is_empty() {
16717 selection.start.column -= suffix_len as u32;
16718 }
16719 selection.end.column -= suffix_len as u32;
16720 }
16721 break;
16722 }
16723 }
16724 }
16725 }
16726
16727 drop(snapshot);
16728 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16729
16730 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16731 let selections_on_single_row = selections.windows(2).all(|selections| {
16732 selections[0].start.row == selections[1].start.row
16733 && selections[0].end.row == selections[1].end.row
16734 && selections[0].start.row == selections[0].end.row
16735 });
16736 let selections_selecting = selections
16737 .iter()
16738 .any(|selection| selection.start != selection.end);
16739 let advance_downwards = action.advance_downwards
16740 && selections_on_single_row
16741 && !selections_selecting
16742 && !matches!(this.mode, EditorMode::SingleLine);
16743
16744 if advance_downwards {
16745 let snapshot = this.buffer.read(cx).snapshot(cx);
16746
16747 this.change_selections(Default::default(), window, cx, |s| {
16748 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16749 let mut point = display_point.to_point(display_snapshot);
16750 point.row += 1;
16751 point = snapshot.clip_point(point, Bias::Left);
16752 let display_point = point.to_display_point(display_snapshot);
16753 let goal = SelectionGoal::HorizontalPosition(
16754 display_snapshot
16755 .x_for_display_point(display_point, text_layout_details)
16756 .into(),
16757 );
16758 (display_point, goal)
16759 })
16760 });
16761 }
16762 });
16763 }
16764
16765 pub fn select_enclosing_symbol(
16766 &mut self,
16767 _: &SelectEnclosingSymbol,
16768 window: &mut Window,
16769 cx: &mut Context<Self>,
16770 ) {
16771 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16772
16773 let buffer = self.buffer.read(cx).snapshot(cx);
16774 let old_selections = self
16775 .selections
16776 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16777 .into_boxed_slice();
16778
16779 fn update_selection(
16780 selection: &Selection<MultiBufferOffset>,
16781 buffer_snap: &MultiBufferSnapshot,
16782 ) -> Option<Selection<MultiBufferOffset>> {
16783 let cursor = selection.head();
16784 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16785 for symbol in symbols.iter().rev() {
16786 let start = symbol.range.start.to_offset(buffer_snap);
16787 let end = symbol.range.end.to_offset(buffer_snap);
16788 let new_range = start..end;
16789 if start < selection.start || end > selection.end {
16790 return Some(Selection {
16791 id: selection.id,
16792 start: new_range.start,
16793 end: new_range.end,
16794 goal: SelectionGoal::None,
16795 reversed: selection.reversed,
16796 });
16797 }
16798 }
16799 None
16800 }
16801
16802 let mut selected_larger_symbol = false;
16803 let new_selections = old_selections
16804 .iter()
16805 .map(|selection| match update_selection(selection, &buffer) {
16806 Some(new_selection) => {
16807 if new_selection.range() != selection.range() {
16808 selected_larger_symbol = true;
16809 }
16810 new_selection
16811 }
16812 None => selection.clone(),
16813 })
16814 .collect::<Vec<_>>();
16815
16816 if selected_larger_symbol {
16817 self.change_selections(Default::default(), window, cx, |s| {
16818 s.select(new_selections);
16819 });
16820 }
16821 }
16822
16823 pub fn select_larger_syntax_node(
16824 &mut self,
16825 _: &SelectLargerSyntaxNode,
16826 window: &mut Window,
16827 cx: &mut Context<Self>,
16828 ) {
16829 let Some(visible_row_count) = self.visible_row_count() else {
16830 return;
16831 };
16832 let old_selections: Box<[_]> = self
16833 .selections
16834 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16835 .into();
16836 if old_selections.is_empty() {
16837 return;
16838 }
16839
16840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16841
16842 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16843 let buffer = self.buffer.read(cx).snapshot(cx);
16844
16845 let mut selected_larger_node = false;
16846 let mut new_selections = old_selections
16847 .iter()
16848 .map(|selection| {
16849 let old_range = selection.start..selection.end;
16850
16851 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16852 // manually select word at selection
16853 if ["string_content", "inline"].contains(&node.kind()) {
16854 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16855 // ignore if word is already selected
16856 if !word_range.is_empty() && old_range != word_range {
16857 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16858 // only select word if start and end point belongs to same word
16859 if word_range == last_word_range {
16860 selected_larger_node = true;
16861 return Selection {
16862 id: selection.id,
16863 start: word_range.start,
16864 end: word_range.end,
16865 goal: SelectionGoal::None,
16866 reversed: selection.reversed,
16867 };
16868 }
16869 }
16870 }
16871 }
16872
16873 let mut new_range = old_range.clone();
16874 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16875 new_range = range;
16876 if !node.is_named() {
16877 continue;
16878 }
16879 if !display_map.intersects_fold(new_range.start)
16880 && !display_map.intersects_fold(new_range.end)
16881 {
16882 break;
16883 }
16884 }
16885
16886 selected_larger_node |= new_range != old_range;
16887 Selection {
16888 id: selection.id,
16889 start: new_range.start,
16890 end: new_range.end,
16891 goal: SelectionGoal::None,
16892 reversed: selection.reversed,
16893 }
16894 })
16895 .collect::<Vec<_>>();
16896
16897 if !selected_larger_node {
16898 return; // don't put this call in the history
16899 }
16900
16901 // scroll based on transformation done to the last selection created by the user
16902 let (last_old, last_new) = old_selections
16903 .last()
16904 .zip(new_selections.last().cloned())
16905 .expect("old_selections isn't empty");
16906
16907 let is_selection_reversed = if new_selections.len() == 1 {
16908 let should_be_reversed = last_old.start != last_new.start;
16909 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
16910 should_be_reversed
16911 } else {
16912 last_new.reversed
16913 };
16914
16915 if selected_larger_node {
16916 self.select_syntax_node_history.disable_clearing = true;
16917 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16918 s.select(new_selections.clone());
16919 });
16920 self.select_syntax_node_history.disable_clearing = false;
16921 }
16922
16923 let start_row = last_new.start.to_display_point(&display_map).row().0;
16924 let end_row = last_new.end.to_display_point(&display_map).row().0;
16925 let selection_height = end_row - start_row + 1;
16926 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16927
16928 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16929 let scroll_behavior = if fits_on_the_screen {
16930 self.request_autoscroll(Autoscroll::fit(), cx);
16931 SelectSyntaxNodeScrollBehavior::FitSelection
16932 } else if is_selection_reversed {
16933 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16934 SelectSyntaxNodeScrollBehavior::CursorTop
16935 } else {
16936 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16937 SelectSyntaxNodeScrollBehavior::CursorBottom
16938 };
16939
16940 let old_selections: Box<[Selection<Anchor>]> = old_selections
16941 .iter()
16942 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16943 .collect();
16944 self.select_syntax_node_history.push((
16945 old_selections,
16946 scroll_behavior,
16947 is_selection_reversed,
16948 ));
16949 }
16950
16951 pub fn select_smaller_syntax_node(
16952 &mut self,
16953 _: &SelectSmallerSyntaxNode,
16954 window: &mut Window,
16955 cx: &mut Context<Self>,
16956 ) {
16957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16958
16959 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16960 self.select_syntax_node_history.pop()
16961 {
16962 if let Some(selection) = selections.last_mut() {
16963 selection.reversed = is_selection_reversed;
16964 }
16965
16966 let snapshot = self.buffer.read(cx).snapshot(cx);
16967 let selections: Vec<Selection<MultiBufferOffset>> = selections
16968 .iter()
16969 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16970 .collect();
16971
16972 self.select_syntax_node_history.disable_clearing = true;
16973 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16974 s.select(selections);
16975 });
16976 self.select_syntax_node_history.disable_clearing = false;
16977
16978 match scroll_behavior {
16979 SelectSyntaxNodeScrollBehavior::CursorTop => {
16980 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16981 }
16982 SelectSyntaxNodeScrollBehavior::FitSelection => {
16983 self.request_autoscroll(Autoscroll::fit(), cx);
16984 }
16985 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16986 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16987 }
16988 }
16989 }
16990 }
16991
16992 pub fn unwrap_syntax_node(
16993 &mut self,
16994 _: &UnwrapSyntaxNode,
16995 window: &mut Window,
16996 cx: &mut Context<Self>,
16997 ) {
16998 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16999
17000 let buffer = self.buffer.read(cx).snapshot(cx);
17001 let selections = self
17002 .selections
17003 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17004 .into_iter()
17005 // subtracting the offset requires sorting
17006 .sorted_by_key(|i| i.start);
17007
17008 let full_edits = selections
17009 .into_iter()
17010 .filter_map(|selection| {
17011 let child = if selection.is_empty()
17012 && let Some((_, ancestor_range)) =
17013 buffer.syntax_ancestor(selection.start..selection.end)
17014 {
17015 ancestor_range
17016 } else {
17017 selection.range()
17018 };
17019
17020 let mut parent = child.clone();
17021 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
17022 parent = ancestor_range;
17023 if parent.start < child.start || parent.end > child.end {
17024 break;
17025 }
17026 }
17027
17028 if parent == child {
17029 return None;
17030 }
17031 let text = buffer.text_for_range(child).collect::<String>();
17032 Some((selection.id, parent, text))
17033 })
17034 .collect::<Vec<_>>();
17035 if full_edits.is_empty() {
17036 return;
17037 }
17038
17039 self.transact(window, cx, |this, window, cx| {
17040 this.buffer.update(cx, |buffer, cx| {
17041 buffer.edit(
17042 full_edits
17043 .iter()
17044 .map(|(_, p, t)| (p.clone(), t.clone()))
17045 .collect::<Vec<_>>(),
17046 None,
17047 cx,
17048 );
17049 });
17050 this.change_selections(Default::default(), window, cx, |s| {
17051 let mut offset = 0;
17052 let mut selections = vec![];
17053 for (id, parent, text) in full_edits {
17054 let start = parent.start - offset;
17055 offset += (parent.end - parent.start) - text.len();
17056 selections.push(Selection {
17057 id,
17058 start,
17059 end: start + text.len(),
17060 reversed: false,
17061 goal: Default::default(),
17062 });
17063 }
17064 s.select(selections);
17065 });
17066 });
17067 }
17068
17069 pub fn select_next_syntax_node(
17070 &mut self,
17071 _: &SelectNextSyntaxNode,
17072 window: &mut Window,
17073 cx: &mut Context<Self>,
17074 ) {
17075 let old_selections = self.selections.all_anchors(&self.display_snapshot(cx));
17076 if old_selections.is_empty() {
17077 return;
17078 }
17079
17080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17081
17082 let buffer = self.buffer.read(cx).snapshot(cx);
17083 let mut selected_sibling = false;
17084
17085 let new_selections = old_selections
17086 .iter()
17087 .map(|selection| {
17088 let old_range =
17089 selection.start.to_offset(&buffer)..selection.end.to_offset(&buffer);
17090 if let Some(results) = buffer.map_excerpt_ranges(
17091 old_range,
17092 |buf, _excerpt_range, input_buffer_range| {
17093 let Some(node) = buf.syntax_next_sibling(input_buffer_range) else {
17094 return Vec::new();
17095 };
17096 vec![(
17097 BufferOffset(node.byte_range().start)
17098 ..BufferOffset(node.byte_range().end),
17099 (),
17100 )]
17101 },
17102 ) && let [(new_range, _)] = results.as_slice()
17103 {
17104 selected_sibling = true;
17105 let new_range =
17106 buffer.anchor_after(new_range.start)..buffer.anchor_before(new_range.end);
17107 Selection {
17108 id: selection.id,
17109 start: new_range.start,
17110 end: new_range.end,
17111 goal: SelectionGoal::None,
17112 reversed: selection.reversed,
17113 }
17114 } else {
17115 selection.clone()
17116 }
17117 })
17118 .collect::<Vec<_>>();
17119
17120 if selected_sibling {
17121 self.change_selections(
17122 SelectionEffects::scroll(Autoscroll::fit()),
17123 window,
17124 cx,
17125 |s| {
17126 s.select(new_selections);
17127 },
17128 );
17129 }
17130 }
17131
17132 pub fn select_prev_syntax_node(
17133 &mut self,
17134 _: &SelectPreviousSyntaxNode,
17135 window: &mut Window,
17136 cx: &mut Context<Self>,
17137 ) {
17138 let old_selections: Arc<[_]> = self.selections.all_anchors(&self.display_snapshot(cx));
17139
17140 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17141
17142 let multibuffer_snapshot = self.buffer.read(cx).snapshot(cx);
17143 let mut selected_sibling = false;
17144
17145 let new_selections = old_selections
17146 .iter()
17147 .map(|selection| {
17148 let old_range = selection.start.to_offset(&multibuffer_snapshot)
17149 ..selection.end.to_offset(&multibuffer_snapshot);
17150 if let Some(results) = multibuffer_snapshot.map_excerpt_ranges(
17151 old_range,
17152 |buf, _excerpt_range, input_buffer_range| {
17153 let Some(node) = buf.syntax_prev_sibling(input_buffer_range) else {
17154 return Vec::new();
17155 };
17156 vec![(
17157 BufferOffset(node.byte_range().start)
17158 ..BufferOffset(node.byte_range().end),
17159 (),
17160 )]
17161 },
17162 ) && let [(new_range, _)] = results.as_slice()
17163 {
17164 selected_sibling = true;
17165 let new_range = multibuffer_snapshot.anchor_after(new_range.start)
17166 ..multibuffer_snapshot.anchor_before(new_range.end);
17167 Selection {
17168 id: selection.id,
17169 start: new_range.start,
17170 end: new_range.end,
17171 goal: SelectionGoal::None,
17172 reversed: selection.reversed,
17173 }
17174 } else {
17175 selection.clone()
17176 }
17177 })
17178 .collect::<Vec<_>>();
17179
17180 if selected_sibling {
17181 self.change_selections(
17182 SelectionEffects::scroll(Autoscroll::fit()),
17183 window,
17184 cx,
17185 |s| {
17186 s.select(new_selections);
17187 },
17188 );
17189 }
17190 }
17191
17192 pub fn move_to_start_of_larger_syntax_node(
17193 &mut self,
17194 _: &MoveToStartOfLargerSyntaxNode,
17195 window: &mut Window,
17196 cx: &mut Context<Self>,
17197 ) {
17198 self.move_cursors_to_syntax_nodes(window, cx, false);
17199 }
17200
17201 pub fn move_to_end_of_larger_syntax_node(
17202 &mut self,
17203 _: &MoveToEndOfLargerSyntaxNode,
17204 window: &mut Window,
17205 cx: &mut Context<Self>,
17206 ) {
17207 self.move_cursors_to_syntax_nodes(window, cx, true);
17208 }
17209
17210 fn find_syntax_node_boundary(
17211 &self,
17212 selection_pos: MultiBufferOffset,
17213 move_to_end: bool,
17214 display_map: &DisplaySnapshot,
17215 buffer: &MultiBufferSnapshot,
17216 ) -> MultiBufferOffset {
17217 let old_range = selection_pos..selection_pos;
17218 let mut new_pos = selection_pos;
17219 let mut search_range = old_range;
17220 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17221 search_range = range.clone();
17222 if !node.is_named()
17223 || display_map.intersects_fold(range.start)
17224 || display_map.intersects_fold(range.end)
17225 // If cursor is already at the end of the syntax node, continue searching
17226 || (move_to_end && range.end == selection_pos)
17227 // If cursor is already at the start of the syntax node, continue searching
17228 || (!move_to_end && range.start == selection_pos)
17229 {
17230 continue;
17231 }
17232
17233 // If we found a string_content node, find the largest parent that is still string_content
17234 // Enables us to skip to the end of strings without taking multiple steps inside the string
17235 let (_, final_range) = if node.kind() == "string_content" {
17236 let mut current_node = node;
17237 let mut current_range = range;
17238 while let Some((parent, parent_range)) =
17239 buffer.syntax_ancestor(current_range.clone())
17240 {
17241 if parent.kind() == "string_content" {
17242 current_node = parent;
17243 current_range = parent_range;
17244 } else {
17245 break;
17246 }
17247 }
17248
17249 (current_node, current_range)
17250 } else {
17251 (node, range)
17252 };
17253
17254 new_pos = if move_to_end {
17255 final_range.end
17256 } else {
17257 final_range.start
17258 };
17259
17260 break;
17261 }
17262
17263 new_pos
17264 }
17265
17266 fn move_cursors_to_syntax_nodes(
17267 &mut self,
17268 window: &mut Window,
17269 cx: &mut Context<Self>,
17270 move_to_end: bool,
17271 ) -> bool {
17272 let old_selections: Box<[_]> = self
17273 .selections
17274 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17275 .into();
17276 if old_selections.is_empty() {
17277 return false;
17278 }
17279
17280 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17281
17282 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17283 let buffer = self.buffer.read(cx).snapshot(cx);
17284
17285 let mut any_cursor_moved = false;
17286 let new_selections = old_selections
17287 .iter()
17288 .map(|selection| {
17289 if !selection.is_empty() {
17290 return selection.clone();
17291 }
17292
17293 let selection_pos = selection.head();
17294 let new_pos = self.find_syntax_node_boundary(
17295 selection_pos,
17296 move_to_end,
17297 &display_map,
17298 &buffer,
17299 );
17300
17301 any_cursor_moved |= new_pos != selection_pos;
17302
17303 Selection {
17304 id: selection.id,
17305 start: new_pos,
17306 end: new_pos,
17307 goal: SelectionGoal::None,
17308 reversed: false,
17309 }
17310 })
17311 .collect::<Vec<_>>();
17312
17313 self.change_selections(Default::default(), window, cx, |s| {
17314 s.select(new_selections);
17315 });
17316 self.request_autoscroll(Autoscroll::newest(), cx);
17317
17318 any_cursor_moved
17319 }
17320
17321 pub fn select_to_start_of_larger_syntax_node(
17322 &mut self,
17323 _: &SelectToStartOfLargerSyntaxNode,
17324 window: &mut Window,
17325 cx: &mut Context<Self>,
17326 ) {
17327 self.select_to_syntax_nodes(window, cx, false);
17328 }
17329
17330 pub fn select_to_end_of_larger_syntax_node(
17331 &mut self,
17332 _: &SelectToEndOfLargerSyntaxNode,
17333 window: &mut Window,
17334 cx: &mut Context<Self>,
17335 ) {
17336 self.select_to_syntax_nodes(window, cx, true);
17337 }
17338
17339 fn select_to_syntax_nodes(
17340 &mut self,
17341 window: &mut Window,
17342 cx: &mut Context<Self>,
17343 move_to_end: bool,
17344 ) {
17345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17346
17347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17348 let buffer = self.buffer.read(cx).snapshot(cx);
17349 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17350
17351 let new_selections = old_selections
17352 .iter()
17353 .map(|selection| {
17354 let new_pos = self.find_syntax_node_boundary(
17355 selection.head(),
17356 move_to_end,
17357 &display_map,
17358 &buffer,
17359 );
17360
17361 let mut new_selection = selection.clone();
17362 new_selection.set_head(new_pos, SelectionGoal::None);
17363 new_selection
17364 })
17365 .collect::<Vec<_>>();
17366
17367 self.change_selections(Default::default(), window, cx, |s| {
17368 s.select(new_selections);
17369 });
17370 }
17371
17372 pub fn move_to_enclosing_bracket(
17373 &mut self,
17374 _: &MoveToEnclosingBracket,
17375 window: &mut Window,
17376 cx: &mut Context<Self>,
17377 ) {
17378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17379 self.change_selections(Default::default(), window, cx, |s| {
17380 s.move_offsets_with(&mut |snapshot, selection| {
17381 let Some(enclosing_bracket_ranges) =
17382 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17383 else {
17384 return;
17385 };
17386
17387 let mut best_length = usize::MAX;
17388 let mut best_inside = false;
17389 let mut best_in_bracket_range = false;
17390 let mut best_destination = None;
17391 for (open, close) in enclosing_bracket_ranges {
17392 let close = close.to_inclusive();
17393 let length = *close.end() - open.start;
17394 let inside = selection.start >= open.end && selection.end <= *close.start();
17395 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17396 || close.contains(&selection.head());
17397
17398 // If best is next to a bracket and current isn't, skip
17399 if !in_bracket_range && best_in_bracket_range {
17400 continue;
17401 }
17402
17403 // Prefer smaller lengths unless best is inside and current isn't
17404 if length > best_length && (best_inside || !inside) {
17405 continue;
17406 }
17407
17408 best_length = length;
17409 best_inside = inside;
17410 best_in_bracket_range = in_bracket_range;
17411 best_destination = Some(
17412 if close.contains(&selection.start) && close.contains(&selection.end) {
17413 if inside { open.end } else { open.start }
17414 } else if inside {
17415 *close.start()
17416 } else {
17417 *close.end()
17418 },
17419 );
17420 }
17421
17422 if let Some(destination) = best_destination {
17423 selection.collapse_to(destination, SelectionGoal::None);
17424 }
17425 })
17426 });
17427 }
17428
17429 pub fn undo_selection(
17430 &mut self,
17431 _: &UndoSelection,
17432 window: &mut Window,
17433 cx: &mut Context<Self>,
17434 ) {
17435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17436 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17437 self.selection_history.mode = SelectionHistoryMode::Undoing;
17438 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17439 this.end_selection(window, cx);
17440 this.change_selections(
17441 SelectionEffects::scroll(Autoscroll::newest()),
17442 window,
17443 cx,
17444 |s| s.select_anchors(entry.selections.to_vec()),
17445 );
17446 });
17447 self.selection_history.mode = SelectionHistoryMode::Normal;
17448
17449 self.select_next_state = entry.select_next_state;
17450 self.select_prev_state = entry.select_prev_state;
17451 self.add_selections_state = entry.add_selections_state;
17452 }
17453 }
17454
17455 pub fn redo_selection(
17456 &mut self,
17457 _: &RedoSelection,
17458 window: &mut Window,
17459 cx: &mut Context<Self>,
17460 ) {
17461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17462 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17463 self.selection_history.mode = SelectionHistoryMode::Redoing;
17464 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17465 this.end_selection(window, cx);
17466 this.change_selections(
17467 SelectionEffects::scroll(Autoscroll::newest()),
17468 window,
17469 cx,
17470 |s| s.select_anchors(entry.selections.to_vec()),
17471 );
17472 });
17473 self.selection_history.mode = SelectionHistoryMode::Normal;
17474
17475 self.select_next_state = entry.select_next_state;
17476 self.select_prev_state = entry.select_prev_state;
17477 self.add_selections_state = entry.add_selections_state;
17478 }
17479 }
17480
17481 pub fn expand_excerpts(
17482 &mut self,
17483 action: &ExpandExcerpts,
17484 _: &mut Window,
17485 cx: &mut Context<Self>,
17486 ) {
17487 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17488 }
17489
17490 pub fn expand_excerpts_down(
17491 &mut self,
17492 action: &ExpandExcerptsDown,
17493 _: &mut Window,
17494 cx: &mut Context<Self>,
17495 ) {
17496 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17497 }
17498
17499 pub fn expand_excerpts_up(
17500 &mut self,
17501 action: &ExpandExcerptsUp,
17502 _: &mut Window,
17503 cx: &mut Context<Self>,
17504 ) {
17505 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17506 }
17507
17508 pub fn expand_excerpts_for_direction(
17509 &mut self,
17510 lines: u32,
17511 direction: ExpandExcerptDirection,
17512 cx: &mut Context<Self>,
17513 ) {
17514 let selections = self.selections.disjoint_anchors_arc();
17515
17516 let lines = if lines == 0 {
17517 EditorSettings::get_global(cx).expand_excerpt_lines
17518 } else {
17519 lines
17520 };
17521
17522 let snapshot = self.buffer.read(cx).snapshot(cx);
17523 let excerpt_anchors = selections
17524 .iter()
17525 .flat_map(|selection| {
17526 snapshot
17527 .range_to_buffer_ranges(selection.range())
17528 .into_iter()
17529 .filter_map(|(buffer_snapshot, range, _)| {
17530 snapshot.anchor_in_excerpt(buffer_snapshot.anchor_after(range.start))
17531 })
17532 })
17533 .collect::<Vec<_>>();
17534
17535 if self.delegate_expand_excerpts {
17536 cx.emit(EditorEvent::ExpandExcerptsRequested {
17537 excerpt_anchors,
17538 lines,
17539 direction,
17540 });
17541 return;
17542 }
17543
17544 self.buffer.update(cx, |buffer, cx| {
17545 buffer.expand_excerpts(excerpt_anchors, lines, direction, cx)
17546 })
17547 }
17548
17549 pub(crate) fn expand_excerpt(
17550 &mut self,
17551 excerpt_anchor: Anchor,
17552 direction: ExpandExcerptDirection,
17553 window: &mut Window,
17554 cx: &mut Context<Self>,
17555 ) {
17556 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17557
17558 if self.delegate_expand_excerpts {
17559 cx.emit(EditorEvent::ExpandExcerptsRequested {
17560 excerpt_anchors: vec![excerpt_anchor],
17561 lines: lines_to_expand,
17562 direction,
17563 });
17564 return;
17565 }
17566
17567 let current_scroll_position = self.scroll_position(cx);
17568 let mut scroll = None;
17569
17570 if direction == ExpandExcerptDirection::Down {
17571 let multi_buffer = self.buffer.read(cx);
17572 let snapshot = multi_buffer.snapshot(cx);
17573 if let Some((buffer_snapshot, excerpt_range)) =
17574 snapshot.excerpt_containing(excerpt_anchor..excerpt_anchor)
17575 {
17576 let excerpt_end_row =
17577 Point::from_anchor(&excerpt_range.context.end, &buffer_snapshot).row;
17578 let last_row = buffer_snapshot.max_point().row;
17579 let lines_below = last_row.saturating_sub(excerpt_end_row);
17580 if lines_below >= lines_to_expand {
17581 scroll = Some(
17582 current_scroll_position
17583 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17584 );
17585 }
17586 }
17587 }
17588 if direction == ExpandExcerptDirection::Up
17589 && self
17590 .buffer
17591 .read(cx)
17592 .snapshot(cx)
17593 .excerpt_before(excerpt_anchor)
17594 .is_none()
17595 {
17596 scroll = Some(current_scroll_position);
17597 }
17598
17599 self.buffer.update(cx, |buffer, cx| {
17600 buffer.expand_excerpts([excerpt_anchor], lines_to_expand, direction, cx)
17601 });
17602
17603 if let Some(new_scroll_position) = scroll {
17604 self.set_scroll_position(new_scroll_position, window, cx);
17605 }
17606 }
17607
17608 pub fn go_to_singleton_buffer_point(
17609 &mut self,
17610 point: Point,
17611 window: &mut Window,
17612 cx: &mut Context<Self>,
17613 ) {
17614 self.go_to_singleton_buffer_range(point..point, window, cx);
17615 }
17616
17617 pub fn go_to_singleton_buffer_range(
17618 &mut self,
17619 range: Range<Point>,
17620 window: &mut Window,
17621 cx: &mut Context<Self>,
17622 ) {
17623 let multibuffer = self.buffer().read(cx);
17624 if !multibuffer.is_singleton() {
17625 return;
17626 };
17627 let anchor_range = range.to_anchors(&multibuffer.snapshot(cx));
17628 self.change_selections(
17629 SelectionEffects::default().nav_history(true),
17630 window,
17631 cx,
17632 |s| s.select_anchor_ranges([anchor_range]),
17633 );
17634 }
17635
17636 pub fn go_to_diagnostic(
17637 &mut self,
17638 action: &GoToDiagnostic,
17639 window: &mut Window,
17640 cx: &mut Context<Self>,
17641 ) {
17642 if !self.diagnostics_enabled() {
17643 return;
17644 }
17645 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17646 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17647 }
17648
17649 pub fn go_to_prev_diagnostic(
17650 &mut self,
17651 action: &GoToPreviousDiagnostic,
17652 window: &mut Window,
17653 cx: &mut Context<Self>,
17654 ) {
17655 if !self.diagnostics_enabled() {
17656 return;
17657 }
17658 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17659 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17660 }
17661
17662 pub fn go_to_diagnostic_impl(
17663 &mut self,
17664 direction: Direction,
17665 severity: GoToDiagnosticSeverityFilter,
17666 window: &mut Window,
17667 cx: &mut Context<Self>,
17668 ) {
17669 let buffer = self.buffer.read(cx).snapshot(cx);
17670 let selection = self
17671 .selections
17672 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17673
17674 let mut active_group_id = None;
17675 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17676 && active_group.active_range.start.to_offset(&buffer) == selection.start
17677 {
17678 active_group_id = Some(active_group.group_id);
17679 }
17680
17681 fn filtered<'a>(
17682 severity: GoToDiagnosticSeverityFilter,
17683 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17684 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17685 diagnostics
17686 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17687 .filter(|entry| entry.range.start != entry.range.end)
17688 .filter(|entry| !entry.diagnostic.is_unnecessary)
17689 }
17690
17691 let before = filtered(
17692 severity,
17693 buffer
17694 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17695 .filter(|entry| entry.range.start <= selection.start),
17696 );
17697 let after = filtered(
17698 severity,
17699 buffer
17700 .diagnostics_in_range(selection.start..buffer.len())
17701 .filter(|entry| entry.range.start >= selection.start),
17702 );
17703
17704 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17705 if direction == Direction::Prev {
17706 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17707 {
17708 for diagnostic in prev_diagnostics.into_iter().rev() {
17709 if diagnostic.range.start != selection.start
17710 || active_group_id
17711 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17712 {
17713 found = Some(diagnostic);
17714 break 'outer;
17715 }
17716 }
17717 }
17718 } else {
17719 for diagnostic in after.chain(before) {
17720 if diagnostic.range.start != selection.start
17721 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17722 {
17723 found = Some(diagnostic);
17724 break;
17725 }
17726 }
17727 }
17728 let Some(next_diagnostic) = found else {
17729 return;
17730 };
17731
17732 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17733 let Some((buffer_anchor, _)) = buffer.anchor_to_buffer_anchor(next_diagnostic_start) else {
17734 return;
17735 };
17736 let buffer_id = buffer_anchor.buffer_id;
17737 let snapshot = self.snapshot(window, cx);
17738 if snapshot.intersects_fold(next_diagnostic.range.start) {
17739 self.unfold_ranges(
17740 std::slice::from_ref(&next_diagnostic.range),
17741 true,
17742 false,
17743 cx,
17744 );
17745 }
17746 self.change_selections(Default::default(), window, cx, |s| {
17747 s.select_ranges(vec![
17748 next_diagnostic.range.start..next_diagnostic.range.start,
17749 ])
17750 });
17751 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17752 self.refresh_edit_prediction(false, true, window, cx);
17753 }
17754
17755 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17757 let snapshot = self.snapshot(window, cx);
17758 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17759 self.go_to_hunk_before_or_after_position(
17760 &snapshot,
17761 selection.head(),
17762 Direction::Next,
17763 true,
17764 window,
17765 cx,
17766 );
17767 }
17768
17769 pub fn go_to_hunk_before_or_after_position(
17770 &mut self,
17771 snapshot: &EditorSnapshot,
17772 position: Point,
17773 direction: Direction,
17774 wrap_around: bool,
17775 window: &mut Window,
17776 cx: &mut Context<Editor>,
17777 ) {
17778 let row = if direction == Direction::Next {
17779 self.hunk_after_position(snapshot, position, wrap_around)
17780 .map(|hunk| hunk.row_range.start)
17781 } else {
17782 self.hunk_before_position(snapshot, position, wrap_around)
17783 };
17784
17785 if let Some(row) = row {
17786 let destination = Point::new(row.0, 0);
17787 let autoscroll = Autoscroll::center();
17788
17789 self.unfold_ranges(&[destination..destination], false, false, cx);
17790 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17791 s.select_ranges([destination..destination]);
17792 });
17793 }
17794 }
17795
17796 fn hunk_after_position(
17797 &mut self,
17798 snapshot: &EditorSnapshot,
17799 position: Point,
17800 wrap_around: bool,
17801 ) -> Option<MultiBufferDiffHunk> {
17802 let result = snapshot
17803 .buffer_snapshot()
17804 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17805 .find(|hunk| hunk.row_range.start.0 > position.row);
17806
17807 if wrap_around {
17808 result.or_else(|| {
17809 snapshot
17810 .buffer_snapshot()
17811 .diff_hunks_in_range(Point::zero()..position)
17812 .find(|hunk| hunk.row_range.end.0 < position.row)
17813 })
17814 } else {
17815 result
17816 }
17817 }
17818
17819 fn go_to_prev_hunk(
17820 &mut self,
17821 _: &GoToPreviousHunk,
17822 window: &mut Window,
17823 cx: &mut Context<Self>,
17824 ) {
17825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17826 let snapshot = self.snapshot(window, cx);
17827 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17828 self.go_to_hunk_before_or_after_position(
17829 &snapshot,
17830 selection.head(),
17831 Direction::Prev,
17832 true,
17833 window,
17834 cx,
17835 );
17836 }
17837
17838 fn hunk_before_position(
17839 &mut self,
17840 snapshot: &EditorSnapshot,
17841 position: Point,
17842 wrap_around: bool,
17843 ) -> Option<MultiBufferRow> {
17844 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17845
17846 if wrap_around {
17847 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17848 } else {
17849 result
17850 }
17851 }
17852
17853 fn go_to_next_change(
17854 &mut self,
17855 _: &GoToNextChange,
17856 window: &mut Window,
17857 cx: &mut Context<Self>,
17858 ) {
17859 if let Some(selections) = self
17860 .change_list
17861 .next_change(1, Direction::Next)
17862 .map(|s| s.to_vec())
17863 {
17864 self.change_selections(Default::default(), window, cx, |s| {
17865 let map = s.display_snapshot();
17866 s.select_display_ranges(selections.iter().map(|a| {
17867 let point = a.to_display_point(&map);
17868 point..point
17869 }))
17870 })
17871 }
17872 }
17873
17874 fn go_to_previous_change(
17875 &mut self,
17876 _: &GoToPreviousChange,
17877 window: &mut Window,
17878 cx: &mut Context<Self>,
17879 ) {
17880 if let Some(selections) = self
17881 .change_list
17882 .next_change(1, Direction::Prev)
17883 .map(|s| s.to_vec())
17884 {
17885 self.change_selections(Default::default(), window, cx, |s| {
17886 let map = s.display_snapshot();
17887 s.select_display_ranges(selections.iter().map(|a| {
17888 let point = a.to_display_point(&map);
17889 point..point
17890 }))
17891 })
17892 }
17893 }
17894
17895 pub fn go_to_next_document_highlight(
17896 &mut self,
17897 _: &GoToNextDocumentHighlight,
17898 window: &mut Window,
17899 cx: &mut Context<Self>,
17900 ) {
17901 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17902 }
17903
17904 pub fn go_to_prev_document_highlight(
17905 &mut self,
17906 _: &GoToPreviousDocumentHighlight,
17907 window: &mut Window,
17908 cx: &mut Context<Self>,
17909 ) {
17910 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17911 }
17912
17913 pub fn go_to_document_highlight_before_or_after_position(
17914 &mut self,
17915 direction: Direction,
17916 window: &mut Window,
17917 cx: &mut Context<Editor>,
17918 ) {
17919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17920 let snapshot = self.snapshot(window, cx);
17921 let buffer = &snapshot.buffer_snapshot();
17922 let position = self
17923 .selections
17924 .newest::<Point>(&snapshot.display_snapshot)
17925 .head();
17926 let anchor_position = buffer.anchor_after(position);
17927
17928 // Get all document highlights (both read and write)
17929 let mut all_highlights = Vec::new();
17930
17931 if let Some((_, read_highlights)) = self
17932 .background_highlights
17933 .get(&HighlightKey::DocumentHighlightRead)
17934 {
17935 all_highlights.extend(read_highlights.iter());
17936 }
17937
17938 if let Some((_, write_highlights)) = self
17939 .background_highlights
17940 .get(&HighlightKey::DocumentHighlightWrite)
17941 {
17942 all_highlights.extend(write_highlights.iter());
17943 }
17944
17945 if all_highlights.is_empty() {
17946 return;
17947 }
17948
17949 // Sort highlights by position
17950 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17951
17952 let target_highlight = match direction {
17953 Direction::Next => {
17954 // Find the first highlight after the current position
17955 all_highlights
17956 .iter()
17957 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17958 }
17959 Direction::Prev => {
17960 // Find the last highlight before the current position
17961 all_highlights
17962 .iter()
17963 .rev()
17964 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17965 }
17966 };
17967
17968 if let Some(highlight) = target_highlight {
17969 let destination = highlight.start.to_point(buffer);
17970 let autoscroll = Autoscroll::center();
17971
17972 self.unfold_ranges(&[destination..destination], false, false, cx);
17973 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17974 s.select_ranges([destination..destination]);
17975 });
17976 }
17977 }
17978
17979 fn go_to_line<T: 'static>(
17980 &mut self,
17981 position: Anchor,
17982 highlight_color: Option<Hsla>,
17983 window: &mut Window,
17984 cx: &mut Context<Self>,
17985 ) {
17986 let snapshot = self.snapshot(window, cx).display_snapshot;
17987 let position = position.to_point(&snapshot.buffer_snapshot());
17988 let start = snapshot
17989 .buffer_snapshot()
17990 .clip_point(Point::new(position.row, 0), Bias::Left);
17991 let end = start + Point::new(1, 0);
17992 let start = snapshot.buffer_snapshot().anchor_before(start);
17993 let end = snapshot.buffer_snapshot().anchor_before(end);
17994
17995 self.highlight_rows::<T>(
17996 start..end,
17997 highlight_color
17998 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17999 Default::default(),
18000 cx,
18001 );
18002
18003 if self.buffer.read(cx).is_singleton() {
18004 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
18005 }
18006 }
18007
18008 pub fn go_to_definition(
18009 &mut self,
18010 _: &GoToDefinition,
18011 window: &mut Window,
18012 cx: &mut Context<Self>,
18013 ) -> Task<Result<Navigated>> {
18014 let definition =
18015 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
18016 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
18017 cx.spawn_in(window, async move |editor, cx| {
18018 if definition.await? == Navigated::Yes {
18019 return Ok(Navigated::Yes);
18020 }
18021 match fallback_strategy {
18022 GoToDefinitionFallback::None => Ok(Navigated::No),
18023 GoToDefinitionFallback::FindAllReferences => {
18024 match editor.update_in(cx, |editor, window, cx| {
18025 editor.find_all_references(&FindAllReferences::default(), window, cx)
18026 })? {
18027 Some(references) => references.await,
18028 None => Ok(Navigated::No),
18029 }
18030 }
18031 }
18032 })
18033 }
18034
18035 pub fn go_to_declaration(
18036 &mut self,
18037 _: &GoToDeclaration,
18038 window: &mut Window,
18039 cx: &mut Context<Self>,
18040 ) -> Task<Result<Navigated>> {
18041 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
18042 }
18043
18044 pub fn go_to_declaration_split(
18045 &mut self,
18046 _: &GoToDeclaration,
18047 window: &mut Window,
18048 cx: &mut Context<Self>,
18049 ) -> Task<Result<Navigated>> {
18050 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
18051 }
18052
18053 pub fn go_to_implementation(
18054 &mut self,
18055 _: &GoToImplementation,
18056 window: &mut Window,
18057 cx: &mut Context<Self>,
18058 ) -> Task<Result<Navigated>> {
18059 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
18060 }
18061
18062 pub fn go_to_implementation_split(
18063 &mut self,
18064 _: &GoToImplementationSplit,
18065 window: &mut Window,
18066 cx: &mut Context<Self>,
18067 ) -> Task<Result<Navigated>> {
18068 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
18069 }
18070
18071 pub fn go_to_type_definition(
18072 &mut self,
18073 _: &GoToTypeDefinition,
18074 window: &mut Window,
18075 cx: &mut Context<Self>,
18076 ) -> Task<Result<Navigated>> {
18077 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
18078 }
18079
18080 pub fn go_to_definition_split(
18081 &mut self,
18082 _: &GoToDefinitionSplit,
18083 window: &mut Window,
18084 cx: &mut Context<Self>,
18085 ) -> Task<Result<Navigated>> {
18086 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
18087 }
18088
18089 pub fn go_to_type_definition_split(
18090 &mut self,
18091 _: &GoToTypeDefinitionSplit,
18092 window: &mut Window,
18093 cx: &mut Context<Self>,
18094 ) -> Task<Result<Navigated>> {
18095 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18096 }
18097
18098 fn go_to_definition_of_kind(
18099 &mut self,
18100 kind: GotoDefinitionKind,
18101 split: bool,
18102 window: &mut Window,
18103 cx: &mut Context<Self>,
18104 ) -> Task<Result<Navigated>> {
18105 let Some(provider) = self.semantics_provider.clone() else {
18106 return Task::ready(Ok(Navigated::No));
18107 };
18108 let head = self
18109 .selections
18110 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18111 .head();
18112 let buffer = self.buffer.read(cx);
18113 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18114 return Task::ready(Ok(Navigated::No));
18115 };
18116 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18117 return Task::ready(Ok(Navigated::No));
18118 };
18119
18120 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18121
18122 cx.spawn_in(window, async move |editor, cx| {
18123 let Some(definitions) = definitions.await? else {
18124 return Ok(Navigated::No);
18125 };
18126 let navigated = editor
18127 .update_in(cx, |editor, window, cx| {
18128 editor.navigate_to_hover_links(
18129 Some(kind),
18130 definitions
18131 .into_iter()
18132 .filter(|location| {
18133 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18134 })
18135 .map(HoverLink::Text)
18136 .collect::<Vec<_>>(),
18137 nav_entry,
18138 split,
18139 window,
18140 cx,
18141 )
18142 })?
18143 .await?;
18144 anyhow::Ok(navigated)
18145 })
18146 }
18147
18148 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18149 let selection = self.selections.newest_anchor();
18150 let head = selection.head();
18151 let tail = selection.tail();
18152
18153 let Some((buffer, start_position)) =
18154 self.buffer.read(cx).text_anchor_for_position(head, cx)
18155 else {
18156 return;
18157 };
18158
18159 let end_position = if head != tail {
18160 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18161 return;
18162 };
18163 Some(pos)
18164 } else {
18165 None
18166 };
18167
18168 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18169 let url = if let Some(end_pos) = end_position {
18170 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18171 } else {
18172 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18173 };
18174
18175 if let Some(url) = url {
18176 cx.update(|window, cx| {
18177 if parse_zed_link(&url, cx).is_some() {
18178 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18179 } else {
18180 cx.open_url(&url);
18181 }
18182 })?;
18183 }
18184
18185 anyhow::Ok(())
18186 });
18187
18188 url_finder.detach();
18189 }
18190
18191 pub fn open_selected_filename(
18192 &mut self,
18193 _: &OpenSelectedFilename,
18194 window: &mut Window,
18195 cx: &mut Context<Self>,
18196 ) {
18197 let Some(workspace) = self.workspace() else {
18198 return;
18199 };
18200
18201 let position = self.selections.newest_anchor().head();
18202
18203 let Some((buffer, buffer_position)) =
18204 self.buffer.read(cx).text_anchor_for_position(position, cx)
18205 else {
18206 return;
18207 };
18208
18209 let project = self.project.clone();
18210
18211 cx.spawn_in(window, async move |_, cx| {
18212 let result = find_file(&buffer, project, buffer_position, cx).await;
18213
18214 if let Some((_, path)) = result {
18215 workspace
18216 .update_in(cx, |workspace, window, cx| {
18217 workspace.open_resolved_path(path, window, cx)
18218 })?
18219 .await?;
18220 }
18221 anyhow::Ok(())
18222 })
18223 .detach();
18224 }
18225
18226 pub(crate) fn navigate_to_hover_links(
18227 &mut self,
18228 kind: Option<GotoDefinitionKind>,
18229 definitions: Vec<HoverLink>,
18230 origin: Option<NavigationEntry>,
18231 split: bool,
18232 window: &mut Window,
18233 cx: &mut Context<Editor>,
18234 ) -> Task<Result<Navigated>> {
18235 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18236 let mut first_url_or_file = None;
18237 let definitions: Vec<_> = definitions
18238 .into_iter()
18239 .filter_map(|def| match def {
18240 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18241 HoverLink::InlayHint(lsp_location, server_id) => {
18242 let computation =
18243 self.compute_target_location(lsp_location, server_id, window, cx);
18244 Some(cx.background_spawn(computation))
18245 }
18246 HoverLink::Url(url) => {
18247 first_url_or_file = Some(Either::Left(url));
18248 None
18249 }
18250 HoverLink::File(path) => {
18251 first_url_or_file = Some(Either::Right(path));
18252 None
18253 }
18254 })
18255 .collect();
18256
18257 let workspace = self.workspace();
18258
18259 let excerpt_context_lines = multi_buffer::excerpt_context_lines(cx);
18260 cx.spawn_in(window, async move |editor, cx| {
18261 let locations: Vec<Location> = future::join_all(definitions)
18262 .await
18263 .into_iter()
18264 .filter_map(|location| location.transpose())
18265 .collect::<Result<_>>()
18266 .context("location tasks")?;
18267 let mut locations = cx.update(|_, cx| {
18268 locations
18269 .into_iter()
18270 .map(|location| {
18271 let buffer = location.buffer.read(cx);
18272 (location.buffer, location.range.to_point(buffer))
18273 })
18274 .into_group_map()
18275 })?;
18276 let mut num_locations = 0;
18277 for ranges in locations.values_mut() {
18278 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18279 ranges.dedup();
18280 // Merge overlapping or contained ranges. After sorting by
18281 // (start, Reverse(end)), we can merge in a single pass:
18282 // if the next range starts before the current one ends,
18283 // extend the current range's end if needed.
18284 let mut i = 0;
18285 while i + 1 < ranges.len() {
18286 if ranges[i + 1].start <= ranges[i].end {
18287 let merged_end = ranges[i].end.max(ranges[i + 1].end);
18288 ranges[i].end = merged_end;
18289 ranges.remove(i + 1);
18290 } else {
18291 i += 1;
18292 }
18293 }
18294 let fits_in_one_excerpt = ranges
18295 .iter()
18296 .tuple_windows()
18297 .all(|(a, b)| b.start.row - a.end.row <= 2 * excerpt_context_lines);
18298 num_locations += if fits_in_one_excerpt { 1 } else { ranges.len() };
18299 }
18300
18301 if num_locations > 1 {
18302 let tab_kind = match kind {
18303 Some(GotoDefinitionKind::Implementation) => "Implementations",
18304 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18305 Some(GotoDefinitionKind::Declaration) => "Declarations",
18306 Some(GotoDefinitionKind::Type) => "Types",
18307 };
18308 let title = editor
18309 .update_in(cx, |_, _, cx| {
18310 let target = locations
18311 .iter()
18312 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18313 .map(|(buffer, location)| {
18314 buffer
18315 .read(cx)
18316 .text_for_range(location.clone())
18317 .collect::<String>()
18318 })
18319 .filter(|text| !text.contains('\n'))
18320 .unique()
18321 .take(3)
18322 .join(", ");
18323 if target.is_empty() {
18324 tab_kind.to_owned()
18325 } else {
18326 format!("{tab_kind} for {target}")
18327 }
18328 })
18329 .context("buffer title")?;
18330
18331 let Some(workspace) = workspace else {
18332 return Ok(Navigated::No);
18333 };
18334
18335 let opened = workspace
18336 .update_in(cx, |workspace, window, cx| {
18337 let allow_preview = PreviewTabsSettings::get_global(cx)
18338 .enable_preview_multibuffer_from_code_navigation;
18339 if let Some((target_editor, target_pane)) =
18340 Self::open_locations_in_multibuffer(
18341 workspace,
18342 locations,
18343 title,
18344 split,
18345 allow_preview,
18346 MultibufferSelectionMode::First,
18347 window,
18348 cx,
18349 )
18350 {
18351 // We create our own nav history instead of using
18352 // `target_editor.nav_history` because `nav_history`
18353 // seems to be populated asynchronously when an item
18354 // is added to a pane
18355 let mut nav_history = target_pane
18356 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18357 target_editor.update(cx, |editor, cx| {
18358 let nav_data = editor
18359 .navigation_data(editor.selections.newest_anchor().head(), cx);
18360 let target =
18361 Some(nav_history.navigation_entry(Some(
18362 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18363 )));
18364 nav_history.push_tag(origin, target);
18365 })
18366 }
18367 })
18368 .is_ok();
18369
18370 anyhow::Ok(Navigated::from_bool(opened))
18371 } else if num_locations == 0 {
18372 // If there is one url or file, open it directly
18373 match first_url_or_file {
18374 Some(Either::Left(url)) => {
18375 cx.update(|window, cx| {
18376 if parse_zed_link(&url, cx).is_some() {
18377 window
18378 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18379 } else {
18380 cx.open_url(&url);
18381 }
18382 })?;
18383 Ok(Navigated::Yes)
18384 }
18385 Some(Either::Right(path)) => {
18386 // TODO(andrew): respect preview tab settings
18387 // `enable_keep_preview_on_code_navigation` and
18388 // `enable_preview_file_from_code_navigation`
18389 let Some(workspace) = workspace else {
18390 return Ok(Navigated::No);
18391 };
18392 workspace
18393 .update_in(cx, |workspace, window, cx| {
18394 workspace.open_resolved_path(path, window, cx)
18395 })?
18396 .await?;
18397 Ok(Navigated::Yes)
18398 }
18399 None => Ok(Navigated::No),
18400 }
18401 } else {
18402 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18403
18404 editor.update_in(cx, |editor, window, cx| {
18405 let target_ranges = target_ranges
18406 .into_iter()
18407 .map(|r| editor.range_for_match(&r))
18408 .map(collapse_multiline_range)
18409 .collect::<Vec<_>>();
18410 if !split
18411 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18412 {
18413 let multibuffer = editor.buffer.read(cx);
18414 let target_ranges = target_ranges
18415 .into_iter()
18416 .filter_map(|r| {
18417 let start = multibuffer.buffer_point_to_anchor(
18418 &target_buffer,
18419 r.start,
18420 cx,
18421 )?;
18422 let end = multibuffer.buffer_point_to_anchor(
18423 &target_buffer,
18424 r.end,
18425 cx,
18426 )?;
18427 Some(start..end)
18428 })
18429 .collect::<Vec<_>>();
18430 if target_ranges.is_empty() {
18431 return Navigated::No;
18432 }
18433
18434 editor.change_selections(
18435 SelectionEffects::default().nav_history(true),
18436 window,
18437 cx,
18438 |s| s.select_anchor_ranges(target_ranges),
18439 );
18440
18441 let target =
18442 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18443 if let Some(mut nav_history) = editor.nav_history.clone() {
18444 nav_history.push_tag(origin, target);
18445 }
18446 } else {
18447 let Some(workspace) = workspace else {
18448 return Navigated::No;
18449 };
18450 let pane = workspace.read(cx).active_pane().clone();
18451 window.defer(cx, move |window, cx| {
18452 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18453 workspace.update(cx, |workspace, cx| {
18454 let pane = if split {
18455 workspace.adjacent_pane(window, cx)
18456 } else {
18457 workspace.active_pane().clone()
18458 };
18459
18460 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18461 let keep_old_preview = preview_tabs_settings
18462 .enable_keep_preview_on_code_navigation;
18463 let allow_new_preview = preview_tabs_settings
18464 .enable_preview_file_from_code_navigation;
18465
18466 let editor = workspace.open_project_item(
18467 pane.clone(),
18468 target_buffer.clone(),
18469 true,
18470 true,
18471 keep_old_preview,
18472 allow_new_preview,
18473 window,
18474 cx,
18475 );
18476 (editor, pane)
18477 });
18478 // We create our own nav history instead of using
18479 // `target_editor.nav_history` because `nav_history`
18480 // seems to be populated asynchronously when an item
18481 // is added to a pane
18482 let mut nav_history = target_pane
18483 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18484 target_editor.update(cx, |target_editor, cx| {
18485 // When selecting a definition in a different buffer, disable the nav history
18486 // to avoid creating a history entry at the previous cursor location.
18487 pane.update(cx, |pane, _| pane.disable_history());
18488
18489 let multibuffer = target_editor.buffer.read(cx);
18490 let Some(target_buffer) = multibuffer.as_singleton() else {
18491 return Navigated::No;
18492 };
18493 let target_ranges = target_ranges
18494 .into_iter()
18495 .filter_map(|r| {
18496 let start = multibuffer.buffer_point_to_anchor(
18497 &target_buffer,
18498 r.start,
18499 cx,
18500 )?;
18501 let end = multibuffer.buffer_point_to_anchor(
18502 &target_buffer,
18503 r.end,
18504 cx,
18505 )?;
18506 Some(start..end)
18507 })
18508 .collect::<Vec<_>>();
18509 if target_ranges.is_empty() {
18510 return Navigated::No;
18511 }
18512
18513 target_editor.change_selections(
18514 SelectionEffects::default().nav_history(true),
18515 window,
18516 cx,
18517 |s| s.select_anchor_ranges(target_ranges),
18518 );
18519
18520 let nav_data = target_editor.navigation_data(
18521 target_editor.selections.newest_anchor().head(),
18522 cx,
18523 );
18524 let target =
18525 Some(nav_history.navigation_entry(Some(
18526 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18527 )));
18528 nav_history.push_tag(origin, target);
18529 pane.update(cx, |pane, _| pane.enable_history());
18530 Navigated::Yes
18531 });
18532 });
18533 }
18534 Navigated::Yes
18535 })
18536 }
18537 })
18538 }
18539
18540 fn compute_target_location(
18541 &self,
18542 lsp_location: lsp::Location,
18543 server_id: LanguageServerId,
18544 window: &mut Window,
18545 cx: &mut Context<Self>,
18546 ) -> Task<anyhow::Result<Option<Location>>> {
18547 let Some(project) = self.project.clone() else {
18548 return Task::ready(Ok(None));
18549 };
18550
18551 cx.spawn_in(window, async move |editor, cx| {
18552 let location_task = editor.update(cx, |_, cx| {
18553 project.update(cx, |project, cx| {
18554 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18555 })
18556 })?;
18557 let location = Some({
18558 let target_buffer_handle = location_task.await.context("open local buffer")?;
18559 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18560 let target_start = target_buffer
18561 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18562 let target_end = target_buffer
18563 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18564 target_buffer.anchor_after(target_start)
18565 ..target_buffer.anchor_before(target_end)
18566 });
18567 Location {
18568 buffer: target_buffer_handle,
18569 range,
18570 }
18571 });
18572 Ok(location)
18573 })
18574 }
18575
18576 fn go_to_next_reference(
18577 &mut self,
18578 _: &GoToNextReference,
18579 window: &mut Window,
18580 cx: &mut Context<Self>,
18581 ) {
18582 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18583 if let Some(task) = task {
18584 task.detach();
18585 };
18586 }
18587
18588 fn go_to_prev_reference(
18589 &mut self,
18590 _: &GoToPreviousReference,
18591 window: &mut Window,
18592 cx: &mut Context<Self>,
18593 ) {
18594 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18595 if let Some(task) = task {
18596 task.detach();
18597 };
18598 }
18599
18600 fn go_to_symbol_by_offset(
18601 &mut self,
18602 window: &mut Window,
18603 cx: &mut Context<Self>,
18604 offset: i8,
18605 ) -> Task<Result<()>> {
18606 let editor_snapshot = self.snapshot(window, cx);
18607
18608 // We don't care about multi-buffer symbols
18609 if !editor_snapshot.is_singleton() {
18610 return Task::ready(Ok(()));
18611 }
18612
18613 let cursor_offset = self
18614 .selections
18615 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18616 .head();
18617
18618 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18619 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18620 let buffer = ed.buffer.read(cx).as_singleton()?;
18621 Some(buffer.read(cx).remote_id())
18622 }) else {
18623 return Ok(());
18624 };
18625
18626 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18627 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18628
18629 let multi_snapshot = editor_snapshot.buffer();
18630 let buffer_range = |range: &Range<_>| {
18631 Some(
18632 multi_snapshot
18633 .buffer_anchor_range_to_anchor_range(range.clone())?
18634 .to_offset(multi_snapshot),
18635 )
18636 };
18637
18638 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18639 let current_idx = outline_items
18640 .iter()
18641 .enumerate()
18642 .filter_map(|(idx, item)| {
18643 // Find the closest outline item by distance between outline text and cursor location
18644 let source_range = buffer_range(&item.source_range_for_text)?;
18645 let distance_to_closest_endpoint = cmp::min(
18646 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18647 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18648 );
18649
18650 let item_towards_offset =
18651 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18652 == (offset as isize).signum();
18653
18654 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18655
18656 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18657 // we should not already be within the outline's source range. We then pick the closest outline
18658 // item.
18659 (item_towards_offset && !source_range_contains_cursor)
18660 .then_some((distance_to_closest_endpoint, idx))
18661 })
18662 .min()
18663 .map(|(_, idx)| idx);
18664
18665 let Some(idx) = current_idx else {
18666 return;
18667 };
18668
18669 let Some(range) = buffer_range(&outline_items[idx].source_range_for_text) else {
18670 return;
18671 };
18672 let selection = [range.start..range.start];
18673
18674 let _ = editor
18675 .update(acx, |editor, ecx| {
18676 editor.change_selections(
18677 SelectionEffects::scroll(Autoscroll::newest()),
18678 window,
18679 ecx,
18680 |s| s.select_ranges(selection),
18681 );
18682 })
18683 .ok();
18684 })?;
18685
18686 Ok(())
18687 })
18688 }
18689
18690 fn go_to_next_symbol(
18691 &mut self,
18692 _: &GoToNextSymbol,
18693 window: &mut Window,
18694 cx: &mut Context<Self>,
18695 ) {
18696 self.go_to_symbol_by_offset(window, cx, 1).detach();
18697 }
18698
18699 fn go_to_previous_symbol(
18700 &mut self,
18701 _: &GoToPreviousSymbol,
18702 window: &mut Window,
18703 cx: &mut Context<Self>,
18704 ) {
18705 self.go_to_symbol_by_offset(window, cx, -1).detach();
18706 }
18707
18708 pub fn go_to_reference_before_or_after_position(
18709 &mut self,
18710 direction: Direction,
18711 count: usize,
18712 window: &mut Window,
18713 cx: &mut Context<Self>,
18714 ) -> Option<Task<Result<()>>> {
18715 let selection = self.selections.newest_anchor();
18716 let head = selection.head();
18717
18718 let multi_buffer = self.buffer.read(cx);
18719
18720 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18721 let workspace = self.workspace()?;
18722 let project = workspace.read(cx).project().clone();
18723 let references =
18724 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18725 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18726 let Some(locations) = references.await? else {
18727 return Ok(());
18728 };
18729
18730 if locations.is_empty() {
18731 // totally normal - the cursor may be on something which is not
18732 // a symbol (e.g. a keyword)
18733 log::info!("no references found under cursor");
18734 return Ok(());
18735 }
18736
18737 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18738
18739 let (locations, current_location_index) =
18740 multi_buffer.update(cx, |multi_buffer, cx| {
18741 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18742 let mut locations = locations
18743 .into_iter()
18744 .filter_map(|loc| {
18745 let start = multi_buffer_snapshot.anchor_in_excerpt(loc.range.start)?;
18746 let end = multi_buffer_snapshot.anchor_in_excerpt(loc.range.end)?;
18747 Some(start..end)
18748 })
18749 .collect::<Vec<_>>();
18750 // There is an O(n) implementation, but given this list will be
18751 // small (usually <100 items), the extra O(log(n)) factor isn't
18752 // worth the (surprisingly large amount of) extra complexity.
18753 locations
18754 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18755
18756 let head_offset = head.to_offset(&multi_buffer_snapshot);
18757
18758 let current_location_index = locations.iter().position(|loc| {
18759 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18760 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18761 });
18762
18763 (locations, current_location_index)
18764 });
18765
18766 let Some(current_location_index) = current_location_index else {
18767 // This indicates something has gone wrong, because we already
18768 // handle the "no references" case above
18769 log::error!(
18770 "failed to find current reference under cursor. Total references: {}",
18771 locations.len()
18772 );
18773 return Ok(());
18774 };
18775
18776 let destination_location_index = match direction {
18777 Direction::Next => (current_location_index + count) % locations.len(),
18778 Direction::Prev => {
18779 (current_location_index + locations.len() - count % locations.len())
18780 % locations.len()
18781 }
18782 };
18783
18784 // TODO(cameron): is this needed?
18785 // the thinking is to avoid "jumping to the current location" (avoid
18786 // polluting "jumplist" in vim terms)
18787 if current_location_index == destination_location_index {
18788 return Ok(());
18789 }
18790
18791 let Range { start, end } = locations[destination_location_index];
18792
18793 editor.update_in(cx, |editor, window, cx| {
18794 let effects = SelectionEffects::default();
18795
18796 editor.unfold_ranges(&[start..end], false, false, cx);
18797 editor.change_selections(effects, window, cx, |s| {
18798 s.select_ranges([start..start]);
18799 });
18800 })?;
18801
18802 Ok(())
18803 }))
18804 }
18805
18806 pub fn find_all_references(
18807 &mut self,
18808 action: &FindAllReferences,
18809 window: &mut Window,
18810 cx: &mut Context<Self>,
18811 ) -> Option<Task<Result<Navigated>>> {
18812 let always_open_multibuffer = action.always_open_multibuffer;
18813 let selection = self.selections.newest_anchor();
18814 let multi_buffer = self.buffer.read(cx);
18815 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18816 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18817 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18818 let head = selection_offset.head();
18819
18820 let head_anchor = multi_buffer_snapshot.anchor_at(
18821 head,
18822 if head < selection_offset.tail() {
18823 Bias::Right
18824 } else {
18825 Bias::Left
18826 },
18827 );
18828
18829 match self
18830 .find_all_references_task_sources
18831 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18832 {
18833 Ok(_) => {
18834 log::info!(
18835 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18836 );
18837 return None;
18838 }
18839 Err(i) => {
18840 self.find_all_references_task_sources.insert(i, head_anchor);
18841 }
18842 }
18843
18844 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18845 let workspace = self.workspace()?;
18846 let project = workspace.read(cx).project().clone();
18847 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18848 Some(cx.spawn_in(window, async move |editor, cx| {
18849 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18850 if let Ok(i) = editor
18851 .find_all_references_task_sources
18852 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18853 {
18854 editor.find_all_references_task_sources.remove(i);
18855 }
18856 });
18857
18858 let Some(locations) = references.await? else {
18859 return anyhow::Ok(Navigated::No);
18860 };
18861 let mut locations = cx.update(|_, cx| {
18862 locations
18863 .into_iter()
18864 .map(|location| {
18865 let buffer = location.buffer.read(cx);
18866 (location.buffer, location.range.to_point(buffer))
18867 })
18868 // if special-casing the single-match case, remove ranges
18869 // that intersect current selection
18870 .filter(|(location_buffer, location)| {
18871 if always_open_multibuffer || &buffer != location_buffer {
18872 return true;
18873 }
18874
18875 !location.contains_inclusive(&selection_point.range())
18876 })
18877 .into_group_map()
18878 })?;
18879 if locations.is_empty() {
18880 return anyhow::Ok(Navigated::No);
18881 }
18882 for ranges in locations.values_mut() {
18883 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18884 ranges.dedup();
18885 }
18886 let mut num_locations = 0;
18887 for ranges in locations.values_mut() {
18888 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18889 ranges.dedup();
18890 num_locations += ranges.len();
18891 }
18892
18893 if num_locations == 1 && !always_open_multibuffer {
18894 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18895 let target_range = target_ranges.first().unwrap().clone();
18896
18897 return editor.update_in(cx, |editor, window, cx| {
18898 let range = target_range.to_point(target_buffer.read(cx));
18899 let range = editor.range_for_match(&range);
18900 let range = range.start..range.start;
18901
18902 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18903 editor.go_to_singleton_buffer_range(range, window, cx);
18904 } else {
18905 let pane = workspace.read(cx).active_pane().clone();
18906 window.defer(cx, move |window, cx| {
18907 let target_editor: Entity<Self> =
18908 workspace.update(cx, |workspace, cx| {
18909 let pane = workspace.active_pane().clone();
18910
18911 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18912 let keep_old_preview = preview_tabs_settings
18913 .enable_keep_preview_on_code_navigation;
18914 let allow_new_preview = preview_tabs_settings
18915 .enable_preview_file_from_code_navigation;
18916
18917 workspace.open_project_item(
18918 pane,
18919 target_buffer.clone(),
18920 true,
18921 true,
18922 keep_old_preview,
18923 allow_new_preview,
18924 window,
18925 cx,
18926 )
18927 });
18928 target_editor.update(cx, |target_editor, cx| {
18929 // When selecting a definition in a different buffer, disable the nav history
18930 // to avoid creating a history entry at the previous cursor location.
18931 pane.update(cx, |pane, _| pane.disable_history());
18932 target_editor.go_to_singleton_buffer_range(range, window, cx);
18933 pane.update(cx, |pane, _| pane.enable_history());
18934 });
18935 });
18936 }
18937 Navigated::No
18938 });
18939 }
18940
18941 workspace.update_in(cx, |workspace, window, cx| {
18942 let target = locations
18943 .iter()
18944 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18945 .map(|(buffer, location)| {
18946 buffer
18947 .read(cx)
18948 .text_for_range(location.clone())
18949 .collect::<String>()
18950 })
18951 .filter(|text| !text.contains('\n'))
18952 .unique()
18953 .take(3)
18954 .join(", ");
18955 let title = if target.is_empty() {
18956 "References".to_owned()
18957 } else {
18958 format!("References to {target}")
18959 };
18960 let allow_preview = PreviewTabsSettings::get_global(cx)
18961 .enable_preview_multibuffer_from_code_navigation;
18962 Self::open_locations_in_multibuffer(
18963 workspace,
18964 locations,
18965 title,
18966 false,
18967 allow_preview,
18968 MultibufferSelectionMode::First,
18969 window,
18970 cx,
18971 );
18972 Navigated::Yes
18973 })
18974 }))
18975 }
18976
18977 /// Opens a multibuffer with the given project locations in it.
18978 pub fn open_locations_in_multibuffer(
18979 workspace: &mut Workspace,
18980 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18981 title: String,
18982 split: bool,
18983 allow_preview: bool,
18984 multibuffer_selection_mode: MultibufferSelectionMode,
18985 window: &mut Window,
18986 cx: &mut Context<Workspace>,
18987 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18988 if locations.is_empty() {
18989 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18990 return None;
18991 }
18992
18993 let capability = workspace.project().read(cx).capability();
18994 let mut ranges = <Vec<Range<Anchor>>>::new();
18995
18996 // a key to find existing multibuffer editors with the same set of locations
18997 // to prevent us from opening more and more multibuffer tabs for searches and the like
18998 let mut key = (title.clone(), vec![]);
18999 let excerpt_buffer = cx.new(|cx| {
19000 let key = &mut key.1;
19001 let mut multibuffer = MultiBuffer::new(capability);
19002 for (buffer, mut ranges_for_buffer) in locations {
19003 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
19004 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
19005 multibuffer.set_excerpts_for_path(
19006 PathKey::for_buffer(&buffer, cx),
19007 buffer.clone(),
19008 ranges_for_buffer.clone(),
19009 multibuffer_context_lines(cx),
19010 cx,
19011 );
19012 let snapshot = multibuffer.snapshot(cx);
19013 let buffer_snapshot = buffer.read(cx).snapshot();
19014 ranges.extend(ranges_for_buffer.into_iter().filter_map(|range| {
19015 let text_range = buffer_snapshot.anchor_range_inside(range);
19016 let start = snapshot.anchor_in_buffer(text_range.start)?;
19017 let end = snapshot.anchor_in_buffer(text_range.end)?;
19018 Some(start..end)
19019 }))
19020 }
19021
19022 multibuffer.with_title(title)
19023 });
19024 let existing = workspace.active_pane().update(cx, |pane, cx| {
19025 pane.items()
19026 .filter_map(|item| item.downcast::<Editor>())
19027 .find(|editor| {
19028 editor
19029 .read(cx)
19030 .lookup_key
19031 .as_ref()
19032 .and_then(|it| {
19033 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
19034 })
19035 .is_some_and(|it| *it == key)
19036 })
19037 });
19038 let was_existing = existing.is_some();
19039 let editor = existing.unwrap_or_else(|| {
19040 cx.new(|cx| {
19041 let mut editor = Editor::for_multibuffer(
19042 excerpt_buffer,
19043 Some(workspace.project().clone()),
19044 window,
19045 cx,
19046 );
19047 editor.lookup_key = Some(Box::new(key));
19048 editor
19049 })
19050 });
19051 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
19052 MultibufferSelectionMode::First => {
19053 if let Some(first_range) = ranges.first() {
19054 editor.change_selections(
19055 SelectionEffects::no_scroll(),
19056 window,
19057 cx,
19058 |selections| {
19059 selections.clear_disjoint();
19060 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
19061 },
19062 );
19063 }
19064 editor.highlight_background(
19065 HighlightKey::Editor,
19066 &ranges,
19067 |_, theme| theme.colors().editor_highlighted_line_background,
19068 cx,
19069 );
19070 }
19071 MultibufferSelectionMode::All => {
19072 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19073 selections.clear_disjoint();
19074 selections.select_anchor_ranges(ranges);
19075 });
19076 }
19077 });
19078
19079 let item = Box::new(editor.clone());
19080
19081 let pane = if split {
19082 workspace.adjacent_pane(window, cx)
19083 } else {
19084 workspace.active_pane().clone()
19085 };
19086 let activate_pane = split;
19087
19088 let mut destination_index = None;
19089 pane.update(cx, |pane, cx| {
19090 if allow_preview && !was_existing {
19091 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
19092 }
19093 if was_existing && !allow_preview {
19094 pane.unpreview_item_if_preview(item.item_id());
19095 }
19096 pane.add_item(item, activate_pane, true, destination_index, window, cx);
19097 });
19098
19099 Some((editor, pane))
19100 }
19101
19102 pub fn rename(
19103 &mut self,
19104 _: &Rename,
19105 window: &mut Window,
19106 cx: &mut Context<Self>,
19107 ) -> Option<Task<Result<()>>> {
19108 use language::ToOffset as _;
19109
19110 let provider = self.semantics_provider.clone()?;
19111 let selection = self.selections.newest_anchor().clone();
19112 let (cursor_buffer, cursor_buffer_position) = self
19113 .buffer
19114 .read(cx)
19115 .text_anchor_for_position(selection.head(), cx)?;
19116 let (tail_buffer, cursor_buffer_position_end) = self
19117 .buffer
19118 .read(cx)
19119 .text_anchor_for_position(selection.tail(), cx)?;
19120 if tail_buffer != cursor_buffer {
19121 return None;
19122 }
19123
19124 let snapshot = cursor_buffer.read(cx).snapshot();
19125 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
19126 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
19127 let prepare_rename = provider.range_for_rename(&cursor_buffer, cursor_buffer_position, cx);
19128 drop(snapshot);
19129
19130 Some(cx.spawn_in(window, async move |this, cx| {
19131 let rename_range = prepare_rename.await?;
19132 if let Some(rename_range) = rename_range {
19133 this.update_in(cx, |this, window, cx| {
19134 let snapshot = cursor_buffer.read(cx).snapshot();
19135 let rename_buffer_range = rename_range.to_offset(&snapshot);
19136 let cursor_offset_in_rename_range =
19137 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
19138 let cursor_offset_in_rename_range_end =
19139 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
19140
19141 this.take_rename(false, window, cx);
19142 let buffer = this.buffer.read(cx).read(cx);
19143 let cursor_offset = selection.head().to_offset(&buffer);
19144 let rename_start =
19145 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
19146 let rename_end = rename_start + rename_buffer_range.len();
19147 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
19148 let mut old_highlight_id = None;
19149 let old_name: Arc<str> = buffer
19150 .chunks(rename_start..rename_end, true)
19151 .map(|chunk| {
19152 if old_highlight_id.is_none() {
19153 old_highlight_id = chunk.syntax_highlight_id;
19154 }
19155 chunk.text
19156 })
19157 .collect::<String>()
19158 .into();
19159
19160 drop(buffer);
19161
19162 // Position the selection in the rename editor so that it matches the current selection.
19163 this.show_local_selections = false;
19164 let rename_editor = cx.new(|cx| {
19165 let mut editor = Editor::single_line(window, cx);
19166 editor.buffer.update(cx, |buffer, cx| {
19167 buffer.edit(
19168 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
19169 None,
19170 cx,
19171 )
19172 });
19173 let cursor_offset_in_rename_range =
19174 MultiBufferOffset(cursor_offset_in_rename_range);
19175 let cursor_offset_in_rename_range_end =
19176 MultiBufferOffset(cursor_offset_in_rename_range_end);
19177 let rename_selection_range = match cursor_offset_in_rename_range
19178 .cmp(&cursor_offset_in_rename_range_end)
19179 {
19180 Ordering::Equal => {
19181 editor.select_all(&SelectAll, window, cx);
19182 return editor;
19183 }
19184 Ordering::Less => {
19185 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
19186 }
19187 Ordering::Greater => {
19188 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
19189 }
19190 };
19191 if rename_selection_range.end.0 > old_name.len() {
19192 editor.select_all(&SelectAll, window, cx);
19193 } else {
19194 editor.change_selections(Default::default(), window, cx, |s| {
19195 s.select_ranges([rename_selection_range]);
19196 });
19197 }
19198 editor
19199 });
19200 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19201 if e == &EditorEvent::Focused {
19202 cx.emit(EditorEvent::FocusedIn)
19203 }
19204 })
19205 .detach();
19206
19207 let write_highlights =
19208 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19209 let read_highlights =
19210 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19211 let ranges = write_highlights
19212 .iter()
19213 .flat_map(|(_, ranges)| ranges.iter())
19214 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19215 .cloned()
19216 .collect();
19217
19218 this.highlight_text(
19219 HighlightKey::Rename,
19220 ranges,
19221 HighlightStyle {
19222 fade_out: Some(0.6),
19223 ..Default::default()
19224 },
19225 cx,
19226 );
19227 let rename_focus_handle = rename_editor.focus_handle(cx);
19228 window.focus(&rename_focus_handle, cx);
19229 let block_id = this.insert_blocks(
19230 [BlockProperties {
19231 style: BlockStyle::Flex,
19232 placement: BlockPlacement::Below(range.start),
19233 height: Some(1),
19234 render: Arc::new({
19235 let rename_editor = rename_editor.clone();
19236 move |cx: &mut BlockContext| {
19237 let mut text_style = cx.editor_style.text.clone();
19238 if let Some(highlight_style) = old_highlight_id
19239 .and_then(|h| cx.editor_style.syntax.get(h).cloned())
19240 {
19241 text_style = text_style.highlight(highlight_style);
19242 }
19243 div()
19244 .block_mouse_except_scroll()
19245 .pl(cx.anchor_x)
19246 .child(EditorElement::new(
19247 &rename_editor,
19248 EditorStyle {
19249 background: cx.theme().system().transparent,
19250 local_player: cx.editor_style.local_player,
19251 text: text_style,
19252 scrollbar_width: cx.editor_style.scrollbar_width,
19253 syntax: cx.editor_style.syntax.clone(),
19254 status: cx.editor_style.status.clone(),
19255 inlay_hints_style: HighlightStyle {
19256 font_weight: Some(FontWeight::BOLD),
19257 ..make_inlay_hints_style(cx.app)
19258 },
19259 edit_prediction_styles: make_suggestion_styles(
19260 cx.app,
19261 ),
19262 ..EditorStyle::default()
19263 },
19264 ))
19265 .into_any_element()
19266 }
19267 }),
19268 priority: 0,
19269 }],
19270 Some(Autoscroll::fit()),
19271 cx,
19272 )[0];
19273 this.pending_rename = Some(RenameState {
19274 range,
19275 old_name,
19276 editor: rename_editor,
19277 block_id,
19278 });
19279 })?;
19280 }
19281
19282 Ok(())
19283 }))
19284 }
19285
19286 pub fn confirm_rename(
19287 &mut self,
19288 _: &ConfirmRename,
19289 window: &mut Window,
19290 cx: &mut Context<Self>,
19291 ) -> Option<Task<Result<()>>> {
19292 let rename = self.take_rename(false, window, cx)?;
19293 let workspace = self.workspace()?.downgrade();
19294 let (buffer, start) = self
19295 .buffer
19296 .read(cx)
19297 .text_anchor_for_position(rename.range.start, cx)?;
19298 let (end_buffer, _) = self
19299 .buffer
19300 .read(cx)
19301 .text_anchor_for_position(rename.range.end, cx)?;
19302 if buffer != end_buffer {
19303 return None;
19304 }
19305
19306 let old_name = rename.old_name;
19307 let new_name = rename.editor.read(cx).text(cx);
19308
19309 let rename = self.semantics_provider.as_ref()?.perform_rename(
19310 &buffer,
19311 start,
19312 new_name.clone(),
19313 cx,
19314 )?;
19315
19316 Some(cx.spawn_in(window, async move |editor, cx| {
19317 let project_transaction = rename.await?;
19318 Self::open_project_transaction(
19319 &editor,
19320 workspace,
19321 project_transaction,
19322 format!("Rename: {} → {}", old_name, new_name),
19323 cx,
19324 )
19325 .await?;
19326
19327 editor.update(cx, |editor, cx| {
19328 editor.refresh_document_highlights(cx);
19329 })?;
19330 Ok(())
19331 }))
19332 }
19333
19334 fn take_rename(
19335 &mut self,
19336 moving_cursor: bool,
19337 window: &mut Window,
19338 cx: &mut Context<Self>,
19339 ) -> Option<RenameState> {
19340 let rename = self.pending_rename.take()?;
19341 if rename.editor.focus_handle(cx).is_focused(window) {
19342 window.focus(&self.focus_handle, cx);
19343 }
19344
19345 self.remove_blocks(
19346 [rename.block_id].into_iter().collect(),
19347 Some(Autoscroll::fit()),
19348 cx,
19349 );
19350 self.clear_highlights(HighlightKey::Rename, cx);
19351 self.show_local_selections = true;
19352
19353 if moving_cursor {
19354 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19355 editor
19356 .selections
19357 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19358 .head()
19359 });
19360
19361 // Update the selection to match the position of the selection inside
19362 // the rename editor.
19363 let snapshot = self.buffer.read(cx).read(cx);
19364 let rename_range = rename.range.to_offset(&snapshot);
19365 let cursor_in_editor = snapshot
19366 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19367 .min(rename_range.end);
19368 drop(snapshot);
19369
19370 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19371 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19372 });
19373 } else {
19374 self.refresh_document_highlights(cx);
19375 }
19376
19377 Some(rename)
19378 }
19379
19380 pub fn pending_rename(&self) -> Option<&RenameState> {
19381 self.pending_rename.as_ref()
19382 }
19383
19384 fn format(
19385 &mut self,
19386 _: &Format,
19387 window: &mut Window,
19388 cx: &mut Context<Self>,
19389 ) -> Option<Task<Result<()>>> {
19390 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19391
19392 let project = match &self.project {
19393 Some(project) => project.clone(),
19394 None => return None,
19395 };
19396
19397 Some(self.perform_format(
19398 project,
19399 FormatTrigger::Manual,
19400 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19401 window,
19402 cx,
19403 ))
19404 }
19405
19406 fn format_selections(
19407 &mut self,
19408 _: &FormatSelections,
19409 window: &mut Window,
19410 cx: &mut Context<Self>,
19411 ) -> Option<Task<Result<()>>> {
19412 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19413
19414 let project = match &self.project {
19415 Some(project) => project.clone(),
19416 None => return None,
19417 };
19418
19419 let ranges = self
19420 .selections
19421 .all_adjusted(&self.display_snapshot(cx))
19422 .into_iter()
19423 .map(|selection| selection.range())
19424 .collect_vec();
19425
19426 Some(self.perform_format(
19427 project,
19428 FormatTrigger::Manual,
19429 FormatTarget::Ranges(ranges),
19430 window,
19431 cx,
19432 ))
19433 }
19434
19435 fn perform_format(
19436 &mut self,
19437 project: Entity<Project>,
19438 trigger: FormatTrigger,
19439 target: FormatTarget,
19440 window: &mut Window,
19441 cx: &mut Context<Self>,
19442 ) -> Task<Result<()>> {
19443 let buffer = self.buffer.clone();
19444 let (buffers, target) = match target {
19445 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19446 FormatTarget::Ranges(selection_ranges) => {
19447 let multi_buffer = buffer.read(cx);
19448 let snapshot = multi_buffer.read(cx);
19449 let mut buffers = HashSet::default();
19450 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19451 BTreeMap::new();
19452 for selection_range in selection_ranges {
19453 for (buffer_snapshot, buffer_range, _) in
19454 snapshot.range_to_buffer_ranges(selection_range.start..selection_range.end)
19455 {
19456 let buffer_id = buffer_snapshot.remote_id();
19457 let start = buffer_snapshot.anchor_before(buffer_range.start);
19458 let end = buffer_snapshot.anchor_after(buffer_range.end);
19459 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19460 buffer_id_to_ranges
19461 .entry(buffer_id)
19462 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19463 .or_insert_with(|| vec![start..end]);
19464 }
19465 }
19466 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19467 }
19468 };
19469
19470 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19471 let selections_prev = transaction_id_prev
19472 .and_then(|transaction_id_prev| {
19473 // default to selections as they were after the last edit, if we have them,
19474 // instead of how they are now.
19475 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19476 // will take you back to where you made the last edit, instead of staying where you scrolled
19477 self.selection_history
19478 .transaction(transaction_id_prev)
19479 .map(|t| t.0.clone())
19480 })
19481 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19482
19483 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19484 let format = project.update(cx, |project, cx| {
19485 project.format(buffers, target, true, trigger, cx)
19486 });
19487
19488 cx.spawn_in(window, async move |editor, cx| {
19489 let transaction = futures::select_biased! {
19490 transaction = format.log_err().fuse() => transaction,
19491 () = timeout => {
19492 log::warn!("timed out waiting for formatting");
19493 None
19494 }
19495 };
19496
19497 buffer.update(cx, |buffer, cx| {
19498 if let Some(transaction) = transaction
19499 && !buffer.is_singleton()
19500 {
19501 buffer.push_transaction(&transaction.0, cx);
19502 }
19503 cx.notify();
19504 });
19505
19506 if let Some(transaction_id_now) =
19507 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19508 {
19509 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19510 if has_new_transaction {
19511 editor
19512 .update(cx, |editor, _| {
19513 editor
19514 .selection_history
19515 .insert_transaction(transaction_id_now, selections_prev);
19516 })
19517 .ok();
19518 }
19519 }
19520
19521 Ok(())
19522 })
19523 }
19524
19525 fn organize_imports(
19526 &mut self,
19527 _: &OrganizeImports,
19528 window: &mut Window,
19529 cx: &mut Context<Self>,
19530 ) -> Option<Task<Result<()>>> {
19531 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19532 let project = match &self.project {
19533 Some(project) => project.clone(),
19534 None => return None,
19535 };
19536 Some(self.perform_code_action_kind(
19537 project,
19538 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19539 window,
19540 cx,
19541 ))
19542 }
19543
19544 fn perform_code_action_kind(
19545 &mut self,
19546 project: Entity<Project>,
19547 kind: CodeActionKind,
19548 window: &mut Window,
19549 cx: &mut Context<Self>,
19550 ) -> Task<Result<()>> {
19551 let buffer = self.buffer.clone();
19552 let buffers = buffer.read(cx).all_buffers();
19553 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19554 let apply_action = project.update(cx, |project, cx| {
19555 project.apply_code_action_kind(buffers, kind, true, cx)
19556 });
19557 cx.spawn_in(window, async move |_, cx| {
19558 let transaction = futures::select_biased! {
19559 () = timeout => {
19560 log::warn!("timed out waiting for executing code action");
19561 None
19562 }
19563 transaction = apply_action.log_err().fuse() => transaction,
19564 };
19565 buffer.update(cx, |buffer, cx| {
19566 // check if we need this
19567 if let Some(transaction) = transaction
19568 && !buffer.is_singleton()
19569 {
19570 buffer.push_transaction(&transaction.0, cx);
19571 }
19572 cx.notify();
19573 });
19574 Ok(())
19575 })
19576 }
19577
19578 pub fn restart_language_server(
19579 &mut self,
19580 _: &RestartLanguageServer,
19581 _: &mut Window,
19582 cx: &mut Context<Self>,
19583 ) {
19584 if let Some(project) = self.project.clone() {
19585 self.buffer.update(cx, |multi_buffer, cx| {
19586 project.update(cx, |project, cx| {
19587 project.restart_language_servers_for_buffers(
19588 multi_buffer.all_buffers().into_iter().collect(),
19589 HashSet::default(),
19590 cx,
19591 );
19592 });
19593 })
19594 }
19595 }
19596
19597 pub fn stop_language_server(
19598 &mut self,
19599 _: &StopLanguageServer,
19600 _: &mut Window,
19601 cx: &mut Context<Self>,
19602 ) {
19603 if let Some(project) = self.project.clone() {
19604 self.buffer.update(cx, |multi_buffer, cx| {
19605 project.update(cx, |project, cx| {
19606 project.stop_language_servers_for_buffers(
19607 multi_buffer.all_buffers().into_iter().collect(),
19608 HashSet::default(),
19609 cx,
19610 );
19611 });
19612 });
19613 }
19614 }
19615
19616 fn cancel_language_server_work(
19617 workspace: &mut Workspace,
19618 _: &actions::CancelLanguageServerWork,
19619 _: &mut Window,
19620 cx: &mut Context<Workspace>,
19621 ) {
19622 let project = workspace.project();
19623 let buffers = workspace
19624 .active_item(cx)
19625 .and_then(|item| item.act_as::<Editor>(cx))
19626 .map_or(HashSet::default(), |editor| {
19627 editor.read(cx).buffer.read(cx).all_buffers()
19628 });
19629 project.update(cx, |project, cx| {
19630 project.cancel_language_server_work_for_buffers(buffers, cx);
19631 });
19632 }
19633
19634 fn show_character_palette(
19635 &mut self,
19636 _: &ShowCharacterPalette,
19637 window: &mut Window,
19638 _: &mut Context<Self>,
19639 ) {
19640 window.show_character_palette();
19641 }
19642
19643 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19644 if !self.diagnostics_enabled() {
19645 return;
19646 }
19647
19648 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19649 let buffer = self.buffer.read(cx).snapshot(cx);
19650 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19651 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19652 let is_valid = buffer
19653 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19654 .any(|entry| {
19655 entry.diagnostic.is_primary
19656 && !entry.range.is_empty()
19657 && entry.range.start == primary_range_start
19658 && entry.diagnostic.message == active_diagnostics.active_message
19659 });
19660
19661 if !is_valid {
19662 self.dismiss_diagnostics(cx);
19663 }
19664 }
19665 }
19666
19667 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19668 match &self.active_diagnostics {
19669 ActiveDiagnostic::Group(group) => Some(group),
19670 _ => None,
19671 }
19672 }
19673
19674 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19675 if !self.diagnostics_enabled() {
19676 return;
19677 }
19678 self.dismiss_diagnostics(cx);
19679 self.active_diagnostics = ActiveDiagnostic::All;
19680 }
19681
19682 fn activate_diagnostics(
19683 &mut self,
19684 buffer_id: BufferId,
19685 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19686 window: &mut Window,
19687 cx: &mut Context<Self>,
19688 ) {
19689 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19690 return;
19691 }
19692 self.dismiss_diagnostics(cx);
19693 let snapshot = self.snapshot(window, cx);
19694 let buffer = self.buffer.read(cx).snapshot(cx);
19695 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19696 return;
19697 };
19698
19699 let diagnostic_group = buffer
19700 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19701 .collect::<Vec<_>>();
19702
19703 let language_registry = self
19704 .project()
19705 .map(|project| project.read(cx).languages().clone());
19706
19707 let blocks = renderer.render_group(
19708 diagnostic_group,
19709 buffer_id,
19710 snapshot,
19711 cx.weak_entity(),
19712 language_registry,
19713 cx,
19714 );
19715
19716 let blocks = self.display_map.update(cx, |display_map, cx| {
19717 display_map.insert_blocks(blocks, cx).into_iter().collect()
19718 });
19719 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19720 active_range: buffer.anchor_before(diagnostic.range.start)
19721 ..buffer.anchor_after(diagnostic.range.end),
19722 active_message: diagnostic.diagnostic.message.clone(),
19723 group_id: diagnostic.diagnostic.group_id,
19724 blocks,
19725 });
19726 cx.notify();
19727 }
19728
19729 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19730 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19731 return;
19732 };
19733
19734 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19735 if let ActiveDiagnostic::Group(group) = prev {
19736 self.display_map.update(cx, |display_map, cx| {
19737 display_map.remove_blocks(group.blocks, cx);
19738 });
19739 cx.notify();
19740 }
19741 }
19742
19743 /// Disable inline diagnostics rendering for this editor.
19744 pub fn disable_inline_diagnostics(&mut self) {
19745 self.inline_diagnostics_enabled = false;
19746 self.inline_diagnostics_update = Task::ready(());
19747 self.inline_diagnostics.clear();
19748 }
19749
19750 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19751 self.diagnostics_enabled = false;
19752 self.dismiss_diagnostics(cx);
19753 self.inline_diagnostics_update = Task::ready(());
19754 self.inline_diagnostics.clear();
19755 }
19756
19757 pub fn disable_word_completions(&mut self) {
19758 self.word_completions_enabled = false;
19759 }
19760
19761 pub fn diagnostics_enabled(&self) -> bool {
19762 self.diagnostics_enabled && self.lsp_data_enabled()
19763 }
19764
19765 pub fn inline_diagnostics_enabled(&self) -> bool {
19766 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19767 }
19768
19769 pub fn show_inline_diagnostics(&self) -> bool {
19770 self.show_inline_diagnostics
19771 }
19772
19773 pub fn toggle_inline_diagnostics(
19774 &mut self,
19775 _: &ToggleInlineDiagnostics,
19776 window: &mut Window,
19777 cx: &mut Context<Editor>,
19778 ) {
19779 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19780 self.refresh_inline_diagnostics(false, window, cx);
19781 }
19782
19783 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19784 self.diagnostics_max_severity = severity;
19785 self.display_map.update(cx, |display_map, _| {
19786 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19787 });
19788 }
19789
19790 pub fn toggle_diagnostics(
19791 &mut self,
19792 _: &ToggleDiagnostics,
19793 window: &mut Window,
19794 cx: &mut Context<Editor>,
19795 ) {
19796 if !self.diagnostics_enabled() {
19797 return;
19798 }
19799
19800 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19801 EditorSettings::get_global(cx)
19802 .diagnostics_max_severity
19803 .filter(|severity| severity != &DiagnosticSeverity::Off)
19804 .unwrap_or(DiagnosticSeverity::Hint)
19805 } else {
19806 DiagnosticSeverity::Off
19807 };
19808 self.set_max_diagnostics_severity(new_severity, cx);
19809 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19810 self.active_diagnostics = ActiveDiagnostic::None;
19811 self.inline_diagnostics_update = Task::ready(());
19812 self.inline_diagnostics.clear();
19813 } else {
19814 self.refresh_inline_diagnostics(false, window, cx);
19815 }
19816
19817 cx.notify();
19818 }
19819
19820 pub fn toggle_minimap(
19821 &mut self,
19822 _: &ToggleMinimap,
19823 window: &mut Window,
19824 cx: &mut Context<Editor>,
19825 ) {
19826 if self.supports_minimap(cx) {
19827 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19828 }
19829 }
19830
19831 fn refresh_inline_diagnostics(
19832 &mut self,
19833 debounce: bool,
19834 window: &mut Window,
19835 cx: &mut Context<Self>,
19836 ) {
19837 let max_severity = ProjectSettings::get_global(cx)
19838 .diagnostics
19839 .inline
19840 .max_severity
19841 .unwrap_or(self.diagnostics_max_severity);
19842
19843 if !self.inline_diagnostics_enabled()
19844 || !self.diagnostics_enabled()
19845 || !self.show_inline_diagnostics
19846 || max_severity == DiagnosticSeverity::Off
19847 {
19848 self.inline_diagnostics_update = Task::ready(());
19849 self.inline_diagnostics.clear();
19850 return;
19851 }
19852
19853 let debounce_ms = ProjectSettings::get_global(cx)
19854 .diagnostics
19855 .inline
19856 .update_debounce_ms;
19857 let debounce = if debounce && debounce_ms > 0 {
19858 Some(Duration::from_millis(debounce_ms))
19859 } else {
19860 None
19861 };
19862 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19863 if let Some(debounce) = debounce {
19864 cx.background_executor().timer(debounce).await;
19865 }
19866 let Some(snapshot) = editor.upgrade().map(|editor| {
19867 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19868 }) else {
19869 return;
19870 };
19871
19872 let new_inline_diagnostics = cx
19873 .background_spawn(async move {
19874 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19875 for diagnostic_entry in
19876 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19877 {
19878 let message = diagnostic_entry
19879 .diagnostic
19880 .message
19881 .split_once('\n')
19882 .map(|(line, _)| line)
19883 .map(SharedString::new)
19884 .unwrap_or_else(|| {
19885 SharedString::new(&*diagnostic_entry.diagnostic.message)
19886 });
19887 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19888 let (Ok(i) | Err(i)) = inline_diagnostics
19889 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19890 inline_diagnostics.insert(
19891 i,
19892 (
19893 start_anchor,
19894 InlineDiagnostic {
19895 message,
19896 group_id: diagnostic_entry.diagnostic.group_id,
19897 start: diagnostic_entry.range.start.to_point(&snapshot),
19898 is_primary: diagnostic_entry.diagnostic.is_primary,
19899 severity: diagnostic_entry.diagnostic.severity,
19900 },
19901 ),
19902 );
19903 }
19904 inline_diagnostics
19905 })
19906 .await;
19907
19908 editor
19909 .update(cx, |editor, cx| {
19910 editor.inline_diagnostics = new_inline_diagnostics;
19911 cx.notify();
19912 })
19913 .ok();
19914 });
19915 }
19916
19917 fn pull_diagnostics(
19918 &mut self,
19919 buffer_id: BufferId,
19920 _window: &Window,
19921 cx: &mut Context<Self>,
19922 ) -> Option<()> {
19923 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19924 // skip any LSP updates for it.
19925
19926 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19927 return None;
19928 }
19929 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19930 .diagnostics
19931 .lsp_pull_diagnostics;
19932 if !pull_diagnostics_settings.enabled {
19933 return None;
19934 }
19935 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19936 let project = self.project()?.downgrade();
19937 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19938
19939 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19940 cx.background_executor().timer(debounce).await;
19941 if let Ok(task) = project.update(cx, |project, cx| {
19942 project.lsp_store().update(cx, |lsp_store, cx| {
19943 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19944 })
19945 }) {
19946 task.await.log_err();
19947 }
19948 project
19949 .update(cx, |project, cx| {
19950 project.lsp_store().update(cx, |lsp_store, cx| {
19951 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19952 })
19953 })
19954 .log_err();
19955 });
19956
19957 Some(())
19958 }
19959
19960 pub fn set_selections_from_remote(
19961 &mut self,
19962 selections: Vec<Selection<Anchor>>,
19963 pending_selection: Option<Selection<Anchor>>,
19964 window: &mut Window,
19965 cx: &mut Context<Self>,
19966 ) {
19967 let old_cursor_position = self.selections.newest_anchor().head();
19968 self.selections
19969 .change_with(&self.display_snapshot(cx), |s| {
19970 s.select_anchors(selections);
19971 if let Some(pending_selection) = pending_selection {
19972 s.set_pending(pending_selection, SelectMode::Character);
19973 } else {
19974 s.clear_pending();
19975 }
19976 });
19977 self.selections_did_change(
19978 false,
19979 &old_cursor_position,
19980 SelectionEffects::default(),
19981 window,
19982 cx,
19983 );
19984 }
19985
19986 pub fn transact(
19987 &mut self,
19988 window: &mut Window,
19989 cx: &mut Context<Self>,
19990 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19991 ) -> Option<TransactionId> {
19992 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19993 this.start_transaction_at(Instant::now(), window, cx);
19994 update(this, window, cx);
19995 this.end_transaction_at(Instant::now(), cx)
19996 })
19997 }
19998
19999 pub fn start_transaction_at(
20000 &mut self,
20001 now: Instant,
20002 window: &mut Window,
20003 cx: &mut Context<Self>,
20004 ) -> Option<TransactionId> {
20005 self.end_selection(window, cx);
20006 if let Some(tx_id) = self
20007 .buffer
20008 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
20009 {
20010 self.selection_history
20011 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
20012 cx.emit(EditorEvent::TransactionBegun {
20013 transaction_id: tx_id,
20014 });
20015 Some(tx_id)
20016 } else {
20017 None
20018 }
20019 }
20020
20021 pub fn end_transaction_at(
20022 &mut self,
20023 now: Instant,
20024 cx: &mut Context<Self>,
20025 ) -> Option<TransactionId> {
20026 if let Some(transaction_id) = self
20027 .buffer
20028 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
20029 {
20030 if let Some((_, end_selections)) =
20031 self.selection_history.transaction_mut(transaction_id)
20032 {
20033 *end_selections = Some(self.selections.disjoint_anchors_arc());
20034 } else {
20035 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
20036 }
20037
20038 cx.emit(EditorEvent::Edited { transaction_id });
20039 Some(transaction_id)
20040 } else {
20041 None
20042 }
20043 }
20044
20045 pub fn modify_transaction_selection_history(
20046 &mut self,
20047 transaction_id: TransactionId,
20048 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
20049 ) -> bool {
20050 self.selection_history
20051 .transaction_mut(transaction_id)
20052 .map(modify)
20053 .is_some()
20054 }
20055
20056 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
20057 if self.selection_mark_mode {
20058 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20059 s.move_with(&mut |_, sel| {
20060 sel.collapse_to(sel.head(), SelectionGoal::None);
20061 });
20062 })
20063 }
20064 self.selection_mark_mode = true;
20065 cx.notify();
20066 }
20067
20068 pub fn swap_selection_ends(
20069 &mut self,
20070 _: &actions::SwapSelectionEnds,
20071 window: &mut Window,
20072 cx: &mut Context<Self>,
20073 ) {
20074 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20075 s.move_with(&mut |_, sel| {
20076 if sel.start != sel.end {
20077 sel.reversed = !sel.reversed
20078 }
20079 });
20080 });
20081 self.request_autoscroll(Autoscroll::newest(), cx);
20082 cx.notify();
20083 }
20084
20085 pub fn toggle_focus(
20086 workspace: &mut Workspace,
20087 _: &actions::ToggleFocus,
20088 window: &mut Window,
20089 cx: &mut Context<Workspace>,
20090 ) {
20091 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
20092 return;
20093 };
20094 workspace.activate_item(&item, true, true, window, cx);
20095 }
20096
20097 pub fn toggle_fold(
20098 &mut self,
20099 _: &actions::ToggleFold,
20100 window: &mut Window,
20101 cx: &mut Context<Self>,
20102 ) {
20103 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20104 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20105 let selection = self.selections.newest::<Point>(&display_map);
20106
20107 let range = if selection.is_empty() {
20108 let point = selection.head().to_display_point(&display_map);
20109 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20110 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20111 .to_point(&display_map);
20112 start..end
20113 } else {
20114 selection.range()
20115 };
20116 if display_map.folds_in_range(range).next().is_some() {
20117 self.unfold_lines(&Default::default(), window, cx)
20118 } else {
20119 self.fold(&Default::default(), window, cx)
20120 }
20121 } else {
20122 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20123 let buffer_ids: HashSet<_> = self
20124 .selections
20125 .disjoint_anchor_ranges()
20126 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20127 .collect();
20128
20129 let should_unfold = buffer_ids
20130 .iter()
20131 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20132
20133 for buffer_id in buffer_ids {
20134 if should_unfold {
20135 self.unfold_buffer(buffer_id, cx);
20136 } else {
20137 self.fold_buffer(buffer_id, cx);
20138 }
20139 }
20140 }
20141 }
20142
20143 pub fn toggle_fold_recursive(
20144 &mut self,
20145 _: &actions::ToggleFoldRecursive,
20146 window: &mut Window,
20147 cx: &mut Context<Self>,
20148 ) {
20149 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20150
20151 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20152 let range = if selection.is_empty() {
20153 let point = selection.head().to_display_point(&display_map);
20154 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20155 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20156 .to_point(&display_map);
20157 start..end
20158 } else {
20159 selection.range()
20160 };
20161 if display_map.folds_in_range(range).next().is_some() {
20162 self.unfold_recursive(&Default::default(), window, cx)
20163 } else {
20164 self.fold_recursive(&Default::default(), window, cx)
20165 }
20166 }
20167
20168 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
20169 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20170 let mut to_fold = Vec::new();
20171 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20172 let selections = self.selections.all_adjusted(&display_map);
20173
20174 for selection in selections {
20175 let range = selection.range().sorted();
20176 let buffer_start_row = range.start.row;
20177
20178 if range.start.row != range.end.row {
20179 let mut found = false;
20180 let mut row = range.start.row;
20181 while row <= range.end.row {
20182 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20183 {
20184 found = true;
20185 row = crease.range().end.row + 1;
20186 to_fold.push(crease);
20187 } else {
20188 row += 1
20189 }
20190 }
20191 if found {
20192 continue;
20193 }
20194 }
20195
20196 for row in (0..=range.start.row).rev() {
20197 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20198 && crease.range().end.row >= buffer_start_row
20199 {
20200 to_fold.push(crease);
20201 if row <= range.start.row {
20202 break;
20203 }
20204 }
20205 }
20206 }
20207
20208 self.fold_creases(to_fold, true, window, cx);
20209 } else {
20210 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20211 let buffer_ids = self
20212 .selections
20213 .disjoint_anchor_ranges()
20214 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20215 .collect::<HashSet<_>>();
20216 for buffer_id in buffer_ids {
20217 self.fold_buffer(buffer_id, cx);
20218 }
20219 }
20220 }
20221
20222 pub fn toggle_fold_all(
20223 &mut self,
20224 _: &actions::ToggleFoldAll,
20225 window: &mut Window,
20226 cx: &mut Context<Self>,
20227 ) {
20228 let has_folds = if self.buffer.read(cx).is_singleton() {
20229 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20230 let has_folds = display_map
20231 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20232 .next()
20233 .is_some();
20234 has_folds
20235 } else {
20236 let snapshot = self.buffer.read(cx).snapshot(cx);
20237 let has_folds = snapshot
20238 .all_buffer_ids()
20239 .any(|buffer_id| self.is_buffer_folded(buffer_id, cx));
20240 has_folds
20241 };
20242
20243 if has_folds {
20244 self.unfold_all(&actions::UnfoldAll, window, cx);
20245 } else {
20246 self.fold_all(&actions::FoldAll, window, cx);
20247 }
20248 }
20249
20250 fn fold_at_level(
20251 &mut self,
20252 fold_at: &FoldAtLevel,
20253 window: &mut Window,
20254 cx: &mut Context<Self>,
20255 ) {
20256 if !self.buffer.read(cx).is_singleton() {
20257 return;
20258 }
20259
20260 let fold_at_level = fold_at.0;
20261 let snapshot = self.buffer.read(cx).snapshot(cx);
20262 let mut to_fold = Vec::new();
20263 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20264
20265 let row_ranges_to_keep: Vec<Range<u32>> = self
20266 .selections
20267 .all::<Point>(&self.display_snapshot(cx))
20268 .into_iter()
20269 .map(|sel| sel.start.row..sel.end.row)
20270 .collect();
20271
20272 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20273 while start_row < end_row {
20274 match self
20275 .snapshot(window, cx)
20276 .crease_for_buffer_row(MultiBufferRow(start_row))
20277 {
20278 Some(crease) => {
20279 let nested_start_row = crease.range().start.row + 1;
20280 let nested_end_row = crease.range().end.row;
20281
20282 if current_level < fold_at_level {
20283 stack.push((nested_start_row, nested_end_row, current_level + 1));
20284 } else if current_level == fold_at_level {
20285 // Fold iff there is no selection completely contained within the fold region
20286 if !row_ranges_to_keep.iter().any(|selection| {
20287 selection.end >= nested_start_row
20288 && selection.start <= nested_end_row
20289 }) {
20290 to_fold.push(crease);
20291 }
20292 }
20293
20294 start_row = nested_end_row + 1;
20295 }
20296 None => start_row += 1,
20297 }
20298 }
20299 }
20300
20301 self.fold_creases(to_fold, true, window, cx);
20302 }
20303
20304 pub fn fold_at_level_1(
20305 &mut self,
20306 _: &actions::FoldAtLevel1,
20307 window: &mut Window,
20308 cx: &mut Context<Self>,
20309 ) {
20310 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20311 }
20312
20313 pub fn fold_at_level_2(
20314 &mut self,
20315 _: &actions::FoldAtLevel2,
20316 window: &mut Window,
20317 cx: &mut Context<Self>,
20318 ) {
20319 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20320 }
20321
20322 pub fn fold_at_level_3(
20323 &mut self,
20324 _: &actions::FoldAtLevel3,
20325 window: &mut Window,
20326 cx: &mut Context<Self>,
20327 ) {
20328 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20329 }
20330
20331 pub fn fold_at_level_4(
20332 &mut self,
20333 _: &actions::FoldAtLevel4,
20334 window: &mut Window,
20335 cx: &mut Context<Self>,
20336 ) {
20337 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20338 }
20339
20340 pub fn fold_at_level_5(
20341 &mut self,
20342 _: &actions::FoldAtLevel5,
20343 window: &mut Window,
20344 cx: &mut Context<Self>,
20345 ) {
20346 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20347 }
20348
20349 pub fn fold_at_level_6(
20350 &mut self,
20351 _: &actions::FoldAtLevel6,
20352 window: &mut Window,
20353 cx: &mut Context<Self>,
20354 ) {
20355 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20356 }
20357
20358 pub fn fold_at_level_7(
20359 &mut self,
20360 _: &actions::FoldAtLevel7,
20361 window: &mut Window,
20362 cx: &mut Context<Self>,
20363 ) {
20364 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20365 }
20366
20367 pub fn fold_at_level_8(
20368 &mut self,
20369 _: &actions::FoldAtLevel8,
20370 window: &mut Window,
20371 cx: &mut Context<Self>,
20372 ) {
20373 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20374 }
20375
20376 pub fn fold_at_level_9(
20377 &mut self,
20378 _: &actions::FoldAtLevel9,
20379 window: &mut Window,
20380 cx: &mut Context<Self>,
20381 ) {
20382 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20383 }
20384
20385 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20386 if self.buffer.read(cx).is_singleton() {
20387 let mut fold_ranges = Vec::new();
20388 let snapshot = self.buffer.read(cx).snapshot(cx);
20389
20390 for row in 0..snapshot.max_row().0 {
20391 if let Some(foldable_range) = self
20392 .snapshot(window, cx)
20393 .crease_for_buffer_row(MultiBufferRow(row))
20394 {
20395 fold_ranges.push(foldable_range);
20396 }
20397 }
20398
20399 self.fold_creases(fold_ranges, true, window, cx);
20400 } else {
20401 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20402 editor
20403 .update_in(cx, |editor, _, cx| {
20404 let snapshot = editor.buffer.read(cx).snapshot(cx);
20405 for buffer_id in snapshot.all_buffer_ids() {
20406 editor.fold_buffer(buffer_id, cx);
20407 }
20408 })
20409 .ok();
20410 });
20411 }
20412 }
20413
20414 pub fn fold_function_bodies(
20415 &mut self,
20416 _: &actions::FoldFunctionBodies,
20417 window: &mut Window,
20418 cx: &mut Context<Self>,
20419 ) {
20420 let snapshot = self.buffer.read(cx).snapshot(cx);
20421
20422 let ranges = snapshot
20423 .text_object_ranges(
20424 MultiBufferOffset(0)..snapshot.len(),
20425 TreeSitterOptions::default(),
20426 )
20427 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20428 .collect::<Vec<_>>();
20429
20430 let creases = ranges
20431 .into_iter()
20432 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20433 .collect();
20434
20435 self.fold_creases(creases, true, window, cx);
20436 }
20437
20438 pub fn fold_recursive(
20439 &mut self,
20440 _: &actions::FoldRecursive,
20441 window: &mut Window,
20442 cx: &mut Context<Self>,
20443 ) {
20444 let mut to_fold = Vec::new();
20445 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20446 let selections = self.selections.all_adjusted(&display_map);
20447
20448 for selection in selections {
20449 let range = selection.range().sorted();
20450 let buffer_start_row = range.start.row;
20451
20452 if range.start.row != range.end.row {
20453 let mut found = false;
20454 for row in range.start.row..=range.end.row {
20455 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20456 found = true;
20457 to_fold.push(crease);
20458 }
20459 }
20460 if found {
20461 continue;
20462 }
20463 }
20464
20465 for row in (0..=range.start.row).rev() {
20466 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20467 if crease.range().end.row >= buffer_start_row {
20468 to_fold.push(crease);
20469 } else {
20470 break;
20471 }
20472 }
20473 }
20474 }
20475
20476 self.fold_creases(to_fold, true, window, cx);
20477 }
20478
20479 pub fn fold_at(
20480 &mut self,
20481 buffer_row: MultiBufferRow,
20482 window: &mut Window,
20483 cx: &mut Context<Self>,
20484 ) {
20485 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20486
20487 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20488 let autoscroll = self
20489 .selections
20490 .all::<Point>(&display_map)
20491 .iter()
20492 .any(|selection| crease.range().overlaps(&selection.range()));
20493
20494 self.fold_creases(vec![crease], autoscroll, window, cx);
20495 }
20496 }
20497
20498 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20499 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20500 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20501 let buffer = display_map.buffer_snapshot();
20502 let selections = self.selections.all::<Point>(&display_map);
20503 let ranges = selections
20504 .iter()
20505 .map(|s| {
20506 let range = s.display_range(&display_map).sorted();
20507 let mut start = range.start.to_point(&display_map);
20508 let mut end = range.end.to_point(&display_map);
20509 start.column = 0;
20510 end.column = buffer.line_len(MultiBufferRow(end.row));
20511 start..end
20512 })
20513 .collect::<Vec<_>>();
20514
20515 self.unfold_ranges(&ranges, true, true, cx);
20516 } else {
20517 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20518 let buffer_ids = self
20519 .selections
20520 .disjoint_anchor_ranges()
20521 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20522 .collect::<HashSet<_>>();
20523 for buffer_id in buffer_ids {
20524 self.unfold_buffer(buffer_id, cx);
20525 }
20526 }
20527 }
20528
20529 pub fn unfold_recursive(
20530 &mut self,
20531 _: &UnfoldRecursive,
20532 _window: &mut Window,
20533 cx: &mut Context<Self>,
20534 ) {
20535 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20536 let selections = self.selections.all::<Point>(&display_map);
20537 let ranges = selections
20538 .iter()
20539 .map(|s| {
20540 let mut range = s.display_range(&display_map).sorted();
20541 *range.start.column_mut() = 0;
20542 *range.end.column_mut() = display_map.line_len(range.end.row());
20543 let start = range.start.to_point(&display_map);
20544 let end = range.end.to_point(&display_map);
20545 start..end
20546 })
20547 .collect::<Vec<_>>();
20548
20549 self.unfold_ranges(&ranges, true, true, cx);
20550 }
20551
20552 pub fn unfold_at(
20553 &mut self,
20554 buffer_row: MultiBufferRow,
20555 _window: &mut Window,
20556 cx: &mut Context<Self>,
20557 ) {
20558 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20559
20560 let intersection_range = Point::new(buffer_row.0, 0)
20561 ..Point::new(
20562 buffer_row.0,
20563 display_map.buffer_snapshot().line_len(buffer_row),
20564 );
20565
20566 let autoscroll = self
20567 .selections
20568 .all::<Point>(&display_map)
20569 .iter()
20570 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20571
20572 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20573 }
20574
20575 pub fn unfold_all(
20576 &mut self,
20577 _: &actions::UnfoldAll,
20578 _window: &mut Window,
20579 cx: &mut Context<Self>,
20580 ) {
20581 if self.buffer.read(cx).is_singleton() {
20582 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20583 self.unfold_ranges(
20584 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20585 true,
20586 true,
20587 cx,
20588 );
20589 } else {
20590 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20591 editor
20592 .update(cx, |editor, cx| {
20593 let snapshot = editor.buffer.read(cx).snapshot(cx);
20594 for buffer_id in snapshot.all_buffer_ids() {
20595 editor.unfold_buffer(buffer_id, cx);
20596 }
20597 })
20598 .ok();
20599 });
20600 }
20601 }
20602
20603 pub fn fold_selected_ranges(
20604 &mut self,
20605 _: &FoldSelectedRanges,
20606 window: &mut Window,
20607 cx: &mut Context<Self>,
20608 ) {
20609 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20610 let selections = self.selections.all_adjusted(&display_map);
20611 let ranges = selections
20612 .into_iter()
20613 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20614 .collect::<Vec<_>>();
20615 self.fold_creases(ranges, true, window, cx);
20616 }
20617
20618 pub fn fold_ranges<T: ToOffset + Clone>(
20619 &mut self,
20620 ranges: Vec<Range<T>>,
20621 auto_scroll: bool,
20622 window: &mut Window,
20623 cx: &mut Context<Self>,
20624 ) {
20625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20626 let ranges = ranges
20627 .into_iter()
20628 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20629 .collect::<Vec<_>>();
20630 self.fold_creases(ranges, auto_scroll, window, cx);
20631 }
20632
20633 pub fn fold_creases<T: ToOffset + Clone>(
20634 &mut self,
20635 creases: Vec<Crease<T>>,
20636 auto_scroll: bool,
20637 window: &mut Window,
20638 cx: &mut Context<Self>,
20639 ) {
20640 if creases.is_empty() {
20641 return;
20642 }
20643
20644 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20645
20646 if auto_scroll {
20647 self.request_autoscroll(Autoscroll::fit(), cx);
20648 }
20649
20650 cx.notify();
20651
20652 self.scrollbar_marker_state.dirty = true;
20653 self.update_data_on_scroll(window, cx);
20654 self.folds_did_change(cx);
20655 }
20656
20657 /// Removes any folds whose ranges intersect any of the given ranges.
20658 pub fn unfold_ranges<T: ToOffset + Clone>(
20659 &mut self,
20660 ranges: &[Range<T>],
20661 inclusive: bool,
20662 auto_scroll: bool,
20663 cx: &mut Context<Self>,
20664 ) {
20665 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20666 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20667 });
20668 self.folds_did_change(cx);
20669 }
20670
20671 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20672 self.fold_buffers([buffer_id], cx);
20673 }
20674
20675 pub fn fold_buffers(
20676 &mut self,
20677 buffer_ids: impl IntoIterator<Item = BufferId>,
20678 cx: &mut Context<Self>,
20679 ) {
20680 if self.buffer().read(cx).is_singleton() {
20681 return;
20682 }
20683
20684 let ids_to_fold: Vec<BufferId> = buffer_ids
20685 .into_iter()
20686 .filter(|id| !self.is_buffer_folded(*id, cx))
20687 .collect();
20688
20689 if ids_to_fold.is_empty() {
20690 return;
20691 }
20692
20693 self.display_map.update(cx, |display_map, cx| {
20694 display_map.fold_buffers(ids_to_fold.clone(), cx)
20695 });
20696
20697 let snapshot = self.display_snapshot(cx);
20698 self.selections.change_with(&snapshot, |selections| {
20699 for buffer_id in ids_to_fold.iter().copied() {
20700 selections.remove_selections_from_buffer(buffer_id);
20701 }
20702 });
20703
20704 cx.emit(EditorEvent::BufferFoldToggled {
20705 ids: ids_to_fold,
20706 folded: true,
20707 });
20708 cx.notify();
20709 }
20710
20711 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20712 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20713 return;
20714 }
20715 self.display_map.update(cx, |display_map, cx| {
20716 display_map.unfold_buffers([buffer_id], cx);
20717 });
20718 cx.emit(EditorEvent::BufferFoldToggled {
20719 ids: vec![buffer_id],
20720 folded: false,
20721 });
20722 cx.notify();
20723 }
20724
20725 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20726 self.display_map.read(cx).is_buffer_folded(buffer)
20727 }
20728
20729 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20730 if self.buffer().read(cx).is_singleton() {
20731 return false;
20732 }
20733 !self.folded_buffers(cx).is_empty()
20734 }
20735
20736 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20737 self.display_map.read(cx).folded_buffers()
20738 }
20739
20740 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20741 self.display_map.update(cx, |display_map, cx| {
20742 display_map.disable_header_for_buffer(buffer_id, cx);
20743 });
20744 cx.notify();
20745 }
20746
20747 /// Removes any folds with the given ranges.
20748 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20749 &mut self,
20750 ranges: &[Range<T>],
20751 type_id: TypeId,
20752 auto_scroll: bool,
20753 cx: &mut Context<Self>,
20754 ) {
20755 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20756 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20757 });
20758 self.folds_did_change(cx);
20759 }
20760
20761 fn remove_folds_with<T: ToOffset + Clone>(
20762 &mut self,
20763 ranges: &[Range<T>],
20764 auto_scroll: bool,
20765 cx: &mut Context<Self>,
20766 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20767 ) {
20768 if ranges.is_empty() {
20769 return;
20770 }
20771
20772 self.display_map.update(cx, update);
20773
20774 if auto_scroll {
20775 self.request_autoscroll(Autoscroll::fit(), cx);
20776 }
20777
20778 cx.notify();
20779 self.scrollbar_marker_state.dirty = true;
20780 self.active_indent_guides_state.dirty = true;
20781 }
20782
20783 pub fn update_renderer_widths(
20784 &mut self,
20785 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20786 cx: &mut Context<Self>,
20787 ) -> bool {
20788 self.display_map
20789 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20790 }
20791
20792 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20793 self.display_map.read(cx).fold_placeholder.clone()
20794 }
20795
20796 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20797 self.buffer.update(cx, |buffer, cx| {
20798 buffer.set_all_diff_hunks_expanded(cx);
20799 });
20800 }
20801
20802 pub fn expand_all_diff_hunks(
20803 &mut self,
20804 _: &ExpandAllDiffHunks,
20805 _window: &mut Window,
20806 cx: &mut Context<Self>,
20807 ) {
20808 self.buffer.update(cx, |buffer, cx| {
20809 buffer.expand_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
20810 });
20811 }
20812
20813 pub fn collapse_all_diff_hunks(
20814 &mut self,
20815 _: &CollapseAllDiffHunks,
20816 _window: &mut Window,
20817 cx: &mut Context<Self>,
20818 ) {
20819 self.buffer.update(cx, |buffer, cx| {
20820 buffer.collapse_diff_hunks(vec![Anchor::Min..Anchor::Max], cx)
20821 });
20822 }
20823
20824 pub fn toggle_selected_diff_hunks(
20825 &mut self,
20826 _: &ToggleSelectedDiffHunks,
20827 _window: &mut Window,
20828 cx: &mut Context<Self>,
20829 ) {
20830 let ranges: Vec<_> = self
20831 .selections
20832 .disjoint_anchors()
20833 .iter()
20834 .map(|s| s.range())
20835 .collect();
20836 self.toggle_diff_hunks_in_ranges(ranges, cx);
20837 }
20838
20839 pub fn diff_hunks_in_ranges<'a>(
20840 &'a self,
20841 ranges: &'a [Range<Anchor>],
20842 buffer: &'a MultiBufferSnapshot,
20843 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20844 ranges.iter().flat_map(move |range| {
20845 let end_excerpt = buffer.excerpt_containing(range.end..range.end);
20846 let range = range.to_point(buffer);
20847 let mut peek_end = range.end;
20848 if range.end.row < buffer.max_row().0 {
20849 peek_end = Point::new(range.end.row + 1, 0);
20850 }
20851 buffer
20852 .diff_hunks_in_range(range.start..peek_end)
20853 .filter(move |hunk| {
20854 if let Some((_, excerpt_range)) = &end_excerpt
20855 && let Some(end_anchor) =
20856 buffer.anchor_in_excerpt(excerpt_range.context.end)
20857 && let Some(hunk_end_anchor) =
20858 buffer.anchor_in_excerpt(hunk.excerpt_range.context.end)
20859 && hunk_end_anchor.cmp(&end_anchor, buffer).is_gt()
20860 {
20861 false
20862 } else {
20863 true
20864 }
20865 })
20866 })
20867 }
20868
20869 pub fn has_stageable_diff_hunks_in_ranges(
20870 &self,
20871 ranges: &[Range<Anchor>],
20872 snapshot: &MultiBufferSnapshot,
20873 ) -> bool {
20874 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20875 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20876 }
20877
20878 pub fn toggle_staged_selected_diff_hunks(
20879 &mut self,
20880 _: &::git::ToggleStaged,
20881 _: &mut Window,
20882 cx: &mut Context<Self>,
20883 ) {
20884 let snapshot = self.buffer.read(cx).snapshot(cx);
20885 let ranges: Vec<_> = self
20886 .selections
20887 .disjoint_anchors()
20888 .iter()
20889 .map(|s| s.range())
20890 .collect();
20891 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20892 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20893 }
20894
20895 pub fn set_render_diff_hunk_controls(
20896 &mut self,
20897 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20898 cx: &mut Context<Self>,
20899 ) {
20900 self.render_diff_hunk_controls = render_diff_hunk_controls;
20901 cx.notify();
20902 }
20903
20904 pub fn stage_and_next(
20905 &mut self,
20906 _: &::git::StageAndNext,
20907 window: &mut Window,
20908 cx: &mut Context<Self>,
20909 ) {
20910 self.do_stage_or_unstage_and_next(true, window, cx);
20911 }
20912
20913 pub fn unstage_and_next(
20914 &mut self,
20915 _: &::git::UnstageAndNext,
20916 window: &mut Window,
20917 cx: &mut Context<Self>,
20918 ) {
20919 self.do_stage_or_unstage_and_next(false, window, cx);
20920 }
20921
20922 pub fn stage_or_unstage_diff_hunks(
20923 &mut self,
20924 stage: bool,
20925 ranges: Vec<Range<Anchor>>,
20926 cx: &mut Context<Self>,
20927 ) {
20928 if self.delegate_stage_and_restore {
20929 let snapshot = self.buffer.read(cx).snapshot(cx);
20930 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20931 if !hunks.is_empty() {
20932 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20933 }
20934 return;
20935 }
20936 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20937 cx.spawn(async move |this, cx| {
20938 task.await?;
20939 this.update(cx, |this, cx| {
20940 let snapshot = this.buffer.read(cx).snapshot(cx);
20941 let chunk_by = this
20942 .diff_hunks_in_ranges(&ranges, &snapshot)
20943 .chunk_by(|hunk| hunk.buffer_id);
20944 for (buffer_id, hunks) in &chunk_by {
20945 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20946 }
20947 })
20948 })
20949 .detach_and_log_err(cx);
20950 }
20951
20952 fn save_buffers_for_ranges_if_needed(
20953 &mut self,
20954 ranges: &[Range<Anchor>],
20955 cx: &mut Context<Editor>,
20956 ) -> Task<Result<()>> {
20957 let multibuffer = self.buffer.read(cx);
20958 let snapshot = multibuffer.read(cx);
20959 let buffer_ids: HashSet<_> = ranges
20960 .iter()
20961 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20962 .collect();
20963 drop(snapshot);
20964
20965 let mut buffers = HashSet::default();
20966 for buffer_id in buffer_ids {
20967 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20968 let buffer = buffer_entity.read(cx);
20969 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20970 {
20971 buffers.insert(buffer_entity);
20972 }
20973 }
20974 }
20975
20976 if let Some(project) = &self.project {
20977 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20978 } else {
20979 Task::ready(Ok(()))
20980 }
20981 }
20982
20983 fn do_stage_or_unstage_and_next(
20984 &mut self,
20985 stage: bool,
20986 window: &mut Window,
20987 cx: &mut Context<Self>,
20988 ) {
20989 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20990
20991 if ranges.iter().any(|range| range.start != range.end) {
20992 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20993 return;
20994 }
20995
20996 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20997
20998 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20999 let wrap_around = !all_diff_hunks_expanded;
21000 let snapshot = self.snapshot(window, cx);
21001 let position = self
21002 .selections
21003 .newest::<Point>(&snapshot.display_snapshot)
21004 .head();
21005
21006 self.go_to_hunk_before_or_after_position(
21007 &snapshot,
21008 position,
21009 Direction::Next,
21010 wrap_around,
21011 window,
21012 cx,
21013 );
21014 }
21015
21016 pub(crate) fn do_stage_or_unstage(
21017 &self,
21018 stage: bool,
21019 buffer_id: BufferId,
21020 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
21021 cx: &mut App,
21022 ) -> Option<()> {
21023 let project = self.project()?;
21024 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
21025 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
21026 let buffer_snapshot = buffer.read(cx).snapshot();
21027 let file_exists = buffer_snapshot
21028 .file()
21029 .is_some_and(|file| file.disk_state().exists());
21030 diff.update(cx, |diff, cx| {
21031 diff.stage_or_unstage_hunks(
21032 stage,
21033 &hunks
21034 .map(|hunk| buffer_diff::DiffHunk {
21035 buffer_range: hunk.buffer_range,
21036 // We don't need to pass in word diffs here because they're only used for rendering and
21037 // this function changes internal state
21038 base_word_diffs: Vec::default(),
21039 buffer_word_diffs: Vec::default(),
21040 diff_base_byte_range: hunk.diff_base_byte_range.start.0
21041 ..hunk.diff_base_byte_range.end.0,
21042 secondary_status: hunk.status.secondary,
21043 range: Point::zero()..Point::zero(), // unused
21044 })
21045 .collect::<Vec<_>>(),
21046 &buffer_snapshot,
21047 file_exists,
21048 cx,
21049 )
21050 });
21051 None
21052 }
21053
21054 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
21055 let ranges: Vec<_> = self
21056 .selections
21057 .disjoint_anchors()
21058 .iter()
21059 .map(|s| s.range())
21060 .collect();
21061 self.buffer
21062 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
21063 }
21064
21065 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
21066 self.buffer.update(cx, |buffer, cx| {
21067 let ranges = vec![Anchor::Min..Anchor::Max];
21068 if !buffer.all_diff_hunks_expanded()
21069 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
21070 {
21071 buffer.collapse_diff_hunks(ranges, cx);
21072 true
21073 } else {
21074 false
21075 }
21076 })
21077 }
21078
21079 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
21080 if self.buffer.read(cx).all_diff_hunks_expanded() {
21081 return true;
21082 }
21083 let ranges = vec![Anchor::Min..Anchor::Max];
21084 self.buffer
21085 .read(cx)
21086 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
21087 }
21088
21089 fn toggle_diff_hunks_in_ranges(
21090 &mut self,
21091 ranges: Vec<Range<Anchor>>,
21092 cx: &mut Context<Editor>,
21093 ) {
21094 self.buffer.update(cx, |buffer, cx| {
21095 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
21096 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
21097 })
21098 }
21099
21100 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
21101 self.buffer.update(cx, |buffer, cx| {
21102 buffer.toggle_single_diff_hunk(range, cx);
21103 })
21104 }
21105
21106 pub(crate) fn apply_all_diff_hunks(
21107 &mut self,
21108 _: &ApplyAllDiffHunks,
21109 window: &mut Window,
21110 cx: &mut Context<Self>,
21111 ) {
21112 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21113
21114 let buffers = self.buffer.read(cx).all_buffers();
21115 for branch_buffer in buffers {
21116 branch_buffer.update(cx, |branch_buffer, cx| {
21117 branch_buffer.merge_into_base(Vec::new(), cx);
21118 });
21119 }
21120
21121 if let Some(project) = self.project.clone() {
21122 self.save(
21123 SaveOptions {
21124 format: true,
21125 autosave: false,
21126 },
21127 project,
21128 window,
21129 cx,
21130 )
21131 .detach_and_log_err(cx);
21132 }
21133 }
21134
21135 pub(crate) fn apply_selected_diff_hunks(
21136 &mut self,
21137 _: &ApplyDiffHunk,
21138 window: &mut Window,
21139 cx: &mut Context<Self>,
21140 ) {
21141 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21142 let snapshot = self.snapshot(window, cx);
21143 let hunks = snapshot.hunks_for_ranges(
21144 self.selections
21145 .all(&snapshot.display_snapshot)
21146 .into_iter()
21147 .map(|selection| selection.range()),
21148 );
21149 let mut ranges_by_buffer = HashMap::default();
21150 self.transact(window, cx, |editor, _window, cx| {
21151 for hunk in hunks {
21152 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
21153 ranges_by_buffer
21154 .entry(buffer.clone())
21155 .or_insert_with(Vec::new)
21156 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
21157 }
21158 }
21159
21160 for (buffer, ranges) in ranges_by_buffer {
21161 buffer.update(cx, |buffer, cx| {
21162 buffer.merge_into_base(ranges, cx);
21163 });
21164 }
21165 });
21166
21167 if let Some(project) = self.project.clone() {
21168 self.save(
21169 SaveOptions {
21170 format: true,
21171 autosave: false,
21172 },
21173 project,
21174 window,
21175 cx,
21176 )
21177 .detach_and_log_err(cx);
21178 }
21179 }
21180
21181 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
21182 if hovered != self.gutter_hovered {
21183 self.gutter_hovered = hovered;
21184 cx.notify();
21185 }
21186 }
21187
21188 pub fn insert_blocks(
21189 &mut self,
21190 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
21191 autoscroll: Option<Autoscroll>,
21192 cx: &mut Context<Self>,
21193 ) -> Vec<CustomBlockId> {
21194 let blocks = self
21195 .display_map
21196 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
21197 if let Some(autoscroll) = autoscroll {
21198 self.request_autoscroll(autoscroll, cx);
21199 }
21200 cx.notify();
21201 blocks
21202 }
21203
21204 pub fn resize_blocks(
21205 &mut self,
21206 heights: HashMap<CustomBlockId, u32>,
21207 autoscroll: Option<Autoscroll>,
21208 cx: &mut Context<Self>,
21209 ) {
21210 self.display_map
21211 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21212 if let Some(autoscroll) = autoscroll {
21213 self.request_autoscroll(autoscroll, cx);
21214 }
21215 cx.notify();
21216 }
21217
21218 pub fn replace_blocks(
21219 &mut self,
21220 renderers: HashMap<CustomBlockId, RenderBlock>,
21221 autoscroll: Option<Autoscroll>,
21222 cx: &mut Context<Self>,
21223 ) {
21224 self.display_map
21225 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21226 if let Some(autoscroll) = autoscroll {
21227 self.request_autoscroll(autoscroll, cx);
21228 }
21229 cx.notify();
21230 }
21231
21232 pub fn remove_blocks(
21233 &mut self,
21234 block_ids: HashSet<CustomBlockId>,
21235 autoscroll: Option<Autoscroll>,
21236 cx: &mut Context<Self>,
21237 ) {
21238 self.display_map.update(cx, |display_map, cx| {
21239 display_map.remove_blocks(block_ids, cx)
21240 });
21241 if let Some(autoscroll) = autoscroll {
21242 self.request_autoscroll(autoscroll, cx);
21243 }
21244 cx.notify();
21245 }
21246
21247 pub fn row_for_block(
21248 &self,
21249 block_id: CustomBlockId,
21250 cx: &mut Context<Self>,
21251 ) -> Option<DisplayRow> {
21252 self.display_map
21253 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21254 }
21255
21256 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21257 self.focused_block = Some(focused_block);
21258 }
21259
21260 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21261 self.focused_block.take()
21262 }
21263
21264 pub fn insert_creases(
21265 &mut self,
21266 creases: impl IntoIterator<Item = Crease<Anchor>>,
21267 cx: &mut Context<Self>,
21268 ) -> Vec<CreaseId> {
21269 self.display_map
21270 .update(cx, |map, cx| map.insert_creases(creases, cx))
21271 }
21272
21273 pub fn remove_creases(
21274 &mut self,
21275 ids: impl IntoIterator<Item = CreaseId>,
21276 cx: &mut Context<Self>,
21277 ) -> Vec<(CreaseId, Range<Anchor>)> {
21278 self.display_map
21279 .update(cx, |map, cx| map.remove_creases(ids, cx))
21280 }
21281
21282 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21283 self.display_map
21284 .update(cx, |map, cx| map.snapshot(cx))
21285 .longest_row()
21286 }
21287
21288 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21289 self.display_map
21290 .update(cx, |map, cx| map.snapshot(cx))
21291 .max_point()
21292 }
21293
21294 pub fn text(&self, cx: &App) -> String {
21295 self.buffer.read(cx).read(cx).text()
21296 }
21297
21298 pub fn is_empty(&self, cx: &App) -> bool {
21299 self.buffer.read(cx).read(cx).is_empty()
21300 }
21301
21302 pub fn text_option(&self, cx: &App) -> Option<String> {
21303 let text = self.text(cx);
21304 let text = text.trim();
21305
21306 if text.is_empty() {
21307 return None;
21308 }
21309
21310 Some(text.to_string())
21311 }
21312
21313 pub fn set_text(
21314 &mut self,
21315 text: impl Into<Arc<str>>,
21316 window: &mut Window,
21317 cx: &mut Context<Self>,
21318 ) {
21319 self.transact(window, cx, |this, _, cx| {
21320 this.buffer
21321 .read(cx)
21322 .as_singleton()
21323 .expect("you can only call set_text on editors for singleton buffers")
21324 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21325 });
21326 }
21327
21328 pub fn display_text(&self, cx: &mut App) -> String {
21329 self.display_map
21330 .update(cx, |map, cx| map.snapshot(cx))
21331 .text()
21332 }
21333
21334 fn create_minimap(
21335 &self,
21336 minimap_settings: MinimapSettings,
21337 window: &mut Window,
21338 cx: &mut Context<Self>,
21339 ) -> Option<Entity<Self>> {
21340 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21341 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21342 }
21343
21344 fn initialize_new_minimap(
21345 &self,
21346 minimap_settings: MinimapSettings,
21347 window: &mut Window,
21348 cx: &mut Context<Self>,
21349 ) -> Entity<Self> {
21350 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21351 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21352
21353 let mut minimap = Editor::new_internal(
21354 EditorMode::Minimap {
21355 parent: cx.weak_entity(),
21356 },
21357 self.buffer.clone(),
21358 None,
21359 Some(self.display_map.clone()),
21360 window,
21361 cx,
21362 );
21363 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21364 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21365 minimap.scroll_manager.clone_state(
21366 &self.scroll_manager,
21367 &my_snapshot,
21368 &minimap_snapshot,
21369 cx,
21370 );
21371 minimap.set_text_style_refinement(TextStyleRefinement {
21372 font_size: Some(MINIMAP_FONT_SIZE),
21373 font_weight: Some(MINIMAP_FONT_WEIGHT),
21374 font_family: Some(MINIMAP_FONT_FAMILY),
21375 ..Default::default()
21376 });
21377 minimap.update_minimap_configuration(minimap_settings, cx);
21378 cx.new(|_| minimap)
21379 }
21380
21381 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21382 let current_line_highlight = minimap_settings
21383 .current_line_highlight
21384 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21385 self.set_current_line_highlight(Some(current_line_highlight));
21386 }
21387
21388 pub fn minimap(&self) -> Option<&Entity<Self>> {
21389 self.minimap
21390 .as_ref()
21391 .filter(|_| self.minimap_visibility.visible())
21392 }
21393
21394 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21395 let mut wrap_guides = smallvec![];
21396
21397 if self.show_wrap_guides == Some(false) {
21398 return wrap_guides;
21399 }
21400
21401 let settings = self.buffer.read(cx).language_settings(cx);
21402 if settings.show_wrap_guides {
21403 match self.soft_wrap_mode(cx) {
21404 SoftWrap::Column(soft_wrap) => {
21405 wrap_guides.push((soft_wrap as usize, true));
21406 }
21407 SoftWrap::Bounded(soft_wrap) => {
21408 wrap_guides.push((soft_wrap as usize, true));
21409 }
21410 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21411 }
21412 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21413 }
21414
21415 wrap_guides
21416 }
21417
21418 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21419 let settings = self.buffer.read(cx).language_settings(cx);
21420 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21421 match mode {
21422 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21423 SoftWrap::None
21424 }
21425 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21426 language_settings::SoftWrap::PreferredLineLength => {
21427 SoftWrap::Column(settings.preferred_line_length)
21428 }
21429 language_settings::SoftWrap::Bounded => {
21430 SoftWrap::Bounded(settings.preferred_line_length)
21431 }
21432 }
21433 }
21434
21435 pub fn set_soft_wrap_mode(
21436 &mut self,
21437 mode: language_settings::SoftWrap,
21438 cx: &mut Context<Self>,
21439 ) {
21440 self.soft_wrap_mode_override = Some(mode);
21441 cx.notify();
21442 }
21443
21444 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21445 self.hard_wrap = hard_wrap;
21446 cx.notify();
21447 }
21448
21449 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21450 self.text_style_refinement = Some(style);
21451 }
21452
21453 /// called by the Element so we know what style we were most recently rendered with.
21454 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21455 // We intentionally do not inform the display map about the minimap style
21456 // so that wrapping is not recalculated and stays consistent for the editor
21457 // and its linked minimap.
21458 if !self.mode.is_minimap() {
21459 let font = style.text.font();
21460 let font_size = style.text.font_size.to_pixels(window.rem_size());
21461 let display_map = self
21462 .placeholder_display_map
21463 .as_ref()
21464 .filter(|_| self.is_empty(cx))
21465 .unwrap_or(&self.display_map);
21466
21467 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21468 }
21469 self.style = Some(style);
21470 }
21471
21472 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21473 if self.style.is_none() {
21474 self.style = Some(self.create_style(cx));
21475 }
21476 self.style.as_ref().unwrap()
21477 }
21478
21479 // Called by the element. This method is not designed to be called outside of the editor
21480 // element's layout code because it does not notify when rewrapping is computed synchronously.
21481 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21482 if self.is_empty(cx) {
21483 self.placeholder_display_map
21484 .as_ref()
21485 .map_or(false, |display_map| {
21486 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21487 })
21488 } else {
21489 self.display_map
21490 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21491 }
21492 }
21493
21494 pub fn set_soft_wrap(&mut self) {
21495 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21496 }
21497
21498 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21499 if self.soft_wrap_mode_override.is_some() {
21500 self.soft_wrap_mode_override.take();
21501 } else {
21502 let soft_wrap = match self.soft_wrap_mode(cx) {
21503 SoftWrap::GitDiff => return,
21504 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21505 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21506 language_settings::SoftWrap::None
21507 }
21508 };
21509 self.soft_wrap_mode_override = Some(soft_wrap);
21510 }
21511 cx.notify();
21512 }
21513
21514 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21515 let Some(workspace) = self.workspace() else {
21516 return;
21517 };
21518 let fs = workspace.read(cx).app_state().fs.clone();
21519 let current_show = TabBarSettings::get_global(cx).show;
21520 update_settings_file(fs, cx, move |setting, _| {
21521 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21522 });
21523 }
21524
21525 pub fn toggle_indent_guides(
21526 &mut self,
21527 _: &ToggleIndentGuides,
21528 _: &mut Window,
21529 cx: &mut Context<Self>,
21530 ) {
21531 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21532 self.buffer
21533 .read(cx)
21534 .language_settings(cx)
21535 .indent_guides
21536 .enabled
21537 });
21538 self.show_indent_guides = Some(!currently_enabled);
21539 cx.notify();
21540 }
21541
21542 fn should_show_indent_guides(&self) -> Option<bool> {
21543 self.show_indent_guides
21544 }
21545
21546 pub fn disable_indent_guides_for_buffer(
21547 &mut self,
21548 buffer_id: BufferId,
21549 cx: &mut Context<Self>,
21550 ) {
21551 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21552 cx.notify();
21553 }
21554
21555 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21556 self.buffers_with_disabled_indent_guides
21557 .contains(&buffer_id)
21558 }
21559
21560 pub fn toggle_line_numbers(
21561 &mut self,
21562 _: &ToggleLineNumbers,
21563 _: &mut Window,
21564 cx: &mut Context<Self>,
21565 ) {
21566 let mut editor_settings = EditorSettings::get_global(cx).clone();
21567 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21568 EditorSettings::override_global(editor_settings, cx);
21569 }
21570
21571 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21572 if let Some(show_line_numbers) = self.show_line_numbers {
21573 return show_line_numbers;
21574 }
21575 EditorSettings::get_global(cx).gutter.line_numbers
21576 }
21577
21578 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21579 match (
21580 self.use_relative_line_numbers,
21581 EditorSettings::get_global(cx).relative_line_numbers,
21582 ) {
21583 (None, setting) => setting,
21584 (Some(false), _) => RelativeLineNumbers::Disabled,
21585 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21586 (Some(true), _) => RelativeLineNumbers::Enabled,
21587 }
21588 }
21589
21590 pub fn toggle_relative_line_numbers(
21591 &mut self,
21592 _: &ToggleRelativeLineNumbers,
21593 _: &mut Window,
21594 cx: &mut Context<Self>,
21595 ) {
21596 let is_relative = self.relative_line_numbers(cx);
21597 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21598 }
21599
21600 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21601 self.use_relative_line_numbers = is_relative;
21602 cx.notify();
21603 }
21604
21605 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21606 self.show_gutter = show_gutter;
21607 cx.notify();
21608 }
21609
21610 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21611 self.show_scrollbars = ScrollbarAxes {
21612 horizontal: show,
21613 vertical: show,
21614 };
21615 cx.notify();
21616 }
21617
21618 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21619 self.show_scrollbars.vertical = show;
21620 cx.notify();
21621 }
21622
21623 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21624 self.show_scrollbars.horizontal = show;
21625 cx.notify();
21626 }
21627
21628 pub fn set_minimap_visibility(
21629 &mut self,
21630 minimap_visibility: MinimapVisibility,
21631 window: &mut Window,
21632 cx: &mut Context<Self>,
21633 ) {
21634 if self.minimap_visibility != minimap_visibility {
21635 if minimap_visibility.visible() && self.minimap.is_none() {
21636 let minimap_settings = EditorSettings::get_global(cx).minimap;
21637 self.minimap =
21638 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21639 }
21640 self.minimap_visibility = minimap_visibility;
21641 cx.notify();
21642 }
21643 }
21644
21645 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21646 self.set_show_scrollbars(false, cx);
21647 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21648 }
21649
21650 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21651 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21652 }
21653
21654 /// Normally the text in full mode and auto height editors is padded on the
21655 /// left side by roughly half a character width for improved hit testing.
21656 ///
21657 /// Use this method to disable this for cases where this is not wanted (e.g.
21658 /// if you want to align the editor text with some other text above or below)
21659 /// or if you want to add this padding to single-line editors.
21660 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21661 self.offset_content = offset_content;
21662 cx.notify();
21663 }
21664
21665 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21666 self.show_line_numbers = Some(show_line_numbers);
21667 cx.notify();
21668 }
21669
21670 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21671 self.disable_expand_excerpt_buttons = true;
21672 cx.notify();
21673 }
21674
21675 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21676 self.number_deleted_lines = number;
21677 cx.notify();
21678 }
21679
21680 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21681 self.delegate_expand_excerpts = delegate;
21682 }
21683
21684 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21685 self.delegate_stage_and_restore = delegate;
21686 }
21687
21688 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21689 self.delegate_open_excerpts = delegate;
21690 }
21691
21692 pub fn set_on_local_selections_changed(
21693 &mut self,
21694 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21695 ) {
21696 self.on_local_selections_changed = callback;
21697 }
21698
21699 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21700 self.suppress_selection_callback = suppress;
21701 }
21702
21703 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21704 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21705 cx.notify();
21706 }
21707
21708 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21709 self.show_code_actions = Some(show_code_actions);
21710 cx.notify();
21711 }
21712
21713 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21714 self.show_runnables = Some(show_runnables);
21715 cx.notify();
21716 }
21717
21718 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21719 self.show_breakpoints = Some(show_breakpoints);
21720 cx.notify();
21721 }
21722
21723 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21724 self.show_diff_review_button = show;
21725 cx.notify();
21726 }
21727
21728 pub fn show_diff_review_button(&self) -> bool {
21729 self.show_diff_review_button
21730 }
21731
21732 pub fn render_diff_review_button(
21733 &self,
21734 display_row: DisplayRow,
21735 width: Pixels,
21736 cx: &mut Context<Self>,
21737 ) -> impl IntoElement {
21738 let text_color = cx.theme().colors().text;
21739 let icon_color = cx.theme().colors().icon_accent;
21740
21741 h_flex()
21742 .id("diff_review_button")
21743 .cursor_pointer()
21744 .w(width - px(1.))
21745 .h(relative(0.9))
21746 .justify_center()
21747 .rounded_sm()
21748 .border_1()
21749 .border_color(text_color.opacity(0.1))
21750 .bg(text_color.opacity(0.15))
21751 .hover(|s| {
21752 s.bg(icon_color.opacity(0.4))
21753 .border_color(icon_color.opacity(0.5))
21754 })
21755 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21756 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21757 .on_mouse_down(
21758 gpui::MouseButton::Left,
21759 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21760 editor.start_diff_review_drag(display_row, window, cx);
21761 }),
21762 )
21763 }
21764
21765 pub fn start_diff_review_drag(
21766 &mut self,
21767 display_row: DisplayRow,
21768 window: &mut Window,
21769 cx: &mut Context<Self>,
21770 ) {
21771 let snapshot = self.snapshot(window, cx);
21772 let point = snapshot
21773 .display_snapshot
21774 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21775 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21776 self.diff_review_drag_state = Some(DiffReviewDragState {
21777 start_anchor: anchor,
21778 current_anchor: anchor,
21779 });
21780 cx.notify();
21781 }
21782
21783 pub fn update_diff_review_drag(
21784 &mut self,
21785 display_row: DisplayRow,
21786 window: &mut Window,
21787 cx: &mut Context<Self>,
21788 ) {
21789 if self.diff_review_drag_state.is_none() {
21790 return;
21791 }
21792 let snapshot = self.snapshot(window, cx);
21793 let point = snapshot
21794 .display_snapshot
21795 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21796 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21797 if let Some(drag_state) = &mut self.diff_review_drag_state {
21798 drag_state.current_anchor = anchor;
21799 cx.notify();
21800 }
21801 }
21802
21803 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21804 if let Some(drag_state) = self.diff_review_drag_state.take() {
21805 let snapshot = self.snapshot(window, cx);
21806 let range = drag_state.row_range(&snapshot.display_snapshot);
21807 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21808 }
21809 cx.notify();
21810 }
21811
21812 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21813 self.diff_review_drag_state = None;
21814 cx.notify();
21815 }
21816
21817 /// Calculates the appropriate block height for the diff review overlay.
21818 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21819 /// and 2 lines per comment when expanded.
21820 fn calculate_overlay_height(
21821 &self,
21822 hunk_key: &DiffHunkKey,
21823 comments_expanded: bool,
21824 snapshot: &MultiBufferSnapshot,
21825 ) -> u32 {
21826 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21827 let base_height: u32 = 2; // Input row with avatar and buttons
21828
21829 if comment_count == 0 {
21830 base_height
21831 } else if comments_expanded {
21832 // Header (1 line) + 2 lines per comment
21833 base_height + 1 + (comment_count as u32 * 2)
21834 } else {
21835 // Just header when collapsed
21836 base_height + 1
21837 }
21838 }
21839
21840 pub fn show_diff_review_overlay(
21841 &mut self,
21842 display_range: Range<DisplayRow>,
21843 window: &mut Window,
21844 cx: &mut Context<Self>,
21845 ) {
21846 let Range { start, end } = display_range.sorted();
21847
21848 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21849 let editor_snapshot = self.snapshot(window, cx);
21850
21851 // Convert display rows to multibuffer points
21852 let start_point = editor_snapshot
21853 .display_snapshot
21854 .display_point_to_point(start.as_display_point(), Bias::Left);
21855 let end_point = editor_snapshot
21856 .display_snapshot
21857 .display_point_to_point(end.as_display_point(), Bias::Left);
21858 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21859
21860 // Create anchor range for the selected lines (start of first line to end of last line)
21861 let line_end = Point::new(
21862 end_point.row,
21863 buffer_snapshot.line_len(end_multi_buffer_row),
21864 );
21865 let anchor_range =
21866 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21867
21868 // Compute the hunk key for this display row
21869 let file_path = buffer_snapshot
21870 .file_at(start_point)
21871 .map(|file: &Arc<dyn language::File>| file.path().clone())
21872 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21873 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21874 let new_hunk_key = DiffHunkKey {
21875 file_path,
21876 hunk_start_anchor,
21877 };
21878
21879 // Check if we already have an overlay for this hunk
21880 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21881 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21882 }) {
21883 // Just focus the existing overlay's prompt editor
21884 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21885 window.focus(&focus_handle, cx);
21886 return;
21887 }
21888
21889 // Dismiss overlays that have no comments for their hunks
21890 self.dismiss_overlays_without_comments(cx);
21891
21892 // Get the current user's avatar URI from the project's user_store
21893 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21894 let user_store = project.read(cx).user_store();
21895 user_store
21896 .read(cx)
21897 .current_user()
21898 .map(|user| user.avatar_uri.clone())
21899 });
21900
21901 // Create anchor at the end of the last row so the block appears immediately below it
21902 // Use multibuffer coordinates for anchor creation
21903 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21904 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21905
21906 // Use the hunk key we already computed
21907 let hunk_key = new_hunk_key;
21908
21909 // Create the prompt editor for the review input
21910 let prompt_editor = cx.new(|cx| {
21911 let mut editor = Editor::single_line(window, cx);
21912 editor.set_placeholder_text("Add a review comment...", window, cx);
21913 editor
21914 });
21915
21916 // Register the Newline action on the prompt editor to submit the review
21917 let parent_editor = cx.entity().downgrade();
21918 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21919 prompt_editor.register_action({
21920 let parent_editor = parent_editor.clone();
21921 move |_: &crate::actions::Newline, window, cx| {
21922 if let Some(editor) = parent_editor.upgrade() {
21923 editor.update(cx, |editor, cx| {
21924 editor.submit_diff_review_comment(window, cx);
21925 });
21926 }
21927 }
21928 })
21929 });
21930
21931 // Calculate initial height based on existing comments for this hunk
21932 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21933
21934 // Create the overlay block
21935 let prompt_editor_for_render = prompt_editor.clone();
21936 let hunk_key_for_render = hunk_key.clone();
21937 let editor_handle = cx.entity().downgrade();
21938 let block = BlockProperties {
21939 style: BlockStyle::Sticky,
21940 placement: BlockPlacement::Below(anchor),
21941 height: Some(initial_height),
21942 render: Arc::new(move |cx| {
21943 Self::render_diff_review_overlay(
21944 &prompt_editor_for_render,
21945 &hunk_key_for_render,
21946 &editor_handle,
21947 cx,
21948 )
21949 }),
21950 priority: 0,
21951 };
21952
21953 let block_ids = self.insert_blocks([block], None, cx);
21954 let Some(block_id) = block_ids.into_iter().next() else {
21955 log::error!("Failed to insert diff review overlay block");
21956 return;
21957 };
21958
21959 self.diff_review_overlays.push(DiffReviewOverlay {
21960 anchor_range,
21961 block_id,
21962 prompt_editor: prompt_editor.clone(),
21963 hunk_key,
21964 comments_expanded: true,
21965 inline_edit_editors: HashMap::default(),
21966 inline_edit_subscriptions: HashMap::default(),
21967 user_avatar_uri,
21968 _subscription: subscription,
21969 });
21970
21971 // Focus the prompt editor
21972 let focus_handle = prompt_editor.focus_handle(cx);
21973 window.focus(&focus_handle, cx);
21974
21975 cx.notify();
21976 }
21977
21978 /// Dismisses all diff review overlays.
21979 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21980 if self.diff_review_overlays.is_empty() {
21981 return;
21982 }
21983 let block_ids: HashSet<_> = self
21984 .diff_review_overlays
21985 .drain(..)
21986 .map(|overlay| overlay.block_id)
21987 .collect();
21988 self.remove_blocks(block_ids, None, cx);
21989 cx.notify();
21990 }
21991
21992 /// Dismisses overlays that have no comments stored for their hunks.
21993 /// Keeps overlays that have at least one comment.
21994 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21995 let snapshot = self.buffer.read(cx).snapshot(cx);
21996
21997 // First, compute which overlays have comments (to avoid borrow issues with retain)
21998 let overlays_with_comments: Vec<bool> = self
21999 .diff_review_overlays
22000 .iter()
22001 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
22002 .collect();
22003
22004 // Now collect block IDs to remove and retain overlays
22005 let mut block_ids_to_remove = HashSet::default();
22006 let mut index = 0;
22007 self.diff_review_overlays.retain(|overlay| {
22008 let has_comments = overlays_with_comments[index];
22009 index += 1;
22010 if !has_comments {
22011 block_ids_to_remove.insert(overlay.block_id);
22012 }
22013 has_comments
22014 });
22015
22016 if !block_ids_to_remove.is_empty() {
22017 self.remove_blocks(block_ids_to_remove, None, cx);
22018 cx.notify();
22019 }
22020 }
22021
22022 /// Refreshes the diff review overlay block to update its height and render function.
22023 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
22024 fn refresh_diff_review_overlay_height(
22025 &mut self,
22026 hunk_key: &DiffHunkKey,
22027 _window: &mut Window,
22028 cx: &mut Context<Self>,
22029 ) {
22030 // Extract all needed data from overlay first to avoid borrow conflicts
22031 let snapshot = self.buffer.read(cx).snapshot(cx);
22032 let (comments_expanded, block_id, prompt_editor) = {
22033 let Some(overlay) = self
22034 .diff_review_overlays
22035 .iter()
22036 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22037 else {
22038 return;
22039 };
22040
22041 (
22042 overlay.comments_expanded,
22043 overlay.block_id,
22044 overlay.prompt_editor.clone(),
22045 )
22046 };
22047
22048 // Calculate new height
22049 let snapshot = self.buffer.read(cx).snapshot(cx);
22050 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
22051
22052 // Update the block height using resize_blocks (avoids flicker)
22053 let mut heights = HashMap::default();
22054 heights.insert(block_id, new_height);
22055 self.resize_blocks(heights, None, cx);
22056
22057 // Update the render function using replace_blocks (avoids flicker)
22058 let hunk_key_for_render = hunk_key.clone();
22059 let editor_handle = cx.entity().downgrade();
22060 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
22061 Arc::new(move |cx| {
22062 Self::render_diff_review_overlay(
22063 &prompt_editor,
22064 &hunk_key_for_render,
22065 &editor_handle,
22066 cx,
22067 )
22068 });
22069
22070 let mut renderers = HashMap::default();
22071 renderers.insert(block_id, render);
22072 self.replace_blocks(renderers, None, cx);
22073 }
22074
22075 /// Action handler for SubmitDiffReviewComment.
22076 pub fn submit_diff_review_comment_action(
22077 &mut self,
22078 _: &SubmitDiffReviewComment,
22079 window: &mut Window,
22080 cx: &mut Context<Self>,
22081 ) {
22082 self.submit_diff_review_comment(window, cx);
22083 }
22084
22085 /// Stores the diff review comment locally.
22086 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
22087 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22088 // Find the overlay that currently has focus
22089 let overlay_index = self
22090 .diff_review_overlays
22091 .iter()
22092 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
22093 let Some(overlay_index) = overlay_index else {
22094 return;
22095 };
22096 let overlay = &self.diff_review_overlays[overlay_index];
22097
22098 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
22099 if comment_text.is_empty() {
22100 return;
22101 }
22102
22103 let anchor_range = overlay.anchor_range.clone();
22104 let hunk_key = overlay.hunk_key.clone();
22105
22106 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
22107
22108 // Clear the prompt editor but keep the overlay open
22109 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
22110 overlay.prompt_editor.update(cx, |editor, cx| {
22111 editor.clear(window, cx);
22112 });
22113 }
22114
22115 // Refresh the overlay to update the block height for the new comment
22116 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22117
22118 cx.notify();
22119 }
22120
22121 /// Returns the prompt editor for the diff review overlay, if one is active.
22122 /// This is primarily used for testing.
22123 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
22124 self.diff_review_overlays
22125 .first()
22126 .map(|overlay| &overlay.prompt_editor)
22127 }
22128
22129 /// Returns the line range for the first diff review overlay, if one is active.
22130 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
22131 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
22132 let overlay = self.diff_review_overlays.first()?;
22133 let snapshot = self.buffer.read(cx).snapshot(cx);
22134 let start_point = overlay.anchor_range.start.to_point(&snapshot);
22135 let end_point = overlay.anchor_range.end.to_point(&snapshot);
22136 let start_row = snapshot
22137 .point_to_buffer_point(start_point)
22138 .map(|(_, p)| p.row)
22139 .unwrap_or(start_point.row);
22140 let end_row = snapshot
22141 .point_to_buffer_point(end_point)
22142 .map(|(_, p)| p.row)
22143 .unwrap_or(end_point.row);
22144 Some((start_row, end_row))
22145 }
22146
22147 /// Sets whether the comments section is expanded in the diff review overlay.
22148 /// This is primarily used for testing.
22149 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
22150 for overlay in &mut self.diff_review_overlays {
22151 overlay.comments_expanded = expanded;
22152 }
22153 cx.notify();
22154 }
22155
22156 /// Compares two DiffHunkKeys for equality by resolving their anchors.
22157 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
22158 a.file_path == b.file_path
22159 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
22160 }
22161
22162 /// Returns comments for a specific hunk, ordered by creation time.
22163 pub fn comments_for_hunk<'a>(
22164 &'a self,
22165 key: &DiffHunkKey,
22166 snapshot: &MultiBufferSnapshot,
22167 ) -> &'a [StoredReviewComment] {
22168 let key_point = key.hunk_start_anchor.to_point(snapshot);
22169 self.stored_review_comments
22170 .iter()
22171 .find(|(k, _)| {
22172 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22173 })
22174 .map(|(_, comments)| comments.as_slice())
22175 .unwrap_or(&[])
22176 }
22177
22178 /// Returns the total count of stored review comments across all hunks.
22179 pub fn total_review_comment_count(&self) -> usize {
22180 self.stored_review_comments
22181 .iter()
22182 .map(|(_, v)| v.len())
22183 .sum()
22184 }
22185
22186 /// Returns the count of comments for a specific hunk.
22187 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
22188 let key_point = key.hunk_start_anchor.to_point(snapshot);
22189 self.stored_review_comments
22190 .iter()
22191 .find(|(k, _)| {
22192 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22193 })
22194 .map(|(_, v)| v.len())
22195 .unwrap_or(0)
22196 }
22197
22198 /// Adds a new review comment to a specific hunk.
22199 pub fn add_review_comment(
22200 &mut self,
22201 hunk_key: DiffHunkKey,
22202 comment: String,
22203 anchor_range: Range<Anchor>,
22204 cx: &mut Context<Self>,
22205 ) -> usize {
22206 let id = self.next_review_comment_id;
22207 self.next_review_comment_id += 1;
22208
22209 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22210
22211 let snapshot = self.buffer.read(cx).snapshot(cx);
22212 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22213
22214 // Find existing entry for this hunk or add a new one
22215 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22216 k.file_path == hunk_key.file_path
22217 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22218 }) {
22219 comments.push(stored_comment);
22220 } else {
22221 self.stored_review_comments
22222 .push((hunk_key, vec![stored_comment]));
22223 }
22224
22225 cx.emit(EditorEvent::ReviewCommentsChanged {
22226 total_count: self.total_review_comment_count(),
22227 });
22228 cx.notify();
22229 id
22230 }
22231
22232 /// Removes a review comment by ID from any hunk.
22233 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22234 for (_, comments) in self.stored_review_comments.iter_mut() {
22235 if let Some(index) = comments.iter().position(|c| c.id == id) {
22236 comments.remove(index);
22237 cx.emit(EditorEvent::ReviewCommentsChanged {
22238 total_count: self.total_review_comment_count(),
22239 });
22240 cx.notify();
22241 return true;
22242 }
22243 }
22244 false
22245 }
22246
22247 /// Updates a review comment's text by ID.
22248 pub fn update_review_comment(
22249 &mut self,
22250 id: usize,
22251 new_comment: String,
22252 cx: &mut Context<Self>,
22253 ) -> bool {
22254 for (_, comments) in self.stored_review_comments.iter_mut() {
22255 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22256 comment.comment = new_comment;
22257 comment.is_editing = false;
22258 cx.emit(EditorEvent::ReviewCommentsChanged {
22259 total_count: self.total_review_comment_count(),
22260 });
22261 cx.notify();
22262 return true;
22263 }
22264 }
22265 false
22266 }
22267
22268 /// Sets a comment's editing state.
22269 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22270 for (_, comments) in self.stored_review_comments.iter_mut() {
22271 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22272 comment.is_editing = is_editing;
22273 cx.notify();
22274 return;
22275 }
22276 }
22277 }
22278
22279 /// Takes all stored comments from all hunks, clearing the storage.
22280 /// Returns a Vec of (hunk_key, comments) pairs.
22281 pub fn take_all_review_comments(
22282 &mut self,
22283 cx: &mut Context<Self>,
22284 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22285 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22286 self.dismiss_all_diff_review_overlays(cx);
22287 let comments = std::mem::take(&mut self.stored_review_comments);
22288 // Reset the ID counter since all comments have been taken
22289 self.next_review_comment_id = 0;
22290 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22291 cx.notify();
22292 comments
22293 }
22294
22295 /// Removes review comments whose anchors are no longer valid or whose
22296 /// associated diff hunks no longer exist.
22297 ///
22298 /// This should be called when the buffer changes to prevent orphaned comments
22299 /// from accumulating.
22300 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22301 let snapshot = self.buffer.read(cx).snapshot(cx);
22302 let original_count = self.total_review_comment_count();
22303
22304 // Remove comments with invalid hunk anchors
22305 self.stored_review_comments
22306 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22307
22308 // Also clean up individual comments with invalid anchor ranges
22309 for (_, comments) in &mut self.stored_review_comments {
22310 comments.retain(|comment| {
22311 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22312 });
22313 }
22314
22315 // Remove empty hunk entries
22316 self.stored_review_comments
22317 .retain(|(_, comments)| !comments.is_empty());
22318
22319 let new_count = self.total_review_comment_count();
22320 if new_count != original_count {
22321 cx.emit(EditorEvent::ReviewCommentsChanged {
22322 total_count: new_count,
22323 });
22324 cx.notify();
22325 }
22326 }
22327
22328 /// Toggles the expanded state of the comments section in the overlay.
22329 pub fn toggle_review_comments_expanded(
22330 &mut self,
22331 _: &ToggleReviewCommentsExpanded,
22332 window: &mut Window,
22333 cx: &mut Context<Self>,
22334 ) {
22335 // Find the overlay that currently has focus, or use the first one
22336 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22337 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22338 overlay.comments_expanded = !overlay.comments_expanded;
22339 Some(overlay.hunk_key.clone())
22340 } else {
22341 None
22342 }
22343 });
22344
22345 // If no focused overlay found, toggle the first one
22346 let hunk_key = overlay_info.or_else(|| {
22347 self.diff_review_overlays.first_mut().map(|overlay| {
22348 overlay.comments_expanded = !overlay.comments_expanded;
22349 overlay.hunk_key.clone()
22350 })
22351 });
22352
22353 if let Some(hunk_key) = hunk_key {
22354 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22355 cx.notify();
22356 }
22357 }
22358
22359 /// Handles the EditReviewComment action - sets a comment into editing mode.
22360 pub fn edit_review_comment(
22361 &mut self,
22362 action: &EditReviewComment,
22363 window: &mut Window,
22364 cx: &mut Context<Self>,
22365 ) {
22366 let comment_id = action.id;
22367
22368 // Set the comment to editing mode
22369 self.set_comment_editing(comment_id, true, cx);
22370
22371 // Find the overlay that contains this comment and create an inline editor if needed
22372 // First, find which hunk this comment belongs to
22373 let hunk_key = self
22374 .stored_review_comments
22375 .iter()
22376 .find_map(|(key, comments)| {
22377 if comments.iter().any(|c| c.id == comment_id) {
22378 Some(key.clone())
22379 } else {
22380 None
22381 }
22382 });
22383
22384 let snapshot = self.buffer.read(cx).snapshot(cx);
22385 if let Some(hunk_key) = hunk_key {
22386 if let Some(overlay) = self
22387 .diff_review_overlays
22388 .iter_mut()
22389 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22390 {
22391 if let std::collections::hash_map::Entry::Vacant(entry) =
22392 overlay.inline_edit_editors.entry(comment_id)
22393 {
22394 // Find the comment text
22395 let comment_text = self
22396 .stored_review_comments
22397 .iter()
22398 .flat_map(|(_, comments)| comments)
22399 .find(|c| c.id == comment_id)
22400 .map(|c| c.comment.clone())
22401 .unwrap_or_default();
22402
22403 // Create inline editor
22404 let parent_editor = cx.entity().downgrade();
22405 let inline_editor = cx.new(|cx| {
22406 let mut editor = Editor::single_line(window, cx);
22407 editor.set_text(&*comment_text, window, cx);
22408 // Select all text for easy replacement
22409 editor.select_all(&crate::actions::SelectAll, window, cx);
22410 editor
22411 });
22412
22413 // Register the Newline action to confirm the edit
22414 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22415 inline_editor.register_action({
22416 let parent_editor = parent_editor.clone();
22417 move |_: &crate::actions::Newline, window, cx| {
22418 if let Some(editor) = parent_editor.upgrade() {
22419 editor.update(cx, |editor, cx| {
22420 editor.confirm_edit_review_comment(comment_id, window, cx);
22421 });
22422 }
22423 }
22424 })
22425 });
22426
22427 // Store the subscription to keep the action handler alive
22428 overlay
22429 .inline_edit_subscriptions
22430 .insert(comment_id, subscription);
22431
22432 // Focus the inline editor
22433 let focus_handle = inline_editor.focus_handle(cx);
22434 window.focus(&focus_handle, cx);
22435
22436 entry.insert(inline_editor);
22437 }
22438 }
22439 }
22440
22441 cx.notify();
22442 }
22443
22444 /// Confirms an inline edit of a review comment.
22445 pub fn confirm_edit_review_comment(
22446 &mut self,
22447 comment_id: usize,
22448 _window: &mut Window,
22449 cx: &mut Context<Self>,
22450 ) {
22451 // Get the new text from the inline editor
22452 // Find the overlay containing this comment's inline editor
22453 let snapshot = self.buffer.read(cx).snapshot(cx);
22454 let hunk_key = self
22455 .stored_review_comments
22456 .iter()
22457 .find_map(|(key, comments)| {
22458 if comments.iter().any(|c| c.id == comment_id) {
22459 Some(key.clone())
22460 } else {
22461 None
22462 }
22463 });
22464
22465 let new_text = hunk_key
22466 .as_ref()
22467 .and_then(|hunk_key| {
22468 self.diff_review_overlays
22469 .iter()
22470 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22471 })
22472 .as_ref()
22473 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22474 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22475
22476 if let Some(new_text) = new_text {
22477 if !new_text.is_empty() {
22478 self.update_review_comment(comment_id, new_text, cx);
22479 }
22480 }
22481
22482 // Remove the inline editor and its subscription
22483 if let Some(hunk_key) = hunk_key {
22484 if let Some(overlay) = self
22485 .diff_review_overlays
22486 .iter_mut()
22487 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22488 {
22489 overlay.inline_edit_editors.remove(&comment_id);
22490 overlay.inline_edit_subscriptions.remove(&comment_id);
22491 }
22492 }
22493
22494 // Clear editing state
22495 self.set_comment_editing(comment_id, false, cx);
22496 }
22497
22498 /// Cancels an inline edit of a review comment.
22499 pub fn cancel_edit_review_comment(
22500 &mut self,
22501 comment_id: usize,
22502 _window: &mut Window,
22503 cx: &mut Context<Self>,
22504 ) {
22505 // Find which hunk this comment belongs to
22506 let hunk_key = self
22507 .stored_review_comments
22508 .iter()
22509 .find_map(|(key, comments)| {
22510 if comments.iter().any(|c| c.id == comment_id) {
22511 Some(key.clone())
22512 } else {
22513 None
22514 }
22515 });
22516
22517 // Remove the inline editor and its subscription
22518 if let Some(hunk_key) = hunk_key {
22519 let snapshot = self.buffer.read(cx).snapshot(cx);
22520 if let Some(overlay) = self
22521 .diff_review_overlays
22522 .iter_mut()
22523 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22524 {
22525 overlay.inline_edit_editors.remove(&comment_id);
22526 overlay.inline_edit_subscriptions.remove(&comment_id);
22527 }
22528 }
22529
22530 // Clear editing state
22531 self.set_comment_editing(comment_id, false, cx);
22532 }
22533
22534 /// Action handler for ConfirmEditReviewComment.
22535 pub fn confirm_edit_review_comment_action(
22536 &mut self,
22537 action: &ConfirmEditReviewComment,
22538 window: &mut Window,
22539 cx: &mut Context<Self>,
22540 ) {
22541 self.confirm_edit_review_comment(action.id, window, cx);
22542 }
22543
22544 /// Action handler for CancelEditReviewComment.
22545 pub fn cancel_edit_review_comment_action(
22546 &mut self,
22547 action: &CancelEditReviewComment,
22548 window: &mut Window,
22549 cx: &mut Context<Self>,
22550 ) {
22551 self.cancel_edit_review_comment(action.id, window, cx);
22552 }
22553
22554 /// Handles the DeleteReviewComment action - removes a comment.
22555 pub fn delete_review_comment(
22556 &mut self,
22557 action: &DeleteReviewComment,
22558 window: &mut Window,
22559 cx: &mut Context<Self>,
22560 ) {
22561 // Get the hunk key before removing the comment
22562 // Find the hunk key from the comment itself
22563 let comment_id = action.id;
22564 let hunk_key = self
22565 .stored_review_comments
22566 .iter()
22567 .find_map(|(key, comments)| {
22568 if comments.iter().any(|c| c.id == comment_id) {
22569 Some(key.clone())
22570 } else {
22571 None
22572 }
22573 });
22574
22575 // Also get it from the overlay for refresh purposes
22576 let overlay_hunk_key = self
22577 .diff_review_overlays
22578 .first()
22579 .map(|o| o.hunk_key.clone());
22580
22581 self.remove_review_comment(action.id, cx);
22582
22583 // Refresh the overlay height after removing a comment
22584 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22585 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22586 }
22587 }
22588
22589 fn render_diff_review_overlay(
22590 prompt_editor: &Entity<Editor>,
22591 hunk_key: &DiffHunkKey,
22592 editor_handle: &WeakEntity<Editor>,
22593 cx: &mut BlockContext,
22594 ) -> AnyElement {
22595 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22596 if ranges.is_empty() {
22597 return None;
22598 }
22599 let formatted: Vec<String> = ranges
22600 .iter()
22601 .map(|(start, end)| {
22602 let start_line = start + 1;
22603 let end_line = end + 1;
22604 if start_line == end_line {
22605 format!("Line {start_line}")
22606 } else {
22607 format!("Lines {start_line}-{end_line}")
22608 }
22609 })
22610 .collect();
22611 // Don't show label for single line in single excerpt
22612 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22613 return None;
22614 }
22615 Some(formatted.join(" ⋯ "))
22616 }
22617
22618 let theme = cx.theme();
22619 let colors = theme.colors();
22620
22621 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22622 editor_handle
22623 .upgrade()
22624 .map(|editor| {
22625 let editor = editor.read(cx);
22626 let snapshot = editor.buffer().read(cx).snapshot(cx);
22627 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22628 let (expanded, editors, avatar_uri, line_ranges) = editor
22629 .diff_review_overlays
22630 .iter()
22631 .find(|overlay| {
22632 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22633 })
22634 .map(|o| {
22635 let start_point = o.anchor_range.start.to_point(&snapshot);
22636 let end_point = o.anchor_range.end.to_point(&snapshot);
22637 // Get line ranges per excerpt to detect discontinuities
22638 let buffer_ranges =
22639 snapshot.range_to_buffer_ranges(start_point..end_point);
22640 let ranges: Vec<(u32, u32)> = buffer_ranges
22641 .iter()
22642 .map(|(buffer_snapshot, range, _)| {
22643 let start = buffer_snapshot.offset_to_point(range.start.0).row;
22644 let end = buffer_snapshot.offset_to_point(range.end.0).row;
22645 (start, end)
22646 })
22647 .collect();
22648 (
22649 o.comments_expanded,
22650 o.inline_edit_editors.clone(),
22651 o.user_avatar_uri.clone(),
22652 if ranges.is_empty() {
22653 None
22654 } else {
22655 Some(ranges)
22656 },
22657 )
22658 })
22659 .unwrap_or((true, HashMap::default(), None, None));
22660 (comments, expanded, editors, avatar_uri, line_ranges)
22661 })
22662 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22663
22664 let comment_count = comments.len();
22665 let avatar_size = px(20.);
22666 let action_icon_size = IconSize::XSmall;
22667
22668 v_flex()
22669 .w_full()
22670 .bg(colors.editor_background)
22671 .border_b_1()
22672 .border_color(colors.border)
22673 .px_2()
22674 .pb_2()
22675 .gap_2()
22676 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22677 .when_some(line_ranges, |el, ranges| {
22678 let label = format_line_ranges(&ranges);
22679 if let Some(label) = label {
22680 el.child(
22681 h_flex()
22682 .w_full()
22683 .px_2()
22684 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22685 )
22686 } else {
22687 el
22688 }
22689 })
22690 // Top row: editable input with user's avatar
22691 .child(
22692 h_flex()
22693 .w_full()
22694 .items_center()
22695 .gap_2()
22696 .px_2()
22697 .py_1p5()
22698 .rounded_md()
22699 .bg(colors.surface_background)
22700 .child(
22701 div()
22702 .size(avatar_size)
22703 .flex_shrink_0()
22704 .rounded_full()
22705 .overflow_hidden()
22706 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22707 Avatar::new(avatar_uri.clone())
22708 .size(avatar_size)
22709 .into_any_element()
22710 } else {
22711 Icon::new(IconName::Person)
22712 .size(IconSize::Small)
22713 .color(ui::Color::Muted)
22714 .into_any_element()
22715 }),
22716 )
22717 .child(
22718 div()
22719 .flex_1()
22720 .border_1()
22721 .border_color(colors.border)
22722 .rounded_md()
22723 .bg(colors.editor_background)
22724 .px_2()
22725 .py_1()
22726 .child(prompt_editor.clone()),
22727 )
22728 .child(
22729 h_flex()
22730 .flex_shrink_0()
22731 .gap_1()
22732 .child(
22733 IconButton::new("diff-review-close", IconName::Close)
22734 .icon_color(ui::Color::Muted)
22735 .icon_size(action_icon_size)
22736 .tooltip(Tooltip::text("Close"))
22737 .on_click(|_, window, cx| {
22738 window
22739 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22740 }),
22741 )
22742 .child(
22743 IconButton::new("diff-review-add", IconName::Return)
22744 .icon_color(ui::Color::Muted)
22745 .icon_size(action_icon_size)
22746 .tooltip(Tooltip::text("Add comment"))
22747 .on_click(|_, window, cx| {
22748 window.dispatch_action(
22749 Box::new(crate::actions::SubmitDiffReviewComment),
22750 cx,
22751 );
22752 }),
22753 ),
22754 ),
22755 )
22756 // Expandable comments section (only shown when there are comments)
22757 .when(comment_count > 0, |el| {
22758 el.child(Self::render_comments_section(
22759 comments,
22760 comments_expanded,
22761 inline_editors,
22762 user_avatar_uri,
22763 avatar_size,
22764 action_icon_size,
22765 colors,
22766 ))
22767 })
22768 .into_any_element()
22769 }
22770
22771 fn render_comments_section(
22772 comments: Vec<StoredReviewComment>,
22773 expanded: bool,
22774 inline_editors: HashMap<usize, Entity<Editor>>,
22775 user_avatar_uri: Option<SharedUri>,
22776 avatar_size: Pixels,
22777 action_icon_size: IconSize,
22778 colors: &theme::ThemeColors,
22779 ) -> impl IntoElement {
22780 let comment_count = comments.len();
22781
22782 v_flex()
22783 .w_full()
22784 .gap_1()
22785 // Header with expand/collapse toggle
22786 .child(
22787 h_flex()
22788 .id("review-comments-header")
22789 .w_full()
22790 .items_center()
22791 .gap_1()
22792 .px_2()
22793 .py_1()
22794 .cursor_pointer()
22795 .rounded_md()
22796 .hover(|style| style.bg(colors.ghost_element_hover))
22797 .on_click(|_, window: &mut Window, cx| {
22798 window.dispatch_action(
22799 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22800 cx,
22801 );
22802 })
22803 .child(
22804 Icon::new(if expanded {
22805 IconName::ChevronDown
22806 } else {
22807 IconName::ChevronRight
22808 })
22809 .size(IconSize::Small)
22810 .color(ui::Color::Muted),
22811 )
22812 .child(
22813 Label::new(format!(
22814 "{} Comment{}",
22815 comment_count,
22816 if comment_count == 1 { "" } else { "s" }
22817 ))
22818 .size(LabelSize::Small)
22819 .color(Color::Muted),
22820 ),
22821 )
22822 // Comments list (when expanded)
22823 .when(expanded, |el| {
22824 el.children(comments.into_iter().map(|comment| {
22825 let inline_editor = inline_editors.get(&comment.id).cloned();
22826 Self::render_comment_row(
22827 comment,
22828 inline_editor,
22829 user_avatar_uri.clone(),
22830 avatar_size,
22831 action_icon_size,
22832 colors,
22833 )
22834 }))
22835 })
22836 }
22837
22838 fn render_comment_row(
22839 comment: StoredReviewComment,
22840 inline_editor: Option<Entity<Editor>>,
22841 user_avatar_uri: Option<SharedUri>,
22842 avatar_size: Pixels,
22843 action_icon_size: IconSize,
22844 colors: &theme::ThemeColors,
22845 ) -> impl IntoElement {
22846 let comment_id = comment.id;
22847 let is_editing = inline_editor.is_some();
22848
22849 h_flex()
22850 .w_full()
22851 .items_center()
22852 .gap_2()
22853 .px_2()
22854 .py_1p5()
22855 .rounded_md()
22856 .bg(colors.surface_background)
22857 .child(
22858 div()
22859 .size(avatar_size)
22860 .flex_shrink_0()
22861 .rounded_full()
22862 .overflow_hidden()
22863 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22864 Avatar::new(avatar_uri.clone())
22865 .size(avatar_size)
22866 .into_any_element()
22867 } else {
22868 Icon::new(IconName::Person)
22869 .size(IconSize::Small)
22870 .color(ui::Color::Muted)
22871 .into_any_element()
22872 }),
22873 )
22874 .child(if let Some(editor) = inline_editor {
22875 // Inline edit mode: show an editable text field
22876 div()
22877 .flex_1()
22878 .border_1()
22879 .border_color(colors.border)
22880 .rounded_md()
22881 .bg(colors.editor_background)
22882 .px_2()
22883 .py_1()
22884 .child(editor)
22885 .into_any_element()
22886 } else {
22887 // Display mode: show the comment text
22888 div()
22889 .flex_1()
22890 .text_sm()
22891 .text_color(colors.text)
22892 .child(comment.comment)
22893 .into_any_element()
22894 })
22895 .child(if is_editing {
22896 // Editing mode: show close and confirm buttons
22897 h_flex()
22898 .gap_1()
22899 .child(
22900 IconButton::new(
22901 format!("diff-review-cancel-edit-{comment_id}"),
22902 IconName::Close,
22903 )
22904 .icon_color(ui::Color::Muted)
22905 .icon_size(action_icon_size)
22906 .tooltip(Tooltip::text("Cancel"))
22907 .on_click(move |_, window, cx| {
22908 window.dispatch_action(
22909 Box::new(crate::actions::CancelEditReviewComment {
22910 id: comment_id,
22911 }),
22912 cx,
22913 );
22914 }),
22915 )
22916 .child(
22917 IconButton::new(
22918 format!("diff-review-confirm-edit-{comment_id}"),
22919 IconName::Return,
22920 )
22921 .icon_color(ui::Color::Muted)
22922 .icon_size(action_icon_size)
22923 .tooltip(Tooltip::text("Confirm"))
22924 .on_click(move |_, window, cx| {
22925 window.dispatch_action(
22926 Box::new(crate::actions::ConfirmEditReviewComment {
22927 id: comment_id,
22928 }),
22929 cx,
22930 );
22931 }),
22932 )
22933 .into_any_element()
22934 } else {
22935 // Display mode: no action buttons for now (edit/delete not yet implemented)
22936 gpui::Empty.into_any_element()
22937 })
22938 }
22939
22940 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22941 if self.display_map.read(cx).masked != masked {
22942 self.display_map.update(cx, |map, _| map.masked = masked);
22943 }
22944 cx.notify()
22945 }
22946
22947 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22948 self.show_wrap_guides = Some(show_wrap_guides);
22949 cx.notify();
22950 }
22951
22952 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22953 self.show_indent_guides = Some(show_indent_guides);
22954 cx.notify();
22955 }
22956
22957 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22958 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22959 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22960 && let Some(dir) = file.abs_path(cx).parent()
22961 {
22962 return Some(dir.to_owned());
22963 }
22964 }
22965
22966 None
22967 }
22968
22969 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22970 self.active_buffer(cx)?
22971 .read(cx)
22972 .file()
22973 .and_then(|f| f.as_local())
22974 }
22975
22976 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22977 self.active_buffer(cx).and_then(|buffer| {
22978 let buffer = buffer.read(cx);
22979 if let Some(project_path) = buffer.project_path(cx) {
22980 let project = self.project()?.read(cx);
22981 project.absolute_path(&project_path, cx)
22982 } else {
22983 buffer
22984 .file()
22985 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22986 }
22987 })
22988 }
22989
22990 pub fn reveal_in_finder(
22991 &mut self,
22992 _: &RevealInFileManager,
22993 _window: &mut Window,
22994 cx: &mut Context<Self>,
22995 ) {
22996 if let Some(path) = self.target_file_abs_path(cx) {
22997 if let Some(project) = self.project() {
22998 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22999 } else {
23000 cx.reveal_path(&path);
23001 }
23002 }
23003 }
23004
23005 pub fn copy_path(
23006 &mut self,
23007 _: &zed_actions::workspace::CopyPath,
23008 _window: &mut Window,
23009 cx: &mut Context<Self>,
23010 ) {
23011 if let Some(path) = self.target_file_abs_path(cx)
23012 && let Some(path) = path.to_str()
23013 {
23014 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
23015 } else {
23016 cx.propagate();
23017 }
23018 }
23019
23020 pub fn copy_relative_path(
23021 &mut self,
23022 _: &zed_actions::workspace::CopyRelativePath,
23023 _window: &mut Window,
23024 cx: &mut Context<Self>,
23025 ) {
23026 if let Some(path) = self.active_buffer(cx).and_then(|buffer| {
23027 let project = self.project()?.read(cx);
23028 let path = buffer.read(cx).file()?.path();
23029 let path = path.display(project.path_style(cx));
23030 Some(path)
23031 }) {
23032 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
23033 } else {
23034 cx.propagate();
23035 }
23036 }
23037
23038 /// Returns the project path for the editor's buffer, if any buffer is
23039 /// opened in the editor.
23040 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
23041 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
23042 buffer.read(cx).project_path(cx)
23043 } else {
23044 None
23045 }
23046 }
23047
23048 // Returns true if the editor handled a go-to-line request
23049 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
23050 maybe!({
23051 let breakpoint_store = self.breakpoint_store.as_ref()?;
23052
23053 let (active_stack_frame, debug_line_pane_id) = {
23054 let store = breakpoint_store.read(cx);
23055 let active_stack_frame = store.active_position().cloned();
23056 let debug_line_pane_id = store.active_debug_line_pane_id();
23057 (active_stack_frame, debug_line_pane_id)
23058 };
23059
23060 let Some(active_stack_frame) = active_stack_frame else {
23061 self.clear_row_highlights::<ActiveDebugLine>();
23062 return None;
23063 };
23064
23065 if let Some(debug_line_pane_id) = debug_line_pane_id {
23066 if let Some(workspace) = self
23067 .workspace
23068 .as_ref()
23069 .and_then(|(workspace, _)| workspace.upgrade())
23070 {
23071 let editor_pane_id = workspace
23072 .read(cx)
23073 .pane_for_item_id(cx.entity_id())
23074 .map(|pane| pane.entity_id());
23075
23076 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
23077 self.clear_row_highlights::<ActiveDebugLine>();
23078 return None;
23079 }
23080 }
23081 }
23082
23083 let position = active_stack_frame.position;
23084
23085 let snapshot = self.buffer.read(cx).snapshot(cx);
23086 let multibuffer_anchor = snapshot.anchor_in_excerpt(position)?;
23087
23088 self.clear_row_highlights::<ActiveDebugLine>();
23089
23090 self.go_to_line::<ActiveDebugLine>(
23091 multibuffer_anchor,
23092 Some(cx.theme().colors().editor_debugger_active_line_background),
23093 window,
23094 cx,
23095 );
23096
23097 cx.notify();
23098
23099 Some(())
23100 })
23101 .is_some()
23102 }
23103
23104 pub fn copy_file_name_without_extension(
23105 &mut self,
23106 _: &CopyFileNameWithoutExtension,
23107 _: &mut Window,
23108 cx: &mut Context<Self>,
23109 ) {
23110 if let Some(file_stem) = self.active_buffer(cx).and_then(|buffer| {
23111 let file = buffer.read(cx).file()?;
23112 file.path().file_stem()
23113 }) {
23114 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
23115 }
23116 }
23117
23118 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
23119 if let Some(file_name) = self.active_buffer(cx).and_then(|buffer| {
23120 let file = buffer.read(cx).file()?;
23121 Some(file.file_name(cx))
23122 }) {
23123 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
23124 }
23125 }
23126
23127 pub fn toggle_git_blame(
23128 &mut self,
23129 _: &::git::Blame,
23130 window: &mut Window,
23131 cx: &mut Context<Self>,
23132 ) {
23133 self.show_git_blame_gutter = !self.show_git_blame_gutter;
23134
23135 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
23136 self.start_git_blame(true, window, cx);
23137 }
23138
23139 cx.notify();
23140 }
23141
23142 pub fn toggle_git_blame_inline(
23143 &mut self,
23144 _: &ToggleGitBlameInline,
23145 window: &mut Window,
23146 cx: &mut Context<Self>,
23147 ) {
23148 self.toggle_git_blame_inline_internal(true, window, cx);
23149 cx.notify();
23150 }
23151
23152 pub fn open_git_blame_commit(
23153 &mut self,
23154 _: &OpenGitBlameCommit,
23155 window: &mut Window,
23156 cx: &mut Context<Self>,
23157 ) {
23158 self.open_git_blame_commit_internal(window, cx);
23159 }
23160
23161 fn open_git_blame_commit_internal(
23162 &mut self,
23163 window: &mut Window,
23164 cx: &mut Context<Self>,
23165 ) -> Option<()> {
23166 let blame = self.blame.as_ref()?;
23167 let snapshot = self.snapshot(window, cx);
23168 let cursor = self
23169 .selections
23170 .newest::<Point>(&snapshot.display_snapshot)
23171 .head();
23172 let (buffer, point) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
23173 let (_, blame_entry) = blame
23174 .update(cx, |blame, cx| {
23175 blame
23176 .blame_for_rows(
23177 &[RowInfo {
23178 buffer_id: Some(buffer.remote_id()),
23179 buffer_row: Some(point.row),
23180 ..Default::default()
23181 }],
23182 cx,
23183 )
23184 .next()
23185 })
23186 .flatten()?;
23187 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23188 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23189 let workspace = self.workspace()?.downgrade();
23190 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23191 None
23192 }
23193
23194 pub fn git_blame_inline_enabled(&self) -> bool {
23195 self.git_blame_inline_enabled
23196 }
23197
23198 pub fn toggle_selection_menu(
23199 &mut self,
23200 _: &ToggleSelectionMenu,
23201 _: &mut Window,
23202 cx: &mut Context<Self>,
23203 ) {
23204 self.show_selection_menu = self
23205 .show_selection_menu
23206 .map(|show_selections_menu| !show_selections_menu)
23207 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23208
23209 cx.notify();
23210 }
23211
23212 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23213 self.show_selection_menu
23214 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23215 }
23216
23217 fn start_git_blame(
23218 &mut self,
23219 user_triggered: bool,
23220 window: &mut Window,
23221 cx: &mut Context<Self>,
23222 ) {
23223 if let Some(project) = self.project() {
23224 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23225 && buffer.read(cx).file().is_none()
23226 {
23227 return;
23228 }
23229
23230 let focused = self.focus_handle(cx).contains_focused(window, cx);
23231
23232 let project = project.clone();
23233 let blame = cx
23234 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23235 self.blame_subscription =
23236 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23237 self.blame = Some(blame);
23238 }
23239 }
23240
23241 fn toggle_git_blame_inline_internal(
23242 &mut self,
23243 user_triggered: bool,
23244 window: &mut Window,
23245 cx: &mut Context<Self>,
23246 ) {
23247 if self.git_blame_inline_enabled {
23248 self.git_blame_inline_enabled = false;
23249 self.show_git_blame_inline = false;
23250 self.show_git_blame_inline_delay_task.take();
23251 } else {
23252 self.git_blame_inline_enabled = true;
23253 self.start_git_blame_inline(user_triggered, window, cx);
23254 }
23255
23256 cx.notify();
23257 }
23258
23259 fn start_git_blame_inline(
23260 &mut self,
23261 user_triggered: bool,
23262 window: &mut Window,
23263 cx: &mut Context<Self>,
23264 ) {
23265 self.start_git_blame(user_triggered, window, cx);
23266
23267 if ProjectSettings::get_global(cx)
23268 .git
23269 .inline_blame_delay()
23270 .is_some()
23271 {
23272 self.start_inline_blame_timer(window, cx);
23273 } else {
23274 self.show_git_blame_inline = true
23275 }
23276 }
23277
23278 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23279 self.blame.as_ref()
23280 }
23281
23282 pub fn show_git_blame_gutter(&self) -> bool {
23283 self.show_git_blame_gutter
23284 }
23285
23286 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23287 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23288 }
23289
23290 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23291 self.show_git_blame_inline
23292 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23293 && !self.newest_selection_head_on_empty_line(cx)
23294 && self.has_blame_entries(cx)
23295 }
23296
23297 fn has_blame_entries(&self, cx: &App) -> bool {
23298 self.blame()
23299 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23300 }
23301
23302 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23303 let cursor_anchor = self.selections.newest_anchor().head();
23304
23305 let snapshot = self.buffer.read(cx).snapshot(cx);
23306 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23307
23308 snapshot.line_len(buffer_row) == 0
23309 }
23310
23311 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23312 let buffer_and_selection = maybe!({
23313 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23314 let selection_range = selection.range();
23315
23316 let multi_buffer = self.buffer().read(cx);
23317 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23318 let buffer_ranges = multi_buffer_snapshot
23319 .range_to_buffer_ranges(selection_range.start..selection_range.end);
23320
23321 let (buffer_snapshot, range, _) = if selection.reversed {
23322 buffer_ranges.first()
23323 } else {
23324 buffer_ranges.last()
23325 }?;
23326
23327 let buffer_range = range.to_point(buffer_snapshot);
23328 let buffer = multi_buffer.buffer(buffer_snapshot.remote_id()).unwrap();
23329
23330 let Some(buffer_diff) = multi_buffer.diff_for(buffer_snapshot.remote_id()) else {
23331 return Some((buffer, buffer_range.start.row..buffer_range.end.row));
23332 };
23333
23334 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23335 let start = buffer_diff_snapshot
23336 .buffer_point_to_base_text_point(buffer_range.start, &buffer_snapshot);
23337 let end = buffer_diff_snapshot
23338 .buffer_point_to_base_text_point(buffer_range.end, &buffer_snapshot);
23339
23340 Some((buffer, start.row..end.row))
23341 });
23342
23343 let Some((buffer, selection)) = buffer_and_selection else {
23344 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23345 };
23346
23347 let Some(project) = self.project() else {
23348 return Task::ready(Err(anyhow!("editor does not have project")));
23349 };
23350
23351 project.update(cx, |project, cx| {
23352 project.get_permalink_to_line(&buffer, selection, cx)
23353 })
23354 }
23355
23356 pub fn copy_permalink_to_line(
23357 &mut self,
23358 _: &CopyPermalinkToLine,
23359 window: &mut Window,
23360 cx: &mut Context<Self>,
23361 ) {
23362 let permalink_task = self.get_permalink_to_line(cx);
23363 let workspace = self.workspace();
23364
23365 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23366 Ok(permalink) => {
23367 cx.update(|_, cx| {
23368 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23369 })
23370 .ok();
23371 }
23372 Err(err) => {
23373 let message = format!("Failed to copy permalink: {err}");
23374
23375 anyhow::Result::<()>::Err(err).log_err();
23376
23377 if let Some(workspace) = workspace {
23378 workspace
23379 .update_in(cx, |workspace, _, cx| {
23380 struct CopyPermalinkToLine;
23381
23382 workspace.show_toast(
23383 Toast::new(
23384 NotificationId::unique::<CopyPermalinkToLine>(),
23385 message,
23386 ),
23387 cx,
23388 )
23389 })
23390 .ok();
23391 }
23392 }
23393 })
23394 .detach();
23395 }
23396
23397 pub fn copy_file_location(
23398 &mut self,
23399 _: &CopyFileLocation,
23400 _: &mut Window,
23401 cx: &mut Context<Self>,
23402 ) {
23403 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23404
23405 let start_line = selection.start.row + 1;
23406 let end_line = selection.end.row + 1;
23407
23408 let end_line = if selection.end.column == 0 && end_line > start_line {
23409 end_line - 1
23410 } else {
23411 end_line
23412 };
23413
23414 if let Some(file_location) = self.active_buffer(cx).and_then(|buffer| {
23415 let project = self.project()?.read(cx);
23416 let file = buffer.read(cx).file()?;
23417 let path = file.path().display(project.path_style(cx));
23418
23419 let location = if start_line == end_line {
23420 format!("{path}:{start_line}")
23421 } else {
23422 format!("{path}:{start_line}-{end_line}")
23423 };
23424 Some(location)
23425 }) {
23426 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23427 }
23428 }
23429
23430 pub fn open_permalink_to_line(
23431 &mut self,
23432 _: &OpenPermalinkToLine,
23433 window: &mut Window,
23434 cx: &mut Context<Self>,
23435 ) {
23436 let permalink_task = self.get_permalink_to_line(cx);
23437 let workspace = self.workspace();
23438
23439 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23440 Ok(permalink) => {
23441 cx.update(|_, cx| {
23442 cx.open_url(permalink.as_ref());
23443 })
23444 .ok();
23445 }
23446 Err(err) => {
23447 let message = format!("Failed to open permalink: {err}");
23448
23449 anyhow::Result::<()>::Err(err).log_err();
23450
23451 if let Some(workspace) = workspace {
23452 workspace.update(cx, |workspace, cx| {
23453 struct OpenPermalinkToLine;
23454
23455 workspace.show_toast(
23456 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23457 cx,
23458 )
23459 });
23460 }
23461 }
23462 })
23463 .detach();
23464 }
23465
23466 pub fn insert_uuid_v4(
23467 &mut self,
23468 _: &InsertUuidV4,
23469 window: &mut Window,
23470 cx: &mut Context<Self>,
23471 ) {
23472 self.insert_uuid(UuidVersion::V4, window, cx);
23473 }
23474
23475 pub fn insert_uuid_v7(
23476 &mut self,
23477 _: &InsertUuidV7,
23478 window: &mut Window,
23479 cx: &mut Context<Self>,
23480 ) {
23481 self.insert_uuid(UuidVersion::V7, window, cx);
23482 }
23483
23484 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23485 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23486 self.transact(window, cx, |this, window, cx| {
23487 let edits = this
23488 .selections
23489 .all::<Point>(&this.display_snapshot(cx))
23490 .into_iter()
23491 .map(|selection| {
23492 let uuid = match version {
23493 UuidVersion::V4 => uuid::Uuid::new_v4(),
23494 UuidVersion::V7 => uuid::Uuid::now_v7(),
23495 };
23496
23497 (selection.range(), uuid.to_string())
23498 });
23499 this.edit(edits, cx);
23500 this.refresh_edit_prediction(true, false, window, cx);
23501 });
23502 }
23503
23504 pub fn open_selections_in_multibuffer(
23505 &mut self,
23506 _: &OpenSelectionsInMultibuffer,
23507 window: &mut Window,
23508 cx: &mut Context<Self>,
23509 ) {
23510 let multibuffer = self.buffer.read(cx);
23511
23512 let Some(buffer) = multibuffer.as_singleton() else {
23513 return;
23514 };
23515 let buffer_snapshot = buffer.read(cx).snapshot();
23516
23517 let Some(workspace) = self.workspace() else {
23518 return;
23519 };
23520
23521 let title = multibuffer.title(cx).to_string();
23522
23523 let locations = self
23524 .selections
23525 .all_anchors(&self.display_snapshot(cx))
23526 .iter()
23527 .map(|selection| {
23528 (
23529 buffer.clone(),
23530 (selection.start.text_anchor_in(&buffer_snapshot)
23531 ..selection.end.text_anchor_in(&buffer_snapshot))
23532 .to_point(buffer.read(cx)),
23533 )
23534 })
23535 .into_group_map();
23536
23537 cx.spawn_in(window, async move |_, cx| {
23538 workspace.update_in(cx, |workspace, window, cx| {
23539 Self::open_locations_in_multibuffer(
23540 workspace,
23541 locations,
23542 format!("Selections for '{title}'"),
23543 false,
23544 false,
23545 MultibufferSelectionMode::All,
23546 window,
23547 cx,
23548 );
23549 })
23550 })
23551 .detach();
23552 }
23553
23554 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23555 /// last highlight added will be used.
23556 ///
23557 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23558 pub fn highlight_rows<T: 'static>(
23559 &mut self,
23560 range: Range<Anchor>,
23561 color: Hsla,
23562 options: RowHighlightOptions,
23563 cx: &mut Context<Self>,
23564 ) {
23565 let snapshot = self.buffer().read(cx).snapshot(cx);
23566 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23567 let ix = row_highlights.binary_search_by(|highlight| {
23568 Ordering::Equal
23569 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23570 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23571 });
23572
23573 if let Err(mut ix) = ix {
23574 let index = post_inc(&mut self.highlight_order);
23575
23576 // If this range intersects with the preceding highlight, then merge it with
23577 // the preceding highlight. Otherwise insert a new highlight.
23578 let mut merged = false;
23579 if ix > 0 {
23580 let prev_highlight = &mut row_highlights[ix - 1];
23581 if prev_highlight
23582 .range
23583 .end
23584 .cmp(&range.start, &snapshot)
23585 .is_ge()
23586 {
23587 ix -= 1;
23588 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23589 prev_highlight.range.end = range.end;
23590 }
23591 merged = true;
23592 prev_highlight.index = index;
23593 prev_highlight.color = color;
23594 prev_highlight.options = options;
23595 }
23596 }
23597
23598 if !merged {
23599 row_highlights.insert(
23600 ix,
23601 RowHighlight {
23602 range,
23603 index,
23604 color,
23605 options,
23606 type_id: TypeId::of::<T>(),
23607 },
23608 );
23609 }
23610
23611 // If any of the following highlights intersect with this one, merge them.
23612 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23613 let highlight = &row_highlights[ix];
23614 if next_highlight
23615 .range
23616 .start
23617 .cmp(&highlight.range.end, &snapshot)
23618 .is_le()
23619 {
23620 if next_highlight
23621 .range
23622 .end
23623 .cmp(&highlight.range.end, &snapshot)
23624 .is_gt()
23625 {
23626 row_highlights[ix].range.end = next_highlight.range.end;
23627 }
23628 row_highlights.remove(ix + 1);
23629 } else {
23630 break;
23631 }
23632 }
23633 }
23634 }
23635
23636 /// Remove any highlighted row ranges of the given type that intersect the
23637 /// given ranges.
23638 pub fn remove_highlighted_rows<T: 'static>(
23639 &mut self,
23640 ranges_to_remove: Vec<Range<Anchor>>,
23641 cx: &mut Context<Self>,
23642 ) {
23643 let snapshot = self.buffer().read(cx).snapshot(cx);
23644 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23645 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23646 row_highlights.retain(|highlight| {
23647 while let Some(range_to_remove) = ranges_to_remove.peek() {
23648 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23649 Ordering::Less | Ordering::Equal => {
23650 ranges_to_remove.next();
23651 }
23652 Ordering::Greater => {
23653 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23654 Ordering::Less | Ordering::Equal => {
23655 return false;
23656 }
23657 Ordering::Greater => break,
23658 }
23659 }
23660 }
23661 }
23662
23663 true
23664 })
23665 }
23666
23667 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23668 pub fn clear_row_highlights<T: 'static>(&mut self) {
23669 self.highlighted_rows.remove(&TypeId::of::<T>());
23670 }
23671
23672 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23673 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23674 self.highlighted_rows
23675 .get(&TypeId::of::<T>())
23676 .map_or(&[] as &[_], |vec| vec.as_slice())
23677 .iter()
23678 .map(|highlight| (highlight.range.clone(), highlight.color))
23679 }
23680
23681 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23682 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23683 /// Allows to ignore certain kinds of highlights.
23684 pub fn highlighted_display_rows(
23685 &self,
23686 window: &mut Window,
23687 cx: &mut App,
23688 ) -> BTreeMap<DisplayRow, LineHighlight> {
23689 let snapshot = self.snapshot(window, cx);
23690 let mut used_highlight_orders = HashMap::default();
23691 self.highlighted_rows
23692 .iter()
23693 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23694 .fold(
23695 BTreeMap::<DisplayRow, LineHighlight>::new(),
23696 |mut unique_rows, highlight| {
23697 let start = highlight.range.start.to_display_point(&snapshot);
23698 let end = highlight.range.end.to_display_point(&snapshot);
23699 let start_row = start.row().0;
23700 let end_row = if !highlight.range.end.is_max() && end.column() == 0 {
23701 end.row().0.saturating_sub(1)
23702 } else {
23703 end.row().0
23704 };
23705 for row in start_row..=end_row {
23706 let used_index =
23707 used_highlight_orders.entry(row).or_insert(highlight.index);
23708 if highlight.index >= *used_index {
23709 *used_index = highlight.index;
23710 unique_rows.insert(
23711 DisplayRow(row),
23712 LineHighlight {
23713 include_gutter: highlight.options.include_gutter,
23714 border: None,
23715 background: highlight.color.into(),
23716 type_id: Some(highlight.type_id),
23717 },
23718 );
23719 }
23720 }
23721 unique_rows
23722 },
23723 )
23724 }
23725
23726 pub fn highlighted_display_row_for_autoscroll(
23727 &self,
23728 snapshot: &DisplaySnapshot,
23729 ) -> Option<DisplayRow> {
23730 self.highlighted_rows
23731 .values()
23732 .flat_map(|highlighted_rows| highlighted_rows.iter())
23733 .filter_map(|highlight| {
23734 if highlight.options.autoscroll {
23735 Some(highlight.range.start.to_display_point(snapshot).row())
23736 } else {
23737 None
23738 }
23739 })
23740 .min()
23741 }
23742
23743 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23744 self.highlight_background(
23745 HighlightKey::SearchWithinRange,
23746 ranges,
23747 |_, colors| colors.colors().editor_document_highlight_read_background,
23748 cx,
23749 )
23750 }
23751
23752 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23753 self.breadcrumb_header = Some(new_header);
23754 }
23755
23756 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23757 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23758 }
23759
23760 pub fn highlight_background(
23761 &mut self,
23762 key: HighlightKey,
23763 ranges: &[Range<Anchor>],
23764 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23765 cx: &mut Context<Self>,
23766 ) {
23767 self.background_highlights
23768 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23769 self.scrollbar_marker_state.dirty = true;
23770 cx.notify();
23771 }
23772
23773 pub fn clear_background_highlights(
23774 &mut self,
23775 key: HighlightKey,
23776 cx: &mut Context<Self>,
23777 ) -> Option<BackgroundHighlight> {
23778 let text_highlights = self.background_highlights.remove(&key)?;
23779 if !text_highlights.1.is_empty() {
23780 self.scrollbar_marker_state.dirty = true;
23781 cx.notify();
23782 }
23783 Some(text_highlights)
23784 }
23785
23786 pub fn highlight_gutter<T: 'static>(
23787 &mut self,
23788 ranges: impl Into<Vec<Range<Anchor>>>,
23789 color_fetcher: fn(&App) -> Hsla,
23790 cx: &mut Context<Self>,
23791 ) {
23792 self.gutter_highlights
23793 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23794 cx.notify();
23795 }
23796
23797 pub fn clear_gutter_highlights<T: 'static>(
23798 &mut self,
23799 cx: &mut Context<Self>,
23800 ) -> Option<GutterHighlight> {
23801 cx.notify();
23802 self.gutter_highlights.remove(&TypeId::of::<T>())
23803 }
23804
23805 pub fn insert_gutter_highlight<T: 'static>(
23806 &mut self,
23807 range: Range<Anchor>,
23808 color_fetcher: fn(&App) -> Hsla,
23809 cx: &mut Context<Self>,
23810 ) {
23811 let snapshot = self.buffer().read(cx).snapshot(cx);
23812 let mut highlights = self
23813 .gutter_highlights
23814 .remove(&TypeId::of::<T>())
23815 .map(|(_, highlights)| highlights)
23816 .unwrap_or_default();
23817 let ix = highlights.binary_search_by(|highlight| {
23818 Ordering::Equal
23819 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23820 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23821 });
23822 if let Err(ix) = ix {
23823 highlights.insert(ix, range);
23824 }
23825 self.gutter_highlights
23826 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23827 }
23828
23829 pub fn remove_gutter_highlights<T: 'static>(
23830 &mut self,
23831 ranges_to_remove: Vec<Range<Anchor>>,
23832 cx: &mut Context<Self>,
23833 ) {
23834 let snapshot = self.buffer().read(cx).snapshot(cx);
23835 let Some((color_fetcher, mut gutter_highlights)) =
23836 self.gutter_highlights.remove(&TypeId::of::<T>())
23837 else {
23838 return;
23839 };
23840 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23841 gutter_highlights.retain(|highlight| {
23842 while let Some(range_to_remove) = ranges_to_remove.peek() {
23843 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23844 Ordering::Less | Ordering::Equal => {
23845 ranges_to_remove.next();
23846 }
23847 Ordering::Greater => {
23848 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23849 Ordering::Less | Ordering::Equal => {
23850 return false;
23851 }
23852 Ordering::Greater => break,
23853 }
23854 }
23855 }
23856 }
23857
23858 true
23859 });
23860 self.gutter_highlights
23861 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23862 }
23863
23864 #[cfg(any(test, feature = "test-support"))]
23865 pub fn all_text_highlights(
23866 &self,
23867 window: &mut Window,
23868 cx: &mut Context<Self>,
23869 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23870 let snapshot = self.snapshot(window, cx);
23871 self.display_map.update(cx, |display_map, _| {
23872 display_map
23873 .all_text_highlights()
23874 .map(|(_, highlight)| {
23875 let (style, ranges) = highlight.as_ref();
23876 (
23877 *style,
23878 ranges
23879 .iter()
23880 .map(|range| range.clone().to_display_points(&snapshot))
23881 .collect(),
23882 )
23883 })
23884 .collect()
23885 })
23886 }
23887
23888 #[cfg(any(test, feature = "test-support"))]
23889 pub fn all_text_background_highlights(
23890 &self,
23891 window: &mut Window,
23892 cx: &mut Context<Self>,
23893 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23894 let snapshot = self.snapshot(window, cx);
23895 let buffer = &snapshot.buffer_snapshot();
23896 let start = buffer.anchor_before(MultiBufferOffset(0));
23897 let end = buffer.anchor_after(buffer.len());
23898 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23899 }
23900
23901 #[cfg(any(test, feature = "test-support"))]
23902 pub fn sorted_background_highlights_in_range(
23903 &self,
23904 search_range: Range<Anchor>,
23905 display_snapshot: &DisplaySnapshot,
23906 theme: &Theme,
23907 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23908 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23909 res.sort_by(|a, b| {
23910 a.0.start
23911 .cmp(&b.0.start)
23912 .then_with(|| a.0.end.cmp(&b.0.end))
23913 .then_with(|| a.1.cmp(&b.1))
23914 });
23915 res
23916 }
23917
23918 #[cfg(any(test, feature = "test-support"))]
23919 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23920 let snapshot = self.buffer().read(cx).snapshot(cx);
23921
23922 let highlights = self
23923 .background_highlights
23924 .get(&HighlightKey::BufferSearchHighlights);
23925
23926 if let Some((_color, ranges)) = highlights {
23927 ranges
23928 .iter()
23929 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23930 .collect_vec()
23931 } else {
23932 vec![]
23933 }
23934 }
23935
23936 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23937 self.background_highlights
23938 .get(&key)
23939 .is_some_and(|(_, highlights)| !highlights.is_empty())
23940 }
23941
23942 /// Returns all background highlights for a given range.
23943 ///
23944 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23945 pub fn background_highlights_in_range(
23946 &self,
23947 search_range: Range<Anchor>,
23948 display_snapshot: &DisplaySnapshot,
23949 theme: &Theme,
23950 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23951 let mut results = Vec::new();
23952 for (color_fetcher, ranges) in self.background_highlights.values() {
23953 let start_ix = match ranges.binary_search_by(|probe| {
23954 let cmp = probe
23955 .end
23956 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23957 if cmp.is_gt() {
23958 Ordering::Greater
23959 } else {
23960 Ordering::Less
23961 }
23962 }) {
23963 Ok(i) | Err(i) => i,
23964 };
23965 for (index, range) in ranges[start_ix..].iter().enumerate() {
23966 if range
23967 .start
23968 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23969 .is_ge()
23970 {
23971 break;
23972 }
23973
23974 let color = color_fetcher(&(start_ix + index), theme);
23975 let start = range.start.to_display_point(display_snapshot);
23976 let end = range.end.to_display_point(display_snapshot);
23977 results.push((start..end, color))
23978 }
23979 }
23980 results
23981 }
23982
23983 pub fn gutter_highlights_in_range(
23984 &self,
23985 search_range: Range<Anchor>,
23986 display_snapshot: &DisplaySnapshot,
23987 cx: &App,
23988 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23989 let mut results = Vec::new();
23990 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23991 let color = color_fetcher(cx);
23992 let start_ix = match ranges.binary_search_by(|probe| {
23993 let cmp = probe
23994 .end
23995 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23996 if cmp.is_gt() {
23997 Ordering::Greater
23998 } else {
23999 Ordering::Less
24000 }
24001 }) {
24002 Ok(i) | Err(i) => i,
24003 };
24004 for range in &ranges[start_ix..] {
24005 if range
24006 .start
24007 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
24008 .is_ge()
24009 {
24010 break;
24011 }
24012
24013 let start = range.start.to_display_point(display_snapshot);
24014 let end = range.end.to_display_point(display_snapshot);
24015 results.push((start..end, color))
24016 }
24017 }
24018 results
24019 }
24020
24021 /// Get the text ranges corresponding to the redaction query
24022 pub fn redacted_ranges(
24023 &self,
24024 search_range: Range<Anchor>,
24025 display_snapshot: &DisplaySnapshot,
24026 cx: &App,
24027 ) -> Vec<Range<DisplayPoint>> {
24028 display_snapshot
24029 .buffer_snapshot()
24030 .redacted_ranges(search_range, |file| {
24031 if let Some(file) = file {
24032 file.is_private()
24033 && EditorSettings::get(
24034 Some(SettingsLocation {
24035 worktree_id: file.worktree_id(cx),
24036 path: file.path().as_ref(),
24037 }),
24038 cx,
24039 )
24040 .redact_private_values
24041 } else {
24042 false
24043 }
24044 })
24045 .map(|range| {
24046 range.start.to_display_point(display_snapshot)
24047 ..range.end.to_display_point(display_snapshot)
24048 })
24049 .collect()
24050 }
24051
24052 pub fn highlight_text_key(
24053 &mut self,
24054 key: HighlightKey,
24055 ranges: Vec<Range<Anchor>>,
24056 style: HighlightStyle,
24057 merge: bool,
24058 cx: &mut Context<Self>,
24059 ) {
24060 self.display_map.update(cx, |map, cx| {
24061 map.highlight_text(key, ranges, style, merge, cx);
24062 });
24063 cx.notify();
24064 }
24065
24066 pub fn highlight_text(
24067 &mut self,
24068 key: HighlightKey,
24069 ranges: Vec<Range<Anchor>>,
24070 style: HighlightStyle,
24071 cx: &mut Context<Self>,
24072 ) {
24073 self.display_map.update(cx, |map, cx| {
24074 map.highlight_text(key, ranges, style, false, cx)
24075 });
24076 cx.notify();
24077 }
24078
24079 pub fn text_highlights<'a>(
24080 &'a self,
24081 key: HighlightKey,
24082 cx: &'a App,
24083 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
24084 self.display_map.read(cx).text_highlights(key)
24085 }
24086
24087 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
24088 let cleared = self
24089 .display_map
24090 .update(cx, |map, _| map.clear_highlights(key));
24091 if cleared {
24092 cx.notify();
24093 }
24094 }
24095
24096 pub fn clear_highlights_with(
24097 &mut self,
24098 f: &mut dyn FnMut(&HighlightKey) -> bool,
24099 cx: &mut Context<Self>,
24100 ) {
24101 let cleared = self
24102 .display_map
24103 .update(cx, |map, _| map.clear_highlights_with(f));
24104 if cleared {
24105 cx.notify();
24106 }
24107 }
24108
24109 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
24110 (self.read_only(cx) || self.blink_manager.read(cx).visible())
24111 && self.focus_handle.is_focused(window)
24112 }
24113
24114 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
24115 self.show_cursor_when_unfocused = is_enabled;
24116 cx.notify();
24117 }
24118
24119 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
24120 cx.notify();
24121 }
24122
24123 fn on_debug_session_event(
24124 &mut self,
24125 _session: Entity<Session>,
24126 event: &SessionEvent,
24127 cx: &mut Context<Self>,
24128 ) {
24129 if let SessionEvent::InvalidateInlineValue = event {
24130 self.refresh_inline_values(cx);
24131 }
24132 }
24133
24134 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
24135 let Some(semantics) = self.semantics_provider.clone() else {
24136 return;
24137 };
24138
24139 if !self.inline_value_cache.enabled {
24140 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24141 self.splice_inlays(&inlays, Vec::new(), cx);
24142 return;
24143 }
24144
24145 let current_execution_position = self
24146 .highlighted_rows
24147 .get(&TypeId::of::<ActiveDebugLine>())
24148 .and_then(|lines| lines.last().map(|line| line.range.end));
24149
24150 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24151 let inline_values = editor
24152 .update(cx, |editor, cx| {
24153 let Some(current_execution_position) = current_execution_position else {
24154 return Some(Task::ready(Ok(Vec::new())));
24155 };
24156
24157 let (buffer, buffer_anchor) =
24158 editor.buffer.read_with(cx, |multibuffer, cx| {
24159 let multibuffer_snapshot = multibuffer.snapshot(cx);
24160 let (buffer_anchor, _) = multibuffer_snapshot
24161 .anchor_to_buffer_anchor(current_execution_position)?;
24162 let buffer = multibuffer.buffer(buffer_anchor.buffer_id)?;
24163 Some((buffer, buffer_anchor))
24164 })?;
24165
24166 let range = buffer.read(cx).anchor_before(0)..buffer_anchor;
24167
24168 semantics.inline_values(buffer, range, cx)
24169 })
24170 .ok()
24171 .flatten()?
24172 .await
24173 .context("refreshing debugger inlays")
24174 .log_err()?;
24175
24176 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24177
24178 for (buffer_id, inline_value) in inline_values
24179 .into_iter()
24180 .map(|hint| (hint.position.buffer_id, hint))
24181 {
24182 buffer_inline_values
24183 .entry(buffer_id)
24184 .or_default()
24185 .push(inline_value);
24186 }
24187
24188 editor
24189 .update(cx, |editor, cx| {
24190 let snapshot = editor.buffer.read(cx).snapshot(cx);
24191 let mut new_inlays = Vec::default();
24192
24193 for (_buffer_id, inline_values) in buffer_inline_values {
24194 for hint in inline_values {
24195 let Some(anchor) = snapshot.anchor_in_excerpt(hint.position) else {
24196 continue;
24197 };
24198 let inlay = Inlay::debugger(
24199 post_inc(&mut editor.next_inlay_id),
24200 anchor,
24201 hint.text(),
24202 );
24203 if !inlay.text().chars().contains(&'\n') {
24204 new_inlays.push(inlay);
24205 }
24206 }
24207 }
24208
24209 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24210 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24211
24212 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24213 })
24214 .ok()?;
24215 Some(())
24216 });
24217 }
24218
24219 fn on_buffer_event(
24220 &mut self,
24221 multibuffer: &Entity<MultiBuffer>,
24222 event: &multi_buffer::Event,
24223 window: &mut Window,
24224 cx: &mut Context<Self>,
24225 ) {
24226 match event {
24227 multi_buffer::Event::Edited {
24228 edited_buffer,
24229 is_local,
24230 } => {
24231 self.scrollbar_marker_state.dirty = true;
24232 self.active_indent_guides_state.dirty = true;
24233 self.refresh_active_diagnostics(cx);
24234 self.refresh_code_actions(window, cx);
24235 self.refresh_single_line_folds(window, cx);
24236 let snapshot = self.snapshot(window, cx);
24237 self.refresh_matching_bracket_highlights(&snapshot, cx);
24238 self.refresh_outline_symbols_at_cursor(cx);
24239 self.refresh_sticky_headers(&snapshot, cx);
24240 if *is_local && self.has_active_edit_prediction() {
24241 self.update_visible_edit_prediction(window, cx);
24242 }
24243
24244 // Clean up orphaned review comments after edits
24245 self.cleanup_orphaned_review_comments(cx);
24246
24247 if let Some(buffer) = edited_buffer {
24248 if buffer.read(cx).file().is_none() {
24249 cx.emit(EditorEvent::TitleChanged);
24250 }
24251
24252 if self.project.is_some() {
24253 let buffer_id = buffer.read(cx).remote_id();
24254 self.register_buffer(buffer_id, cx);
24255 self.update_lsp_data(Some(buffer_id), window, cx);
24256 self.refresh_inlay_hints(
24257 InlayHintRefreshReason::BufferEdited(buffer_id),
24258 cx,
24259 );
24260 }
24261 }
24262
24263 cx.emit(EditorEvent::BufferEdited);
24264 cx.emit(SearchEvent::MatchesInvalidated);
24265
24266 let Some(project) = &self.project else { return };
24267 let (telemetry, is_via_ssh) = {
24268 let project = project.read(cx);
24269 let telemetry = project.client().telemetry().clone();
24270 let is_via_ssh = project.is_via_remote_server();
24271 (telemetry, is_via_ssh)
24272 };
24273 telemetry.log_edit_event("editor", is_via_ssh);
24274 }
24275 multi_buffer::Event::BufferRangesUpdated {
24276 buffer,
24277 ranges,
24278 path_key,
24279 } => {
24280 self.refresh_document_highlights(cx);
24281 let buffer_id = buffer.read(cx).remote_id();
24282 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24283 && let Some(project) = &self.project
24284 {
24285 update_uncommitted_diff_for_buffer(
24286 cx.entity(),
24287 project,
24288 [buffer.clone()],
24289 self.buffer.clone(),
24290 cx,
24291 )
24292 .detach();
24293 }
24294 self.register_visible_buffers(cx);
24295 self.update_lsp_data(Some(buffer_id), window, cx);
24296 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24297 self.refresh_runnables(None, window, cx);
24298 self.bracket_fetched_tree_sitter_chunks
24299 .retain(|range, _| range.start.buffer_id != buffer_id);
24300 self.colorize_brackets(false, cx);
24301 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24302 self.semantic_token_state.invalidate_buffer(&buffer_id);
24303 cx.emit(EditorEvent::BufferRangesUpdated {
24304 buffer: buffer.clone(),
24305 ranges: ranges.clone(),
24306 path_key: path_key.clone(),
24307 });
24308 }
24309 multi_buffer::Event::BuffersRemoved { removed_buffer_ids } => {
24310 if let Some(inlay_hints) = &mut self.inlay_hints {
24311 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24312 }
24313 self.refresh_inlay_hints(
24314 InlayHintRefreshReason::BuffersRemoved(removed_buffer_ids.clone()),
24315 cx,
24316 );
24317 for buffer_id in removed_buffer_ids {
24318 self.registered_buffers.remove(buffer_id);
24319 self.clear_runnables(Some(*buffer_id));
24320 self.semantic_token_state.invalidate_buffer(buffer_id);
24321 self.display_map.update(cx, |display_map, cx| {
24322 display_map.invalidate_semantic_highlights(*buffer_id);
24323 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24324 });
24325 }
24326
24327 self.display_map.update(cx, |display_map, cx| {
24328 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24329 });
24330
24331 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24332 cx.emit(EditorEvent::BuffersRemoved {
24333 removed_buffer_ids: removed_buffer_ids.clone(),
24334 });
24335 }
24336 multi_buffer::Event::BuffersEdited { buffer_ids } => {
24337 self.display_map.update(cx, |map, cx| {
24338 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24339 });
24340 cx.emit(EditorEvent::BuffersEdited {
24341 buffer_ids: buffer_ids.clone(),
24342 });
24343 }
24344 multi_buffer::Event::Reparsed(buffer_id) => {
24345 self.refresh_runnables(Some(*buffer_id), window, cx);
24346 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24347 self.colorize_brackets(true, cx);
24348 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24349
24350 cx.emit(EditorEvent::Reparsed(*buffer_id));
24351 }
24352 multi_buffer::Event::DiffHunksToggled => {
24353 self.refresh_runnables(None, window, cx);
24354 }
24355 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24356 if !is_fresh_language {
24357 self.registered_buffers.remove(&buffer_id);
24358 }
24359 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24360 cx.emit(EditorEvent::Reparsed(*buffer_id));
24361 self.update_edit_prediction_settings(cx);
24362 cx.notify();
24363 }
24364 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24365 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24366 multi_buffer::Event::FileHandleChanged
24367 | multi_buffer::Event::Reloaded
24368 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24369 multi_buffer::Event::DiagnosticsUpdated => {
24370 self.update_diagnostics_state(window, cx);
24371 }
24372 _ => {}
24373 };
24374 }
24375
24376 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24377 if !self.diagnostics_enabled() {
24378 return;
24379 }
24380 self.refresh_active_diagnostics(cx);
24381 self.refresh_inline_diagnostics(true, window, cx);
24382 self.scrollbar_marker_state.dirty = true;
24383 cx.notify();
24384 }
24385
24386 pub fn start_temporary_diff_override(&mut self) {
24387 self.load_diff_task.take();
24388 self.temporary_diff_override = true;
24389 }
24390
24391 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24392 self.temporary_diff_override = false;
24393 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24394 self.buffer.update(cx, |buffer, cx| {
24395 buffer.set_all_diff_hunks_collapsed(cx);
24396 });
24397
24398 if let Some(project) = self.project.clone() {
24399 self.load_diff_task = Some(
24400 update_uncommitted_diff_for_buffer(
24401 cx.entity(),
24402 &project,
24403 self.buffer.read(cx).all_buffers(),
24404 self.buffer.clone(),
24405 cx,
24406 )
24407 .shared(),
24408 );
24409 }
24410 }
24411
24412 fn on_display_map_changed(
24413 &mut self,
24414 _: Entity<DisplayMap>,
24415 _: &mut Window,
24416 cx: &mut Context<Self>,
24417 ) {
24418 cx.notify();
24419 }
24420
24421 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24422 if !self.mode.is_full() {
24423 return None;
24424 }
24425
24426 let theme_settings = theme_settings::ThemeSettings::get_global(cx);
24427 let theme = cx.theme();
24428 let accent_colors = theme.accents().clone();
24429
24430 let accent_overrides = theme_settings
24431 .theme_overrides
24432 .get(theme.name.as_ref())
24433 .map(|theme_style| &theme_style.accents)
24434 .into_iter()
24435 .flatten()
24436 .chain(
24437 theme_settings
24438 .experimental_theme_overrides
24439 .as_ref()
24440 .map(|overrides| &overrides.accents)
24441 .into_iter()
24442 .flatten(),
24443 )
24444 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24445 .collect();
24446
24447 Some(AccentData {
24448 colors: accent_colors,
24449 overrides: accent_overrides,
24450 })
24451 }
24452
24453 fn fetch_applicable_language_settings(
24454 &self,
24455 cx: &App,
24456 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24457 if !self.mode.is_full() {
24458 return HashMap::default();
24459 }
24460
24461 self.buffer().read(cx).all_buffers().into_iter().fold(
24462 HashMap::default(),
24463 |mut acc, buffer| {
24464 let buffer = buffer.read(cx);
24465 let language = buffer.language().map(|language| language.name());
24466 if let hash_map::Entry::Vacant(v) = acc.entry(language) {
24467 v.insert(LanguageSettings::for_buffer(&buffer, cx).into_owned());
24468 }
24469 acc
24470 },
24471 )
24472 }
24473
24474 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24475 let new_language_settings = self.fetch_applicable_language_settings(cx);
24476 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24477 self.applicable_language_settings = new_language_settings;
24478
24479 let new_accents = self.fetch_accent_data(cx);
24480 let accents_changed = new_accents != self.accent_data;
24481 self.accent_data = new_accents;
24482
24483 if self.diagnostics_enabled() {
24484 let new_severity = EditorSettings::get_global(cx)
24485 .diagnostics_max_severity
24486 .unwrap_or(DiagnosticSeverity::Hint);
24487 self.set_max_diagnostics_severity(new_severity, cx);
24488 }
24489 self.refresh_runnables(None, window, cx);
24490 self.update_edit_prediction_settings(cx);
24491 self.refresh_edit_prediction(true, false, window, cx);
24492 self.refresh_inline_values(cx);
24493
24494 let old_cursor_shape = self.cursor_shape;
24495 let old_show_breadcrumbs = self.show_breadcrumbs;
24496
24497 {
24498 let editor_settings = EditorSettings::get_global(cx);
24499 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24500 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24501 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24502 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24503 }
24504
24505 if old_cursor_shape != self.cursor_shape {
24506 cx.emit(EditorEvent::CursorShapeChanged);
24507 }
24508
24509 if old_show_breadcrumbs != self.show_breadcrumbs {
24510 cx.emit(EditorEvent::BreadcrumbsChanged);
24511 }
24512
24513 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24514 let project_settings = ProjectSettings::get_global(cx);
24515 (
24516 project_settings.session.restore_unsaved_buffers,
24517 project_settings.diagnostics.inline.enabled,
24518 project_settings.git.inline_blame.enabled,
24519 )
24520 };
24521 self.buffer_serialization = self
24522 .should_serialize_buffer()
24523 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24524
24525 if self.mode.is_full() {
24526 if self.show_inline_diagnostics != show_inline_diagnostics {
24527 self.show_inline_diagnostics = show_inline_diagnostics;
24528 self.refresh_inline_diagnostics(false, window, cx);
24529 }
24530
24531 if self.git_blame_inline_enabled != inline_blame_enabled {
24532 self.toggle_git_blame_inline_internal(false, window, cx);
24533 }
24534
24535 let minimap_settings = EditorSettings::get_global(cx).minimap;
24536 if self.minimap_visibility != MinimapVisibility::Disabled {
24537 if self.minimap_visibility.settings_visibility()
24538 != minimap_settings.minimap_enabled()
24539 {
24540 self.set_minimap_visibility(
24541 MinimapVisibility::for_mode(self.mode(), cx),
24542 window,
24543 cx,
24544 );
24545 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24546 minimap_entity.update(cx, |minimap_editor, cx| {
24547 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24548 })
24549 }
24550 }
24551
24552 if language_settings_changed || accents_changed {
24553 self.colorize_brackets(true, cx);
24554 }
24555
24556 if language_settings_changed {
24557 self.clear_disabled_lsp_folding_ranges(window, cx);
24558 self.refresh_document_symbols(None, cx);
24559 }
24560
24561 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24562 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24563 }) {
24564 if !inlay_splice.is_empty() {
24565 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24566 }
24567 self.refresh_document_colors(None, window, cx);
24568 }
24569
24570 self.refresh_inlay_hints(
24571 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24572 self.selections.newest_anchor().head(),
24573 &self.buffer.read(cx).snapshot(cx),
24574 cx,
24575 )),
24576 cx,
24577 );
24578
24579 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24580 .global_lsp_settings
24581 .semantic_token_rules
24582 .clone();
24583 let semantic_token_rules_changed = self
24584 .semantic_token_state
24585 .update_rules(new_semantic_token_rules);
24586 if language_settings_changed || semantic_token_rules_changed {
24587 self.invalidate_semantic_tokens(None);
24588 self.refresh_semantic_tokens(None, None, cx);
24589 }
24590 }
24591
24592 cx.notify();
24593 }
24594
24595 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24596 if !self.mode.is_full() {
24597 return;
24598 }
24599
24600 let new_accents = self.fetch_accent_data(cx);
24601 if new_accents != self.accent_data {
24602 self.accent_data = new_accents;
24603 self.colorize_brackets(true, cx);
24604 }
24605
24606 self.invalidate_semantic_tokens(None);
24607 self.refresh_semantic_tokens(None, None, cx);
24608 }
24609
24610 pub fn set_searchable(&mut self, searchable: bool) {
24611 self.searchable = searchable;
24612 }
24613
24614 pub fn searchable(&self) -> bool {
24615 self.searchable
24616 }
24617
24618 pub fn open_excerpts_in_split(
24619 &mut self,
24620 _: &OpenExcerptsSplit,
24621 window: &mut Window,
24622 cx: &mut Context<Self>,
24623 ) {
24624 self.open_excerpts_common(None, true, window, cx)
24625 }
24626
24627 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24628 self.open_excerpts_common(None, false, window, cx)
24629 }
24630
24631 pub(crate) fn open_excerpts_common(
24632 &mut self,
24633 jump_data: Option<JumpData>,
24634 split: bool,
24635 window: &mut Window,
24636 cx: &mut Context<Self>,
24637 ) {
24638 if self.buffer.read(cx).is_singleton() {
24639 cx.propagate();
24640 return;
24641 }
24642
24643 let mut new_selections_by_buffer = HashMap::default();
24644 match &jump_data {
24645 Some(JumpData::MultiBufferPoint {
24646 anchor,
24647 position,
24648 line_offset_from_top,
24649 }) => {
24650 if let Some(buffer) = self.buffer.read(cx).buffer(anchor.buffer_id) {
24651 let buffer_snapshot = buffer.read(cx).snapshot();
24652 let jump_to_point = if buffer_snapshot.can_resolve(&anchor) {
24653 language::ToPoint::to_point(anchor, &buffer_snapshot)
24654 } else {
24655 buffer_snapshot.clip_point(*position, Bias::Left)
24656 };
24657 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24658 new_selections_by_buffer.insert(
24659 buffer,
24660 (
24661 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24662 Some(*line_offset_from_top),
24663 ),
24664 );
24665 }
24666 }
24667 Some(JumpData::MultiBufferRow {
24668 row,
24669 line_offset_from_top,
24670 }) => {
24671 let point = MultiBufferPoint::new(row.0, 0);
24672 if let Some((buffer, buffer_point)) =
24673 self.buffer.read(cx).point_to_buffer_point(point, cx)
24674 {
24675 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24676 new_selections_by_buffer
24677 .entry(buffer)
24678 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24679 .0
24680 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24681 }
24682 }
24683 None => {
24684 let selections = self
24685 .selections
24686 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24687 let multi_buffer = self.buffer.read(cx);
24688 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
24689 for selection in selections {
24690 for (snapshot, range, anchor) in multi_buffer_snapshot
24691 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24692 {
24693 if let Some((text_anchor, _)) = anchor.and_then(|anchor| {
24694 multi_buffer_snapshot.anchor_to_buffer_anchor(anchor)
24695 }) {
24696 let Some(buffer_handle) = multi_buffer.buffer(text_anchor.buffer_id)
24697 else {
24698 continue;
24699 };
24700 let offset = text::ToOffset::to_offset(
24701 &text_anchor,
24702 &buffer_handle.read(cx).snapshot(),
24703 );
24704 let range = BufferOffset(offset)..BufferOffset(offset);
24705 new_selections_by_buffer
24706 .entry(buffer_handle)
24707 .or_insert((Vec::new(), None))
24708 .0
24709 .push(range)
24710 } else {
24711 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24712 else {
24713 continue;
24714 };
24715 new_selections_by_buffer
24716 .entry(buffer_handle)
24717 .or_insert((Vec::new(), None))
24718 .0
24719 .push(range)
24720 }
24721 }
24722 }
24723 }
24724 }
24725
24726 if self.delegate_open_excerpts {
24727 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24728 .into_iter()
24729 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24730 .collect();
24731 if !selections_by_buffer.is_empty() {
24732 cx.emit(EditorEvent::OpenExcerptsRequested {
24733 selections_by_buffer,
24734 split,
24735 });
24736 }
24737 return;
24738 }
24739
24740 let Some(workspace) = self.workspace() else {
24741 cx.propagate();
24742 return;
24743 };
24744
24745 new_selections_by_buffer
24746 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24747
24748 if new_selections_by_buffer.is_empty() {
24749 return;
24750 }
24751
24752 Self::open_buffers_in_workspace(
24753 workspace.downgrade(),
24754 new_selections_by_buffer,
24755 split,
24756 window,
24757 cx,
24758 );
24759 }
24760
24761 pub(crate) fn open_buffers_in_workspace(
24762 workspace: WeakEntity<Workspace>,
24763 new_selections_by_buffer: HashMap<
24764 Entity<language::Buffer>,
24765 (Vec<Range<BufferOffset>>, Option<u32>),
24766 >,
24767 split: bool,
24768 window: &mut Window,
24769 cx: &mut App,
24770 ) {
24771 // We defer the pane interaction because we ourselves are a workspace item
24772 // and activating a new item causes the pane to call a method on us reentrantly,
24773 // which panics if we're on the stack.
24774 window.defer(cx, move |window, cx| {
24775 workspace
24776 .update(cx, |workspace, cx| {
24777 let pane = if split {
24778 workspace.adjacent_pane(window, cx)
24779 } else {
24780 workspace.active_pane().clone()
24781 };
24782
24783 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24784 let buffer_read = buffer.read(cx);
24785 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24786 (true, project::File::from_dyn(Some(file)).is_some())
24787 } else {
24788 (false, false)
24789 };
24790
24791 // If project file is none workspace.open_project_item will fail to open the excerpt
24792 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24793 // so we check if there's a tab match in that case first
24794 let editor = (!has_file || !is_project_file)
24795 .then(|| {
24796 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24797 // so `workspace.open_project_item` will never find them, always opening a new editor.
24798 // Instead, we try to activate the existing editor in the pane first.
24799 let (editor, pane_item_index, pane_item_id) =
24800 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24801 let editor = item.downcast::<Editor>()?;
24802 let singleton_buffer =
24803 editor.read(cx).buffer().read(cx).as_singleton()?;
24804 if singleton_buffer == buffer {
24805 Some((editor, i, item.item_id()))
24806 } else {
24807 None
24808 }
24809 })?;
24810 pane.update(cx, |pane, cx| {
24811 pane.activate_item(pane_item_index, true, true, window, cx);
24812 if !PreviewTabsSettings::get_global(cx)
24813 .enable_preview_from_multibuffer
24814 {
24815 pane.unpreview_item_if_preview(pane_item_id);
24816 }
24817 });
24818 Some(editor)
24819 })
24820 .flatten()
24821 .unwrap_or_else(|| {
24822 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24823 .enable_keep_preview_on_code_navigation;
24824 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24825 .enable_preview_from_multibuffer;
24826 workspace.open_project_item::<Self>(
24827 pane.clone(),
24828 buffer,
24829 true,
24830 true,
24831 keep_old_preview,
24832 allow_new_preview,
24833 window,
24834 cx,
24835 )
24836 });
24837
24838 editor.update(cx, |editor, cx| {
24839 if has_file && !is_project_file {
24840 editor.set_read_only(true);
24841 }
24842 let autoscroll = match scroll_offset {
24843 Some(scroll_offset) => {
24844 Autoscroll::top_relative(scroll_offset as usize)
24845 }
24846 None => Autoscroll::newest(),
24847 };
24848 let nav_history = editor.nav_history.take();
24849 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24850 let Some(buffer_snapshot) = multibuffer_snapshot.as_singleton() else {
24851 return;
24852 };
24853 editor.change_selections(
24854 SelectionEffects::scroll(autoscroll),
24855 window,
24856 cx,
24857 |s| {
24858 s.select_ranges(ranges.into_iter().map(|range| {
24859 let range = buffer_snapshot.anchor_before(range.start)
24860 ..buffer_snapshot.anchor_after(range.end);
24861 multibuffer_snapshot
24862 .buffer_anchor_range_to_anchor_range(range)
24863 .unwrap()
24864 }));
24865 },
24866 );
24867 editor.nav_history = nav_history;
24868 });
24869 }
24870 })
24871 .ok();
24872 });
24873 }
24874
24875 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24876 let snapshot = self.buffer.read(cx).read(cx);
24877 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24878 Some(
24879 ranges
24880 .iter()
24881 .map(move |range| {
24882 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24883 })
24884 .collect(),
24885 )
24886 }
24887
24888 fn selection_replacement_ranges(
24889 &self,
24890 range: Range<MultiBufferOffsetUtf16>,
24891 cx: &mut App,
24892 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24893 let selections = self
24894 .selections
24895 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24896 let newest_selection = selections
24897 .iter()
24898 .max_by_key(|selection| selection.id)
24899 .unwrap();
24900 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24901 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24902 let snapshot = self.buffer.read(cx).read(cx);
24903 selections
24904 .into_iter()
24905 .map(|mut selection| {
24906 selection.start.0.0 =
24907 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24908 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24909 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24910 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24911 })
24912 .collect()
24913 }
24914
24915 fn report_editor_event(
24916 &self,
24917 reported_event: ReportEditorEvent,
24918 file_extension: Option<String>,
24919 cx: &App,
24920 ) {
24921 if cfg!(any(test, feature = "test-support")) {
24922 return;
24923 }
24924
24925 let Some(project) = &self.project else { return };
24926
24927 // If None, we are in a file without an extension
24928 let file = self
24929 .buffer
24930 .read(cx)
24931 .as_singleton()
24932 .and_then(|b| b.read(cx).file());
24933 let file_extension = file_extension.or(file
24934 .as_ref()
24935 .and_then(|file| Path::new(file.file_name(cx)).extension())
24936 .and_then(|e| e.to_str())
24937 .map(|a| a.to_string()));
24938
24939 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24940 .map(|vim_mode| vim_mode.0)
24941 .unwrap_or(false);
24942
24943 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24944 let copilot_enabled = edit_predictions_provider
24945 == language::language_settings::EditPredictionProvider::Copilot;
24946 let copilot_enabled_for_language = self
24947 .buffer
24948 .read(cx)
24949 .language_settings(cx)
24950 .show_edit_predictions;
24951
24952 let project = project.read(cx);
24953 let event_type = reported_event.event_type();
24954
24955 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24956 telemetry::event!(
24957 event_type,
24958 type = if auto_saved {"autosave"} else {"manual"},
24959 file_extension,
24960 vim_mode,
24961 copilot_enabled,
24962 copilot_enabled_for_language,
24963 edit_predictions_provider,
24964 is_via_ssh = project.is_via_remote_server(),
24965 );
24966 } else {
24967 telemetry::event!(
24968 event_type,
24969 file_extension,
24970 vim_mode,
24971 copilot_enabled,
24972 copilot_enabled_for_language,
24973 edit_predictions_provider,
24974 is_via_ssh = project.is_via_remote_server(),
24975 );
24976 };
24977 }
24978
24979 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
24980 /// with each line being an array of {text, highlight} objects.
24981 fn copy_highlight_json(
24982 &mut self,
24983 _: &CopyHighlightJson,
24984 _: &mut Window,
24985 cx: &mut Context<Self>,
24986 ) {
24987 #[derive(Serialize)]
24988 struct Chunk<'a> {
24989 text: String,
24990 highlight: Option<&'a str>,
24991 }
24992
24993 let snapshot = self.buffer.read(cx).snapshot(cx);
24994 let mut selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
24995 let max_point = snapshot.max_point();
24996
24997 let range = if self.selections.line_mode() {
24998 selection.start = Point::new(selection.start.row, 0);
24999 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
25000 selection.goal = SelectionGoal::None;
25001 selection.range()
25002 } else if selection.is_empty() {
25003 Point::new(0, 0)..max_point
25004 } else {
25005 selection.range()
25006 };
25007
25008 let chunks = snapshot.chunks(range, true);
25009 let mut lines = Vec::new();
25010 let mut line: VecDeque<Chunk> = VecDeque::new();
25011
25012 let Some(style) = self.style.as_ref() else {
25013 return;
25014 };
25015
25016 for chunk in chunks {
25017 let highlight = chunk
25018 .syntax_highlight_id
25019 .and_then(|id| style.syntax.get_capture_name(id));
25020
25021 let mut chunk_lines = chunk.text.split('\n').peekable();
25022 while let Some(text) = chunk_lines.next() {
25023 let mut merged_with_last_token = false;
25024 if let Some(last_token) = line.back_mut()
25025 && last_token.highlight == highlight
25026 {
25027 last_token.text.push_str(text);
25028 merged_with_last_token = true;
25029 }
25030
25031 if !merged_with_last_token {
25032 line.push_back(Chunk {
25033 text: text.into(),
25034 highlight,
25035 });
25036 }
25037
25038 if chunk_lines.peek().is_some() {
25039 if line.len() > 1 && line.front().unwrap().text.is_empty() {
25040 line.pop_front();
25041 }
25042 if line.len() > 1 && line.back().unwrap().text.is_empty() {
25043 line.pop_back();
25044 }
25045
25046 lines.push(mem::take(&mut line));
25047 }
25048 }
25049 }
25050
25051 if line.iter().any(|chunk| !chunk.text.is_empty()) {
25052 lines.push(line);
25053 }
25054
25055 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
25056 return;
25057 };
25058 cx.write_to_clipboard(ClipboardItem::new_string(lines));
25059 }
25060
25061 pub fn open_context_menu(
25062 &mut self,
25063 _: &OpenContextMenu,
25064 window: &mut Window,
25065 cx: &mut Context<Self>,
25066 ) {
25067 self.request_autoscroll(Autoscroll::newest(), cx);
25068 let position = self
25069 .selections
25070 .newest_display(&self.display_snapshot(cx))
25071 .start;
25072 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
25073 }
25074
25075 pub fn replay_insert_event(
25076 &mut self,
25077 text: &str,
25078 relative_utf16_range: Option<Range<isize>>,
25079 window: &mut Window,
25080 cx: &mut Context<Self>,
25081 ) {
25082 if !self.input_enabled {
25083 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25084 return;
25085 }
25086 if let Some(relative_utf16_range) = relative_utf16_range {
25087 let selections = self
25088 .selections
25089 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25090 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25091 let new_ranges = selections.into_iter().map(|range| {
25092 let start = MultiBufferOffsetUtf16(OffsetUtf16(
25093 range
25094 .head()
25095 .0
25096 .0
25097 .saturating_add_signed(relative_utf16_range.start),
25098 ));
25099 let end = MultiBufferOffsetUtf16(OffsetUtf16(
25100 range
25101 .head()
25102 .0
25103 .0
25104 .saturating_add_signed(relative_utf16_range.end),
25105 ));
25106 start..end
25107 });
25108 s.select_ranges(new_ranges);
25109 });
25110 }
25111
25112 self.handle_input(text, window, cx);
25113 }
25114
25115 pub fn is_focused(&self, window: &Window) -> bool {
25116 self.focus_handle.is_focused(window)
25117 }
25118
25119 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25120 cx.emit(EditorEvent::Focused);
25121
25122 if let Some(descendant) = self
25123 .last_focused_descendant
25124 .take()
25125 .and_then(|descendant| descendant.upgrade())
25126 {
25127 window.focus(&descendant, cx);
25128 } else {
25129 if let Some(blame) = self.blame.as_ref() {
25130 blame.update(cx, GitBlame::focus)
25131 }
25132
25133 self.blink_manager.update(cx, BlinkManager::enable);
25134 self.show_cursor_names(window, cx);
25135 self.buffer.update(cx, |buffer, cx| {
25136 buffer.finalize_last_transaction(cx);
25137 if self.leader_id.is_none() {
25138 buffer.set_active_selections(
25139 &self.selections.disjoint_anchors_arc(),
25140 self.selections.line_mode(),
25141 self.cursor_shape,
25142 cx,
25143 );
25144 }
25145 });
25146
25147 if let Some(position_map) = self.last_position_map.clone()
25148 && !self.mouse_cursor_hidden
25149 {
25150 EditorElement::mouse_moved(
25151 self,
25152 &MouseMoveEvent {
25153 position: window.mouse_position(),
25154 pressed_button: None,
25155 modifiers: window.modifiers(),
25156 },
25157 &position_map,
25158 None,
25159 window,
25160 cx,
25161 );
25162 }
25163 }
25164 }
25165
25166 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25167 cx.emit(EditorEvent::FocusedIn)
25168 }
25169
25170 fn handle_focus_out(
25171 &mut self,
25172 event: FocusOutEvent,
25173 _window: &mut Window,
25174 cx: &mut Context<Self>,
25175 ) {
25176 if event.blurred != self.focus_handle {
25177 self.last_focused_descendant = Some(event.blurred);
25178 }
25179 self.selection_drag_state = SelectionDragState::None;
25180 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25181 }
25182
25183 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25184 self.blink_manager.update(cx, BlinkManager::disable);
25185 self.buffer
25186 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25187
25188 if let Some(blame) = self.blame.as_ref() {
25189 blame.update(cx, GitBlame::blur)
25190 }
25191 if !self.hover_state.focused(window, cx) {
25192 hide_hover(self, cx);
25193 }
25194 if !self
25195 .context_menu
25196 .borrow()
25197 .as_ref()
25198 .is_some_and(|context_menu| context_menu.focused(window, cx))
25199 {
25200 self.hide_context_menu(window, cx);
25201 }
25202 self.take_active_edit_prediction(true, cx);
25203 cx.emit(EditorEvent::Blurred);
25204 cx.notify();
25205 }
25206
25207 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25208 let mut pending: String = window
25209 .pending_input_keystrokes()
25210 .into_iter()
25211 .flatten()
25212 .filter_map(|keystroke| keystroke.key_char.clone())
25213 .collect();
25214
25215 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25216 pending = "".to_string();
25217 }
25218
25219 let existing_pending = self
25220 .text_highlights(HighlightKey::PendingInput, cx)
25221 .map(|(_, ranges)| ranges.to_vec());
25222 if existing_pending.is_none() && pending.is_empty() {
25223 return;
25224 }
25225 let transaction =
25226 self.transact(window, cx, |this, window, cx| {
25227 let selections = this
25228 .selections
25229 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25230 let edits = selections
25231 .iter()
25232 .map(|selection| (selection.end..selection.end, pending.clone()));
25233 this.edit(edits, cx);
25234 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25235 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25236 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25237 }));
25238 });
25239 if let Some(existing_ranges) = existing_pending {
25240 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25241 this.edit(edits, cx);
25242 }
25243 });
25244
25245 let snapshot = self.snapshot(window, cx);
25246 let ranges = self
25247 .selections
25248 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25249 .into_iter()
25250 .map(|selection| {
25251 snapshot.buffer_snapshot().anchor_after(selection.end)
25252 ..snapshot
25253 .buffer_snapshot()
25254 .anchor_before(selection.end + pending.len())
25255 })
25256 .collect();
25257
25258 if pending.is_empty() {
25259 self.clear_highlights(HighlightKey::PendingInput, cx);
25260 } else {
25261 self.highlight_text(
25262 HighlightKey::PendingInput,
25263 ranges,
25264 HighlightStyle {
25265 underline: Some(UnderlineStyle {
25266 thickness: px(1.),
25267 color: None,
25268 wavy: false,
25269 }),
25270 ..Default::default()
25271 },
25272 cx,
25273 );
25274 }
25275
25276 self.ime_transaction = self.ime_transaction.or(transaction);
25277 if let Some(transaction) = self.ime_transaction {
25278 self.buffer.update(cx, |buffer, cx| {
25279 buffer.group_until_transaction(transaction, cx);
25280 });
25281 }
25282
25283 if self
25284 .text_highlights(HighlightKey::PendingInput, cx)
25285 .is_none()
25286 {
25287 self.ime_transaction.take();
25288 }
25289 }
25290
25291 pub fn register_action_renderer(
25292 &mut self,
25293 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25294 ) -> Subscription {
25295 let id = self.next_editor_action_id.post_inc();
25296 self.editor_actions
25297 .borrow_mut()
25298 .insert(id, Box::new(listener));
25299
25300 let editor_actions = self.editor_actions.clone();
25301 Subscription::new(move || {
25302 editor_actions.borrow_mut().remove(&id);
25303 })
25304 }
25305
25306 pub fn register_action<A: Action>(
25307 &mut self,
25308 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25309 ) -> Subscription {
25310 let id = self.next_editor_action_id.post_inc();
25311 let listener = Arc::new(listener);
25312 self.editor_actions.borrow_mut().insert(
25313 id,
25314 Box::new(move |_, window, _| {
25315 let listener = listener.clone();
25316 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25317 let action = action.downcast_ref().unwrap();
25318 if phase == DispatchPhase::Bubble {
25319 listener(action, window, cx)
25320 }
25321 })
25322 }),
25323 );
25324
25325 let editor_actions = self.editor_actions.clone();
25326 Subscription::new(move || {
25327 editor_actions.borrow_mut().remove(&id);
25328 })
25329 }
25330
25331 pub fn file_header_size(&self) -> u32 {
25332 FILE_HEADER_HEIGHT
25333 }
25334
25335 pub fn restore(
25336 &mut self,
25337 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25338 window: &mut Window,
25339 cx: &mut Context<Self>,
25340 ) {
25341 self.buffer().update(cx, |multi_buffer, cx| {
25342 for (buffer_id, changes) in revert_changes {
25343 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25344 buffer.update(cx, |buffer, cx| {
25345 buffer.edit(
25346 changes
25347 .into_iter()
25348 .map(|(range, text)| (range, text.to_string())),
25349 None,
25350 cx,
25351 );
25352 });
25353 }
25354 }
25355 });
25356 let selections = self
25357 .selections
25358 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
25359 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25360 s.select(selections);
25361 });
25362 }
25363
25364 pub fn to_pixel_point(
25365 &mut self,
25366 source: Anchor,
25367 editor_snapshot: &EditorSnapshot,
25368 window: &mut Window,
25369 cx: &mut App,
25370 ) -> Option<gpui::Point<Pixels>> {
25371 let source_point = source.to_display_point(editor_snapshot);
25372 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25373 }
25374
25375 pub fn display_to_pixel_point(
25376 &mut self,
25377 source: DisplayPoint,
25378 editor_snapshot: &EditorSnapshot,
25379 window: &mut Window,
25380 cx: &mut App,
25381 ) -> Option<gpui::Point<Pixels>> {
25382 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25383 let text_layout_details = self.text_layout_details(window, cx);
25384 let scroll_top = text_layout_details
25385 .scroll_anchor
25386 .scroll_position(editor_snapshot)
25387 .y;
25388
25389 if source.row().as_f64() < scroll_top.floor() {
25390 return None;
25391 }
25392 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25393 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25394 Some(gpui::Point::new(source_x, source_y))
25395 }
25396
25397 pub fn has_visible_completions_menu(&self) -> bool {
25398 !self.edit_prediction_preview_is_active()
25399 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25400 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25401 })
25402 }
25403
25404 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25405 if self.mode.is_minimap() {
25406 return;
25407 }
25408 self.addons
25409 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25410 }
25411
25412 pub fn unregister_addon<T: Addon>(&mut self) {
25413 self.addons.remove(&std::any::TypeId::of::<T>());
25414 }
25415
25416 pub fn addon<T: Addon>(&self) -> Option<&T> {
25417 let type_id = std::any::TypeId::of::<T>();
25418 self.addons
25419 .get(&type_id)
25420 .and_then(|item| item.to_any().downcast_ref::<T>())
25421 }
25422
25423 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25424 let type_id = std::any::TypeId::of::<T>();
25425 self.addons
25426 .get_mut(&type_id)
25427 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25428 }
25429
25430 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25431 let text_layout_details = self.text_layout_details(window, cx);
25432 let style = &text_layout_details.editor_style;
25433 let font_id = window.text_system().resolve_font(&style.text.font());
25434 let font_size = style.text.font_size.to_pixels(window.rem_size());
25435 let line_height = style.text.line_height_in_pixels(window.rem_size());
25436 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25437 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25438
25439 CharacterDimensions {
25440 em_width,
25441 em_advance,
25442 line_height,
25443 }
25444 }
25445
25446 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25447 self.load_diff_task.clone()
25448 }
25449
25450 fn read_metadata_from_db(
25451 &mut self,
25452 item_id: u64,
25453 workspace_id: WorkspaceId,
25454 window: &mut Window,
25455 cx: &mut Context<Editor>,
25456 ) {
25457 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25458 && !self.mode.is_minimap()
25459 && WorkspaceSettings::get(None, cx).restore_on_startup
25460 != RestoreOnStartupBehavior::EmptyTab
25461 {
25462 let buffer_snapshot = OnceCell::new();
25463
25464 // Get file path for path-based fold lookup
25465 let file_path: Option<Arc<Path>> =
25466 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25467 project::File::from_dyn(buffer.read(cx).file())
25468 .map(|file| Arc::from(file.abs_path(cx)))
25469 });
25470
25471 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25472 let db = EditorDb::global(cx);
25473 let (folds, needs_migration) = if let Some(ref path) = file_path {
25474 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
25475 && !folds.is_empty()
25476 {
25477 (Some(folds), false)
25478 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
25479 && !folds.is_empty()
25480 {
25481 // Found old editor_folds data, will migrate to file_folds
25482 (Some(folds), true)
25483 } else {
25484 (None, false)
25485 }
25486 } else {
25487 // No file path, try editor_folds as fallback
25488 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
25489 (folds.filter(|f| !f.is_empty()), false)
25490 };
25491
25492 if let Some(folds) = folds {
25493 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25494 let snapshot_len = snapshot.len().0;
25495
25496 // Helper: search for fingerprint in buffer, return offset if found
25497 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25498 // Ensure we start at a character boundary (defensive)
25499 let search_start = snapshot
25500 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25501 .0;
25502 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25503
25504 let mut byte_offset = search_start;
25505 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25506 if byte_offset > search_end {
25507 break;
25508 }
25509 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25510 return Some(byte_offset);
25511 }
25512 byte_offset += ch.len_utf8();
25513 }
25514 None
25515 };
25516
25517 // Track search position to handle duplicate fingerprints correctly.
25518 // Folds are stored in document order, so we advance after each match.
25519 let mut search_start = 0usize;
25520
25521 // Collect db_folds for migration (only folds with valid fingerprints)
25522 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25523
25524 let valid_folds: Vec<_> = folds
25525 .into_iter()
25526 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25527 // Skip folds without fingerprints (old data before migration)
25528 let sfp = start_fp?;
25529 let efp = end_fp?;
25530 let efp_len = efp.len();
25531
25532 // Fast path: check if fingerprints match at stored offsets
25533 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25534 let start_matches = stored_start < snapshot_len
25535 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25536 let efp_check_pos = stored_end.saturating_sub(efp_len);
25537 let end_matches = efp_check_pos >= stored_start
25538 && stored_end <= snapshot_len
25539 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25540
25541 let (new_start, new_end) = if start_matches && end_matches {
25542 // Offsets unchanged, use stored values
25543 (stored_start, stored_end)
25544 } else if sfp == efp {
25545 // Short fold: identical fingerprints can only match once per search
25546 // Use stored fold length to compute new_end
25547 let new_start = find_fingerprint(&sfp, search_start)?;
25548 let fold_len = stored_end - stored_start;
25549 let new_end = new_start + fold_len;
25550 (new_start, new_end)
25551 } else {
25552 // Slow path: search for fingerprints in buffer
25553 let new_start = find_fingerprint(&sfp, search_start)?;
25554 // Search for end_fp after start, then add efp_len to get actual fold end
25555 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25556 let new_end = efp_pos + efp_len;
25557 (new_start, new_end)
25558 };
25559
25560 // Advance search position for next fold
25561 search_start = new_end;
25562
25563 // Validate fold makes sense (end must be after start)
25564 if new_end <= new_start {
25565 return None;
25566 }
25567
25568 // Collect for migration if needed
25569 if needs_migration {
25570 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25571 }
25572
25573 Some(
25574 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25575 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25576 )
25577 })
25578 .collect();
25579
25580 if !valid_folds.is_empty() {
25581 self.fold_ranges(valid_folds, false, window, cx);
25582
25583 // Migrate from editor_folds to file_folds if we loaded from old table
25584 if needs_migration {
25585 if let Some(ref path) = file_path {
25586 let path = path.clone();
25587 let db = EditorDb::global(cx);
25588 cx.spawn(async move |_, _| {
25589 db.save_file_folds(workspace_id, path, db_folds_for_migration)
25590 .await
25591 .log_err();
25592 })
25593 .detach();
25594 }
25595 }
25596 }
25597 }
25598
25599 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
25600 && !selections.is_empty()
25601 {
25602 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25603 // skip adding the initial selection to selection history
25604 self.selection_history.mode = SelectionHistoryMode::Skipping;
25605 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25606 s.select_ranges(selections.into_iter().map(|(start, end)| {
25607 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25608 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25609 }));
25610 });
25611 self.selection_history.mode = SelectionHistoryMode::Normal;
25612 };
25613 }
25614
25615 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25616 }
25617
25618 /// Load folds from the file_folds database table by file path.
25619 /// Used when manually opening a file that was previously closed.
25620 fn load_folds_from_db(
25621 &mut self,
25622 workspace_id: WorkspaceId,
25623 file_path: PathBuf,
25624 window: &mut Window,
25625 cx: &mut Context<Editor>,
25626 ) {
25627 if self.mode.is_minimap()
25628 || WorkspaceSettings::get(None, cx).restore_on_startup
25629 == RestoreOnStartupBehavior::EmptyTab
25630 {
25631 return;
25632 }
25633
25634 let Some(folds) = EditorDb::global(cx)
25635 .get_file_folds(workspace_id, &file_path)
25636 .log_err()
25637 else {
25638 return;
25639 };
25640 if folds.is_empty() {
25641 return;
25642 }
25643
25644 let snapshot = self.buffer.read(cx).snapshot(cx);
25645 let snapshot_len = snapshot.len().0;
25646
25647 // Helper: search for fingerprint in buffer, return offset if found
25648 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25649 let search_start = snapshot
25650 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25651 .0;
25652 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25653
25654 let mut byte_offset = search_start;
25655 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25656 if byte_offset > search_end {
25657 break;
25658 }
25659 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25660 return Some(byte_offset);
25661 }
25662 byte_offset += ch.len_utf8();
25663 }
25664 None
25665 };
25666
25667 let mut search_start = 0usize;
25668
25669 let valid_folds: Vec<_> = folds
25670 .into_iter()
25671 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25672 let sfp = start_fp?;
25673 let efp = end_fp?;
25674 let efp_len = efp.len();
25675
25676 let start_matches = stored_start < snapshot_len
25677 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25678 let efp_check_pos = stored_end.saturating_sub(efp_len);
25679 let end_matches = efp_check_pos >= stored_start
25680 && stored_end <= snapshot_len
25681 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25682
25683 let (new_start, new_end) = if start_matches && end_matches {
25684 (stored_start, stored_end)
25685 } else if sfp == efp {
25686 let new_start = find_fingerprint(&sfp, search_start)?;
25687 let fold_len = stored_end - stored_start;
25688 let new_end = new_start + fold_len;
25689 (new_start, new_end)
25690 } else {
25691 let new_start = find_fingerprint(&sfp, search_start)?;
25692 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25693 let new_end = efp_pos + efp_len;
25694 (new_start, new_end)
25695 };
25696
25697 search_start = new_end;
25698
25699 if new_end <= new_start {
25700 return None;
25701 }
25702
25703 Some(
25704 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25705 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25706 )
25707 })
25708 .collect();
25709
25710 if !valid_folds.is_empty() {
25711 self.fold_ranges(valid_folds, false, window, cx);
25712 }
25713 }
25714
25715 fn lsp_data_enabled(&self) -> bool {
25716 self.enable_lsp_data && self.mode().is_full()
25717 }
25718
25719 fn update_lsp_data(
25720 &mut self,
25721 for_buffer: Option<BufferId>,
25722 window: &mut Window,
25723 cx: &mut Context<'_, Self>,
25724 ) {
25725 if !self.lsp_data_enabled() {
25726 return;
25727 }
25728
25729 if let Some(buffer_id) = for_buffer {
25730 self.pull_diagnostics(buffer_id, window, cx);
25731 }
25732 self.refresh_semantic_tokens(for_buffer, None, cx);
25733 self.refresh_document_colors(for_buffer, window, cx);
25734 self.refresh_folding_ranges(for_buffer, window, cx);
25735 self.refresh_document_symbols(for_buffer, cx);
25736 }
25737
25738 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25739 if !self.lsp_data_enabled() {
25740 return;
25741 }
25742 let visible_buffers: Vec<_> = self
25743 .visible_buffers(cx)
25744 .into_iter()
25745 .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
25746 .collect();
25747 for visible_buffer in visible_buffers {
25748 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25749 }
25750 }
25751
25752 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25753 if !self.lsp_data_enabled() {
25754 return;
25755 }
25756
25757 if !self.registered_buffers.contains_key(&buffer_id)
25758 && let Some(project) = self.project.as_ref()
25759 {
25760 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25761 project.update(cx, |project, cx| {
25762 self.registered_buffers.insert(
25763 buffer_id,
25764 project.register_buffer_with_language_servers(&buffer, cx),
25765 );
25766 });
25767 } else {
25768 self.registered_buffers.remove(&buffer_id);
25769 }
25770 }
25771 }
25772
25773 fn create_style(&self, cx: &App) -> EditorStyle {
25774 let settings = ThemeSettings::get_global(cx);
25775
25776 let mut text_style = match self.mode {
25777 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25778 color: cx.theme().colors().editor_foreground,
25779 font_family: settings.ui_font.family.clone(),
25780 font_features: settings.ui_font.features.clone(),
25781 font_fallbacks: settings.ui_font.fallbacks.clone(),
25782 font_size: rems(0.875).into(),
25783 font_weight: settings.ui_font.weight,
25784 line_height: relative(settings.buffer_line_height.value()),
25785 ..Default::default()
25786 },
25787 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25788 color: cx.theme().colors().editor_foreground,
25789 font_family: settings.buffer_font.family.clone(),
25790 font_features: settings.buffer_font.features.clone(),
25791 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25792 font_size: settings.buffer_font_size(cx).into(),
25793 font_weight: settings.buffer_font.weight,
25794 line_height: relative(settings.buffer_line_height.value()),
25795 ..Default::default()
25796 },
25797 };
25798 if let Some(text_style_refinement) = &self.text_style_refinement {
25799 text_style.refine(text_style_refinement)
25800 }
25801
25802 let background = match self.mode {
25803 EditorMode::SingleLine => cx.theme().system().transparent,
25804 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25805 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25806 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25807 };
25808
25809 EditorStyle {
25810 background,
25811 border: cx.theme().colors().border,
25812 local_player: cx.theme().players().local(),
25813 text: text_style,
25814 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25815 syntax: cx.theme().syntax().clone(),
25816 status: cx.theme().status().clone(),
25817 inlay_hints_style: make_inlay_hints_style(cx),
25818 edit_prediction_styles: make_suggestion_styles(cx),
25819 unnecessary_code_fade: settings.unnecessary_code_fade,
25820 show_underlines: self.diagnostics_enabled(),
25821 }
25822 }
25823
25824 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
25825 let multibuffer = self.buffer().read(cx);
25826 let is_singleton = multibuffer.is_singleton();
25827 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25828 let buffer = multibuffer.buffer(*buffer_id)?;
25829
25830 let buffer = buffer.read(cx);
25831 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25832 let mut breadcrumbs = if is_singleton {
25833 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25834 buffer
25835 .snapshot()
25836 .resolve_file_path(
25837 self.project
25838 .as_ref()
25839 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25840 .unwrap_or_default(),
25841 cx,
25842 )
25843 .unwrap_or_else(|| {
25844 if multibuffer.is_singleton() {
25845 multibuffer.title(cx).to_string()
25846 } else {
25847 "untitled".to_string()
25848 }
25849 })
25850 });
25851 vec![HighlightedText {
25852 text: text.into(),
25853 highlights: vec![],
25854 }]
25855 } else {
25856 vec![]
25857 };
25858
25859 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
25860 text: symbol.text.clone().into(),
25861 highlights: symbol.highlight_ranges.clone(),
25862 }));
25863 Some(breadcrumbs)
25864 }
25865
25866 fn disable_lsp_data(&mut self) {
25867 self.enable_lsp_data = false;
25868 }
25869
25870 fn disable_runnables(&mut self) {
25871 self.enable_runnables = false;
25872 }
25873
25874 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
25875 self.register_visible_buffers(cx);
25876 self.colorize_brackets(false, cx);
25877 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
25878 if !self.buffer().read(cx).is_singleton() {
25879 self.update_lsp_data(None, window, cx);
25880 self.refresh_runnables(None, window, cx);
25881 }
25882 }
25883}
25884
25885fn edit_for_markdown_paste<'a>(
25886 buffer: &MultiBufferSnapshot,
25887 range: Range<MultiBufferOffset>,
25888 to_insert: &'a str,
25889 url: Option<url::Url>,
25890) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25891 if url.is_none() {
25892 return (range, Cow::Borrowed(to_insert));
25893 };
25894
25895 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25896
25897 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25898 Cow::Borrowed(to_insert)
25899 } else {
25900 Cow::Owned(format!("[{old_text}]({to_insert})"))
25901 };
25902 (range, new_text)
25903}
25904
25905fn process_completion_for_edit(
25906 completion: &Completion,
25907 intent: CompletionIntent,
25908 buffer: &Entity<Buffer>,
25909 cursor_position: &text::Anchor,
25910 cx: &mut Context<Editor>,
25911) -> CompletionEdit {
25912 let buffer = buffer.read(cx);
25913 let buffer_snapshot = buffer.snapshot();
25914 let (snippet, new_text) = if completion.is_snippet() {
25915 let mut snippet_source = completion.new_text.clone();
25916 // Workaround for typescript language server issues so that methods don't expand within
25917 // strings and functions with type expressions. The previous point is used because the query
25918 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25919 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25920 let previous_point = if previous_point.column > 0 {
25921 cursor_position.to_previous_offset(&buffer_snapshot)
25922 } else {
25923 cursor_position.to_offset(&buffer_snapshot)
25924 };
25925 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25926 && scope.prefers_label_for_snippet_in_completion()
25927 && let Some(label) = completion.label()
25928 && matches!(
25929 completion.kind(),
25930 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25931 )
25932 {
25933 snippet_source = label;
25934 }
25935 match Snippet::parse(&snippet_source).log_err() {
25936 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25937 None => (None, completion.new_text.clone()),
25938 }
25939 } else {
25940 (None, completion.new_text.clone())
25941 };
25942
25943 let mut range_to_replace = {
25944 let replace_range = &completion.replace_range;
25945 if let CompletionSource::Lsp {
25946 insert_range: Some(insert_range),
25947 ..
25948 } = &completion.source
25949 {
25950 debug_assert_eq!(
25951 insert_range.start, replace_range.start,
25952 "insert_range and replace_range should start at the same position"
25953 );
25954 debug_assert!(
25955 insert_range
25956 .start
25957 .cmp(cursor_position, &buffer_snapshot)
25958 .is_le(),
25959 "insert_range should start before or at cursor position"
25960 );
25961 debug_assert!(
25962 replace_range
25963 .start
25964 .cmp(cursor_position, &buffer_snapshot)
25965 .is_le(),
25966 "replace_range should start before or at cursor position"
25967 );
25968
25969 let should_replace = match intent {
25970 CompletionIntent::CompleteWithInsert => false,
25971 CompletionIntent::CompleteWithReplace => true,
25972 CompletionIntent::Complete | CompletionIntent::Compose => {
25973 let insert_mode = LanguageSettings::for_buffer(&buffer, cx)
25974 .completions
25975 .lsp_insert_mode;
25976 match insert_mode {
25977 LspInsertMode::Insert => false,
25978 LspInsertMode::Replace => true,
25979 LspInsertMode::ReplaceSubsequence => {
25980 let mut text_to_replace = buffer.chars_for_range(
25981 buffer.anchor_before(replace_range.start)
25982 ..buffer.anchor_after(replace_range.end),
25983 );
25984 let mut current_needle = text_to_replace.next();
25985 for haystack_ch in completion.label.text.chars() {
25986 if let Some(needle_ch) = current_needle
25987 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
25988 {
25989 current_needle = text_to_replace.next();
25990 }
25991 }
25992 current_needle.is_none()
25993 }
25994 LspInsertMode::ReplaceSuffix => {
25995 if replace_range
25996 .end
25997 .cmp(cursor_position, &buffer_snapshot)
25998 .is_gt()
25999 {
26000 let range_after_cursor = *cursor_position..replace_range.end;
26001 let text_after_cursor = buffer
26002 .text_for_range(
26003 buffer.anchor_before(range_after_cursor.start)
26004 ..buffer.anchor_after(range_after_cursor.end),
26005 )
26006 .collect::<String>()
26007 .to_ascii_lowercase();
26008 completion
26009 .label
26010 .text
26011 .to_ascii_lowercase()
26012 .ends_with(&text_after_cursor)
26013 } else {
26014 true
26015 }
26016 }
26017 }
26018 }
26019 };
26020
26021 if should_replace {
26022 replace_range.clone()
26023 } else {
26024 insert_range.clone()
26025 }
26026 } else {
26027 replace_range.clone()
26028 }
26029 };
26030
26031 if range_to_replace
26032 .end
26033 .cmp(cursor_position, &buffer_snapshot)
26034 .is_lt()
26035 {
26036 range_to_replace.end = *cursor_position;
26037 }
26038
26039 CompletionEdit {
26040 new_text,
26041 replace_range: range_to_replace,
26042 snippet,
26043 }
26044}
26045
26046struct CompletionEdit {
26047 new_text: String,
26048 replace_range: Range<text::Anchor>,
26049 snippet: Option<Snippet>,
26050}
26051
26052fn comment_delimiter_for_newline(
26053 start_point: &Point,
26054 buffer: &MultiBufferSnapshot,
26055 language: &LanguageScope,
26056) -> Option<Arc<str>> {
26057 let delimiters = language.line_comment_prefixes();
26058 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
26059 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26060
26061 let num_of_whitespaces = snapshot
26062 .chars_for_range(range.clone())
26063 .take_while(|c| c.is_whitespace())
26064 .count();
26065 let comment_candidate = snapshot
26066 .chars_for_range(range.clone())
26067 .skip(num_of_whitespaces)
26068 .take(max_len_of_delimiter + 2)
26069 .collect::<String>();
26070 let (delimiter, trimmed_len, is_repl) = delimiters
26071 .iter()
26072 .filter_map(|delimiter| {
26073 let prefix = delimiter.trim_end();
26074 if comment_candidate.starts_with(prefix) {
26075 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
26076 {
26077 stripped_comment.starts_with(" %%")
26078 } else {
26079 false
26080 };
26081 Some((delimiter, prefix.len(), is_repl))
26082 } else {
26083 None
26084 }
26085 })
26086 .max_by_key(|(_, len, _)| *len)?;
26087
26088 if let Some(BlockCommentConfig {
26089 start: block_start, ..
26090 }) = language.block_comment()
26091 {
26092 let block_start_trimmed = block_start.trim_end();
26093 if block_start_trimmed.starts_with(delimiter.trim_end()) {
26094 let line_content = snapshot
26095 .chars_for_range(range.clone())
26096 .skip(num_of_whitespaces)
26097 .take(block_start_trimmed.len())
26098 .collect::<String>();
26099
26100 if line_content.starts_with(block_start_trimmed) {
26101 return None;
26102 }
26103 }
26104 }
26105
26106 let cursor_is_placed_after_comment_marker =
26107 num_of_whitespaces + trimmed_len <= start_point.column as usize;
26108 if cursor_is_placed_after_comment_marker {
26109 if !is_repl {
26110 return Some(delimiter.clone());
26111 }
26112
26113 let line_content_after_cursor: String = snapshot
26114 .chars_for_range(range)
26115 .skip(start_point.column as usize)
26116 .collect();
26117
26118 if line_content_after_cursor.trim().is_empty() {
26119 return None;
26120 } else {
26121 return Some(delimiter.clone());
26122 }
26123 } else {
26124 None
26125 }
26126}
26127
26128fn documentation_delimiter_for_newline(
26129 start_point: &Point,
26130 buffer: &MultiBufferSnapshot,
26131 language: &LanguageScope,
26132 newline_config: &mut NewlineConfig,
26133) -> Option<Arc<str>> {
26134 let BlockCommentConfig {
26135 start: start_tag,
26136 end: end_tag,
26137 prefix: delimiter,
26138 tab_size: len,
26139 } = language.documentation_comment()?;
26140 let is_within_block_comment = buffer
26141 .language_scope_at(*start_point)
26142 .is_some_and(|scope| scope.override_name() == Some("comment"));
26143 if !is_within_block_comment {
26144 return None;
26145 }
26146
26147 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26148
26149 let num_of_whitespaces = snapshot
26150 .chars_for_range(range.clone())
26151 .take_while(|c| c.is_whitespace())
26152 .count();
26153
26154 // 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.
26155 let column = start_point.column;
26156 let cursor_is_after_start_tag = {
26157 let start_tag_len = start_tag.len();
26158 let start_tag_line = snapshot
26159 .chars_for_range(range.clone())
26160 .skip(num_of_whitespaces)
26161 .take(start_tag_len)
26162 .collect::<String>();
26163 if start_tag_line.starts_with(start_tag.as_ref()) {
26164 num_of_whitespaces + start_tag_len <= column as usize
26165 } else {
26166 false
26167 }
26168 };
26169
26170 let cursor_is_after_delimiter = {
26171 let delimiter_trim = delimiter.trim_end();
26172 let delimiter_line = snapshot
26173 .chars_for_range(range.clone())
26174 .skip(num_of_whitespaces)
26175 .take(delimiter_trim.len())
26176 .collect::<String>();
26177 if delimiter_line.starts_with(delimiter_trim) {
26178 num_of_whitespaces + delimiter_trim.len() <= column as usize
26179 } else {
26180 false
26181 }
26182 };
26183
26184 let mut needs_extra_line = false;
26185 let mut extra_line_additional_indent = IndentSize::spaces(0);
26186
26187 let cursor_is_before_end_tag_if_exists = {
26188 let mut char_position = 0u32;
26189 let mut end_tag_offset = None;
26190
26191 'outer: for chunk in snapshot.text_for_range(range) {
26192 if let Some(byte_pos) = chunk.find(&**end_tag) {
26193 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26194 end_tag_offset = Some(char_position + chars_before_match);
26195 break 'outer;
26196 }
26197 char_position += chunk.chars().count() as u32;
26198 }
26199
26200 if let Some(end_tag_offset) = end_tag_offset {
26201 let cursor_is_before_end_tag = column <= end_tag_offset;
26202 if cursor_is_after_start_tag {
26203 if cursor_is_before_end_tag {
26204 needs_extra_line = true;
26205 }
26206 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26207 if cursor_is_at_start_of_end_tag {
26208 extra_line_additional_indent.len = *len;
26209 }
26210 }
26211 cursor_is_before_end_tag
26212 } else {
26213 true
26214 }
26215 };
26216
26217 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26218 && cursor_is_before_end_tag_if_exists
26219 {
26220 let additional_indent = if cursor_is_after_start_tag {
26221 IndentSize::spaces(*len)
26222 } else {
26223 IndentSize::spaces(0)
26224 };
26225
26226 *newline_config = NewlineConfig::Newline {
26227 additional_indent,
26228 extra_line_additional_indent: if needs_extra_line {
26229 Some(extra_line_additional_indent)
26230 } else {
26231 None
26232 },
26233 prevent_auto_indent: true,
26234 };
26235 Some(delimiter.clone())
26236 } else {
26237 None
26238 }
26239}
26240
26241const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26242
26243fn list_delimiter_for_newline(
26244 start_point: &Point,
26245 buffer: &MultiBufferSnapshot,
26246 language: &LanguageScope,
26247 newline_config: &mut NewlineConfig,
26248) -> Option<Arc<str>> {
26249 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26250
26251 let num_of_whitespaces = snapshot
26252 .chars_for_range(range.clone())
26253 .take_while(|c| c.is_whitespace())
26254 .count();
26255
26256 let task_list_entries: Vec<_> = language
26257 .task_list()
26258 .into_iter()
26259 .flat_map(|config| {
26260 config
26261 .prefixes
26262 .iter()
26263 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26264 })
26265 .collect();
26266 let unordered_list_entries: Vec<_> = language
26267 .unordered_list()
26268 .iter()
26269 .map(|marker| (marker.as_ref(), marker.as_ref()))
26270 .collect();
26271
26272 let all_entries: Vec<_> = task_list_entries
26273 .into_iter()
26274 .chain(unordered_list_entries)
26275 .collect();
26276
26277 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26278 let candidate: String = snapshot
26279 .chars_for_range(range.clone())
26280 .skip(num_of_whitespaces)
26281 .take(max_prefix_len)
26282 .collect();
26283
26284 if let Some((prefix, continuation)) = all_entries
26285 .iter()
26286 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26287 .max_by_key(|(prefix, _)| prefix.len())
26288 {
26289 let end_of_prefix = num_of_whitespaces + prefix.len();
26290 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26291 let has_content_after_marker = snapshot
26292 .chars_for_range(range)
26293 .skip(end_of_prefix)
26294 .any(|c| !c.is_whitespace());
26295
26296 if has_content_after_marker && cursor_is_after_prefix {
26297 return Some((*continuation).into());
26298 }
26299
26300 if start_point.column as usize == end_of_prefix {
26301 if num_of_whitespaces == 0 {
26302 *newline_config = NewlineConfig::ClearCurrentLine;
26303 } else {
26304 *newline_config = NewlineConfig::UnindentCurrentLine {
26305 continuation: (*continuation).into(),
26306 };
26307 }
26308 }
26309
26310 return None;
26311 }
26312 }
26313
26314 let candidate: String = snapshot
26315 .chars_for_range(range.clone())
26316 .skip(num_of_whitespaces)
26317 .take(ORDERED_LIST_MAX_MARKER_LEN)
26318 .collect();
26319
26320 for ordered_config in language.ordered_list() {
26321 let regex = match Regex::new(&ordered_config.pattern) {
26322 Ok(r) => r,
26323 Err(_) => continue,
26324 };
26325
26326 if let Some(captures) = regex.captures(&candidate) {
26327 let full_match = captures.get(0)?;
26328 let marker_len = full_match.len();
26329 let end_of_prefix = num_of_whitespaces + marker_len;
26330 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26331
26332 let has_content_after_marker = snapshot
26333 .chars_for_range(range)
26334 .skip(end_of_prefix)
26335 .any(|c| !c.is_whitespace());
26336
26337 if has_content_after_marker && cursor_is_after_prefix {
26338 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26339 let continuation = ordered_config
26340 .format
26341 .replace("{1}", &(number + 1).to_string());
26342 return Some(continuation.into());
26343 }
26344
26345 if start_point.column as usize == end_of_prefix {
26346 let continuation = ordered_config.format.replace("{1}", "1");
26347 if num_of_whitespaces == 0 {
26348 *newline_config = NewlineConfig::ClearCurrentLine;
26349 } else {
26350 *newline_config = NewlineConfig::UnindentCurrentLine {
26351 continuation: continuation.into(),
26352 };
26353 }
26354 }
26355
26356 return None;
26357 }
26358 }
26359
26360 None
26361}
26362
26363fn is_list_prefix_row(
26364 row: MultiBufferRow,
26365 buffer: &MultiBufferSnapshot,
26366 language: &LanguageScope,
26367) -> bool {
26368 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26369 return false;
26370 };
26371
26372 let num_of_whitespaces = snapshot
26373 .chars_for_range(range.clone())
26374 .take_while(|c| c.is_whitespace())
26375 .count();
26376
26377 let task_list_prefixes: Vec<_> = language
26378 .task_list()
26379 .into_iter()
26380 .flat_map(|config| {
26381 config
26382 .prefixes
26383 .iter()
26384 .map(|p| p.as_ref())
26385 .collect::<Vec<_>>()
26386 })
26387 .collect();
26388 let unordered_list_markers: Vec<_> = language
26389 .unordered_list()
26390 .iter()
26391 .map(|marker| marker.as_ref())
26392 .collect();
26393 let all_prefixes: Vec<_> = task_list_prefixes
26394 .into_iter()
26395 .chain(unordered_list_markers)
26396 .collect();
26397 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26398 let candidate: String = snapshot
26399 .chars_for_range(range.clone())
26400 .skip(num_of_whitespaces)
26401 .take(max_prefix_len)
26402 .collect();
26403 if all_prefixes
26404 .iter()
26405 .any(|prefix| candidate.starts_with(*prefix))
26406 {
26407 return true;
26408 }
26409 }
26410
26411 let ordered_list_candidate: String = snapshot
26412 .chars_for_range(range)
26413 .skip(num_of_whitespaces)
26414 .take(ORDERED_LIST_MAX_MARKER_LEN)
26415 .collect();
26416 for ordered_config in language.ordered_list() {
26417 let regex = match Regex::new(&ordered_config.pattern) {
26418 Ok(r) => r,
26419 Err(_) => continue,
26420 };
26421 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26422 return captures.get(0).is_some();
26423 }
26424 }
26425
26426 false
26427}
26428
26429#[derive(Debug)]
26430enum NewlineConfig {
26431 /// Insert newline with optional additional indent and optional extra blank line
26432 Newline {
26433 additional_indent: IndentSize,
26434 extra_line_additional_indent: Option<IndentSize>,
26435 prevent_auto_indent: bool,
26436 },
26437 /// Clear the current line
26438 ClearCurrentLine,
26439 /// Unindent the current line and add continuation
26440 UnindentCurrentLine { continuation: Arc<str> },
26441}
26442
26443impl NewlineConfig {
26444 fn has_extra_line(&self) -> bool {
26445 matches!(
26446 self,
26447 Self::Newline {
26448 extra_line_additional_indent: Some(_),
26449 ..
26450 }
26451 )
26452 }
26453
26454 fn insert_extra_newline_brackets(
26455 buffer: &MultiBufferSnapshot,
26456 range: Range<MultiBufferOffset>,
26457 language: &language::LanguageScope,
26458 ) -> bool {
26459 let leading_whitespace_len = buffer
26460 .reversed_chars_at(range.start)
26461 .take_while(|c| c.is_whitespace() && *c != '\n')
26462 .map(|c| c.len_utf8())
26463 .sum::<usize>();
26464 let trailing_whitespace_len = buffer
26465 .chars_at(range.end)
26466 .take_while(|c| c.is_whitespace() && *c != '\n')
26467 .map(|c| c.len_utf8())
26468 .sum::<usize>();
26469 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26470
26471 language.brackets().any(|(pair, enabled)| {
26472 let pair_start = pair.start.trim_end();
26473 let pair_end = pair.end.trim_start();
26474
26475 enabled
26476 && pair.newline
26477 && buffer.contains_str_at(range.end, pair_end)
26478 && buffer.contains_str_at(
26479 range.start.saturating_sub_usize(pair_start.len()),
26480 pair_start,
26481 )
26482 })
26483 }
26484
26485 fn insert_extra_newline_tree_sitter(
26486 buffer: &MultiBufferSnapshot,
26487 range: Range<MultiBufferOffset>,
26488 ) -> bool {
26489 let (buffer, range) = match buffer
26490 .range_to_buffer_ranges(range.start..range.end)
26491 .as_slice()
26492 {
26493 [(buffer_snapshot, range, _)] => (buffer_snapshot.clone(), range.clone()),
26494 _ => return false,
26495 };
26496 let pair = {
26497 let mut result: Option<BracketMatch<usize>> = None;
26498
26499 for pair in buffer
26500 .all_bracket_ranges(range.start.0..range.end.0)
26501 .filter(move |pair| {
26502 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26503 })
26504 {
26505 let len = pair.close_range.end - pair.open_range.start;
26506
26507 if let Some(existing) = &result {
26508 let existing_len = existing.close_range.end - existing.open_range.start;
26509 if len > existing_len {
26510 continue;
26511 }
26512 }
26513
26514 result = Some(pair);
26515 }
26516
26517 result
26518 };
26519 let Some(pair) = pair else {
26520 return false;
26521 };
26522 pair.newline_only
26523 && buffer
26524 .chars_for_range(pair.open_range.end..range.start.0)
26525 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26526 .all(|c| c.is_whitespace() && c != '\n')
26527 }
26528}
26529
26530fn update_uncommitted_diff_for_buffer(
26531 editor: Entity<Editor>,
26532 project: &Entity<Project>,
26533 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26534 buffer: Entity<MultiBuffer>,
26535 cx: &mut App,
26536) -> Task<()> {
26537 let mut tasks = Vec::new();
26538 project.update(cx, |project, cx| {
26539 for buffer in buffers {
26540 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26541 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26542 }
26543 }
26544 });
26545 cx.spawn(async move |cx| {
26546 let diffs = future::join_all(tasks).await;
26547 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26548 return;
26549 }
26550
26551 buffer.update(cx, |buffer, cx| {
26552 for diff in diffs.into_iter().flatten() {
26553 buffer.add_diff(diff, cx);
26554 }
26555 });
26556 })
26557}
26558
26559fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26560 let tab_size = tab_size.get() as usize;
26561 let mut width = offset;
26562
26563 for ch in text.chars() {
26564 width += if ch == '\t' {
26565 tab_size - (width % tab_size)
26566 } else {
26567 1
26568 };
26569 }
26570
26571 width - offset
26572}
26573
26574#[cfg(test)]
26575mod tests {
26576 use super::*;
26577
26578 #[test]
26579 fn test_string_size_with_expanded_tabs() {
26580 let nz = |val| NonZeroU32::new(val).unwrap();
26581 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26582 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26583 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26584 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26585 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26586 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26587 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26588 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26589 }
26590}
26591
26592/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26593struct WordBreakingTokenizer<'a> {
26594 input: &'a str,
26595}
26596
26597impl<'a> WordBreakingTokenizer<'a> {
26598 fn new(input: &'a str) -> Self {
26599 Self { input }
26600 }
26601}
26602
26603fn is_char_ideographic(ch: char) -> bool {
26604 use unicode_script::Script::*;
26605 use unicode_script::UnicodeScript;
26606 matches!(ch.script(), Han | Tangut | Yi)
26607}
26608
26609fn is_grapheme_ideographic(text: &str) -> bool {
26610 text.chars().any(is_char_ideographic)
26611}
26612
26613fn is_grapheme_whitespace(text: &str) -> bool {
26614 text.chars().any(|x| x.is_whitespace())
26615}
26616
26617fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26618 text.chars()
26619 .next()
26620 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26621}
26622
26623#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26624enum WordBreakToken<'a> {
26625 Word { token: &'a str, grapheme_len: usize },
26626 InlineWhitespace { token: &'a str, grapheme_len: usize },
26627 Newline,
26628}
26629
26630impl<'a> Iterator for WordBreakingTokenizer<'a> {
26631 /// Yields a span, the count of graphemes in the token, and whether it was
26632 /// whitespace. Note that it also breaks at word boundaries.
26633 type Item = WordBreakToken<'a>;
26634
26635 fn next(&mut self) -> Option<Self::Item> {
26636 use unicode_segmentation::UnicodeSegmentation;
26637 if self.input.is_empty() {
26638 return None;
26639 }
26640
26641 let mut iter = self.input.graphemes(true).peekable();
26642 let mut offset = 0;
26643 let mut grapheme_len = 0;
26644 if let Some(first_grapheme) = iter.next() {
26645 let is_newline = first_grapheme == "\n";
26646 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26647 offset += first_grapheme.len();
26648 grapheme_len += 1;
26649 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26650 if let Some(grapheme) = iter.peek().copied()
26651 && should_stay_with_preceding_ideograph(grapheme)
26652 {
26653 offset += grapheme.len();
26654 grapheme_len += 1;
26655 }
26656 } else {
26657 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26658 let mut next_word_bound = words.peek().copied();
26659 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26660 next_word_bound = words.next();
26661 }
26662 while let Some(grapheme) = iter.peek().copied() {
26663 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26664 break;
26665 };
26666 if is_grapheme_whitespace(grapheme) != is_whitespace
26667 || (grapheme == "\n") != is_newline
26668 {
26669 break;
26670 };
26671 offset += grapheme.len();
26672 grapheme_len += 1;
26673 iter.next();
26674 }
26675 }
26676 let token = &self.input[..offset];
26677 self.input = &self.input[offset..];
26678 if token == "\n" {
26679 Some(WordBreakToken::Newline)
26680 } else if is_whitespace {
26681 Some(WordBreakToken::InlineWhitespace {
26682 token,
26683 grapheme_len,
26684 })
26685 } else {
26686 Some(WordBreakToken::Word {
26687 token,
26688 grapheme_len,
26689 })
26690 }
26691 } else {
26692 None
26693 }
26694 }
26695}
26696
26697#[test]
26698fn test_word_breaking_tokenizer() {
26699 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26700 ("", &[]),
26701 (" ", &[whitespace(" ", 2)]),
26702 ("Ʒ", &[word("Ʒ", 1)]),
26703 ("Ǽ", &[word("Ǽ", 1)]),
26704 ("⋑", &[word("⋑", 1)]),
26705 ("⋑⋑", &[word("⋑⋑", 2)]),
26706 (
26707 "原理,进而",
26708 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26709 ),
26710 (
26711 "hello world",
26712 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26713 ),
26714 (
26715 "hello, world",
26716 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26717 ),
26718 (
26719 " hello world",
26720 &[
26721 whitespace(" ", 2),
26722 word("hello", 5),
26723 whitespace(" ", 1),
26724 word("world", 5),
26725 ],
26726 ),
26727 (
26728 "这是什么 \n 钢笔",
26729 &[
26730 word("这", 1),
26731 word("是", 1),
26732 word("什", 1),
26733 word("么", 1),
26734 whitespace(" ", 1),
26735 newline(),
26736 whitespace(" ", 1),
26737 word("钢", 1),
26738 word("笔", 1),
26739 ],
26740 ),
26741 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26742 ];
26743
26744 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26745 WordBreakToken::Word {
26746 token,
26747 grapheme_len,
26748 }
26749 }
26750
26751 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26752 WordBreakToken::InlineWhitespace {
26753 token,
26754 grapheme_len,
26755 }
26756 }
26757
26758 fn newline() -> WordBreakToken<'static> {
26759 WordBreakToken::Newline
26760 }
26761
26762 for (input, result) in tests {
26763 assert_eq!(
26764 WordBreakingTokenizer::new(input)
26765 .collect::<Vec<_>>()
26766 .as_slice(),
26767 *result,
26768 );
26769 }
26770}
26771
26772fn wrap_with_prefix(
26773 first_line_prefix: String,
26774 subsequent_lines_prefix: String,
26775 unwrapped_text: String,
26776 wrap_column: usize,
26777 tab_size: NonZeroU32,
26778 preserve_existing_whitespace: bool,
26779) -> String {
26780 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26781 let subsequent_lines_prefix_len =
26782 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26783 let mut wrapped_text = String::new();
26784 let mut current_line = first_line_prefix;
26785 let mut is_first_line = true;
26786
26787 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26788 let mut current_line_len = first_line_prefix_len;
26789 let mut in_whitespace = false;
26790 for token in tokenizer {
26791 let have_preceding_whitespace = in_whitespace;
26792 match token {
26793 WordBreakToken::Word {
26794 token,
26795 grapheme_len,
26796 } => {
26797 in_whitespace = false;
26798 let current_prefix_len = if is_first_line {
26799 first_line_prefix_len
26800 } else {
26801 subsequent_lines_prefix_len
26802 };
26803 if current_line_len + grapheme_len > wrap_column
26804 && current_line_len != current_prefix_len
26805 {
26806 wrapped_text.push_str(current_line.trim_end());
26807 wrapped_text.push('\n');
26808 is_first_line = false;
26809 current_line = subsequent_lines_prefix.clone();
26810 current_line_len = subsequent_lines_prefix_len;
26811 }
26812 current_line.push_str(token);
26813 current_line_len += grapheme_len;
26814 }
26815 WordBreakToken::InlineWhitespace {
26816 mut token,
26817 mut grapheme_len,
26818 } => {
26819 in_whitespace = true;
26820 if have_preceding_whitespace && !preserve_existing_whitespace {
26821 continue;
26822 }
26823 if !preserve_existing_whitespace {
26824 // Keep a single whitespace grapheme as-is
26825 if let Some(first) =
26826 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26827 {
26828 token = first;
26829 } else {
26830 token = " ";
26831 }
26832 grapheme_len = 1;
26833 }
26834 let current_prefix_len = if is_first_line {
26835 first_line_prefix_len
26836 } else {
26837 subsequent_lines_prefix_len
26838 };
26839 if current_line_len + grapheme_len > wrap_column {
26840 wrapped_text.push_str(current_line.trim_end());
26841 wrapped_text.push('\n');
26842 is_first_line = false;
26843 current_line = subsequent_lines_prefix.clone();
26844 current_line_len = subsequent_lines_prefix_len;
26845 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26846 current_line.push_str(token);
26847 current_line_len += grapheme_len;
26848 }
26849 }
26850 WordBreakToken::Newline => {
26851 in_whitespace = true;
26852 let current_prefix_len = if is_first_line {
26853 first_line_prefix_len
26854 } else {
26855 subsequent_lines_prefix_len
26856 };
26857 if preserve_existing_whitespace {
26858 wrapped_text.push_str(current_line.trim_end());
26859 wrapped_text.push('\n');
26860 is_first_line = false;
26861 current_line = subsequent_lines_prefix.clone();
26862 current_line_len = subsequent_lines_prefix_len;
26863 } else if have_preceding_whitespace {
26864 continue;
26865 } else if current_line_len + 1 > wrap_column
26866 && current_line_len != current_prefix_len
26867 {
26868 wrapped_text.push_str(current_line.trim_end());
26869 wrapped_text.push('\n');
26870 is_first_line = false;
26871 current_line = subsequent_lines_prefix.clone();
26872 current_line_len = subsequent_lines_prefix_len;
26873 } else if current_line_len != current_prefix_len {
26874 current_line.push(' ');
26875 current_line_len += 1;
26876 }
26877 }
26878 }
26879 }
26880
26881 if !current_line.is_empty() {
26882 wrapped_text.push_str(¤t_line);
26883 }
26884 wrapped_text
26885}
26886
26887#[test]
26888fn test_wrap_with_prefix() {
26889 assert_eq!(
26890 wrap_with_prefix(
26891 "# ".to_string(),
26892 "# ".to_string(),
26893 "abcdefg".to_string(),
26894 4,
26895 NonZeroU32::new(4).unwrap(),
26896 false,
26897 ),
26898 "# abcdefg"
26899 );
26900 assert_eq!(
26901 wrap_with_prefix(
26902 "".to_string(),
26903 "".to_string(),
26904 "\thello world".to_string(),
26905 8,
26906 NonZeroU32::new(4).unwrap(),
26907 false,
26908 ),
26909 "hello\nworld"
26910 );
26911 assert_eq!(
26912 wrap_with_prefix(
26913 "// ".to_string(),
26914 "// ".to_string(),
26915 "xx \nyy zz aa bb cc".to_string(),
26916 12,
26917 NonZeroU32::new(4).unwrap(),
26918 false,
26919 ),
26920 "// xx yy zz\n// aa bb cc"
26921 );
26922 assert_eq!(
26923 wrap_with_prefix(
26924 String::new(),
26925 String::new(),
26926 "这是什么 \n 钢笔".to_string(),
26927 3,
26928 NonZeroU32::new(4).unwrap(),
26929 false,
26930 ),
26931 "这是什\n么 钢\n笔"
26932 );
26933 assert_eq!(
26934 wrap_with_prefix(
26935 String::new(),
26936 String::new(),
26937 format!("foo{}bar", '\u{2009}'), // thin space
26938 80,
26939 NonZeroU32::new(4).unwrap(),
26940 false,
26941 ),
26942 format!("foo{}bar", '\u{2009}')
26943 );
26944}
26945
26946pub trait CollaborationHub {
26947 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26948 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26949 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26950}
26951
26952impl CollaborationHub for Entity<Project> {
26953 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26954 self.read(cx).collaborators()
26955 }
26956
26957 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26958 self.read(cx).user_store().read(cx).participant_indices()
26959 }
26960
26961 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26962 let this = self.read(cx);
26963 let user_ids = this.collaborators().values().map(|c| c.user_id);
26964 this.user_store().read(cx).participant_names(user_ids, cx)
26965 }
26966}
26967
26968pub trait SemanticsProvider {
26969 fn hover(
26970 &self,
26971 buffer: &Entity<Buffer>,
26972 position: text::Anchor,
26973 cx: &mut App,
26974 ) -> Option<Task<Option<Vec<project::Hover>>>>;
26975
26976 fn inline_values(
26977 &self,
26978 buffer_handle: Entity<Buffer>,
26979 range: Range<text::Anchor>,
26980 cx: &mut App,
26981 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
26982
26983 fn applicable_inlay_chunks(
26984 &self,
26985 buffer: &Entity<Buffer>,
26986 ranges: &[Range<text::Anchor>],
26987 cx: &mut App,
26988 ) -> Vec<Range<BufferRow>>;
26989
26990 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
26991
26992 fn inlay_hints(
26993 &self,
26994 invalidate: InvalidationStrategy,
26995 buffer: Entity<Buffer>,
26996 ranges: Vec<Range<text::Anchor>>,
26997 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
26998 cx: &mut App,
26999 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
27000
27001 fn semantic_tokens(
27002 &self,
27003 buffer: Entity<Buffer>,
27004 refresh: Option<RefreshForServer>,
27005 cx: &mut App,
27006 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
27007
27008 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27009
27010 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27011
27012 fn document_highlights(
27013 &self,
27014 buffer: &Entity<Buffer>,
27015 position: text::Anchor,
27016 cx: &mut App,
27017 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
27018
27019 fn definitions(
27020 &self,
27021 buffer: &Entity<Buffer>,
27022 position: text::Anchor,
27023 kind: GotoDefinitionKind,
27024 cx: &mut App,
27025 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
27026
27027 fn range_for_rename(
27028 &self,
27029 buffer: &Entity<Buffer>,
27030 position: text::Anchor,
27031 cx: &mut App,
27032 ) -> Task<Result<Option<Range<text::Anchor>>>>;
27033
27034 fn perform_rename(
27035 &self,
27036 buffer: &Entity<Buffer>,
27037 position: text::Anchor,
27038 new_name: String,
27039 cx: &mut App,
27040 ) -> Option<Task<Result<ProjectTransaction>>>;
27041}
27042
27043pub trait CompletionProvider {
27044 fn completions(
27045 &self,
27046 buffer: &Entity<Buffer>,
27047 buffer_position: text::Anchor,
27048 trigger: CompletionContext,
27049 window: &mut Window,
27050 cx: &mut Context<Editor>,
27051 ) -> Task<Result<Vec<CompletionResponse>>>;
27052
27053 fn resolve_completions(
27054 &self,
27055 _buffer: Entity<Buffer>,
27056 _completion_indices: Vec<usize>,
27057 _completions: Rc<RefCell<Box<[Completion]>>>,
27058 _cx: &mut Context<Editor>,
27059 ) -> Task<Result<bool>> {
27060 Task::ready(Ok(false))
27061 }
27062
27063 fn apply_additional_edits_for_completion(
27064 &self,
27065 _buffer: Entity<Buffer>,
27066 _completions: Rc<RefCell<Box<[Completion]>>>,
27067 _completion_index: usize,
27068 _push_to_history: bool,
27069 _all_commit_ranges: Vec<Range<language::Anchor>>,
27070 _cx: &mut Context<Editor>,
27071 ) -> Task<Result<Option<language::Transaction>>> {
27072 Task::ready(Ok(None))
27073 }
27074
27075 fn is_completion_trigger(
27076 &self,
27077 buffer: &Entity<Buffer>,
27078 position: language::Anchor,
27079 text: &str,
27080 trigger_in_words: bool,
27081 cx: &mut Context<Editor>,
27082 ) -> bool;
27083
27084 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
27085
27086 fn sort_completions(&self) -> bool {
27087 true
27088 }
27089
27090 fn filter_completions(&self) -> bool {
27091 true
27092 }
27093
27094 fn show_snippets(&self) -> bool {
27095 false
27096 }
27097}
27098
27099pub trait CodeActionProvider {
27100 fn id(&self) -> Arc<str>;
27101
27102 fn code_actions(
27103 &self,
27104 buffer: &Entity<Buffer>,
27105 range: Range<text::Anchor>,
27106 window: &mut Window,
27107 cx: &mut App,
27108 ) -> Task<Result<Vec<CodeAction>>>;
27109
27110 fn apply_code_action(
27111 &self,
27112 buffer_handle: Entity<Buffer>,
27113 action: CodeAction,
27114 push_to_history: bool,
27115 window: &mut Window,
27116 cx: &mut App,
27117 ) -> Task<Result<ProjectTransaction>>;
27118}
27119
27120impl CodeActionProvider for Entity<Project> {
27121 fn id(&self) -> Arc<str> {
27122 "project".into()
27123 }
27124
27125 fn code_actions(
27126 &self,
27127 buffer: &Entity<Buffer>,
27128 range: Range<text::Anchor>,
27129 _window: &mut Window,
27130 cx: &mut App,
27131 ) -> Task<Result<Vec<CodeAction>>> {
27132 self.update(cx, |project, cx| {
27133 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27134 let code_actions = project.code_actions(buffer, range, None, cx);
27135 cx.background_spawn(async move {
27136 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27137 Ok(code_lens_actions
27138 .context("code lens fetch")?
27139 .into_iter()
27140 .flatten()
27141 .chain(
27142 code_actions
27143 .context("code action fetch")?
27144 .into_iter()
27145 .flatten(),
27146 )
27147 .collect())
27148 })
27149 })
27150 }
27151
27152 fn apply_code_action(
27153 &self,
27154 buffer_handle: Entity<Buffer>,
27155 action: CodeAction,
27156 push_to_history: bool,
27157 _window: &mut Window,
27158 cx: &mut App,
27159 ) -> Task<Result<ProjectTransaction>> {
27160 self.update(cx, |project, cx| {
27161 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27162 })
27163 }
27164}
27165
27166fn snippet_completions(
27167 project: &Project,
27168 buffer: &Entity<Buffer>,
27169 buffer_anchor: text::Anchor,
27170 classifier: CharClassifier,
27171 cx: &mut App,
27172) -> Task<Result<CompletionResponse>> {
27173 let languages = buffer.read(cx).languages_at(buffer_anchor);
27174 let snippet_store = project.snippets().read(cx);
27175
27176 let scopes: Vec<_> = languages
27177 .iter()
27178 .filter_map(|language| {
27179 let language_name = language.lsp_id();
27180 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27181
27182 if snippets.is_empty() {
27183 None
27184 } else {
27185 Some((language.default_scope(), snippets))
27186 }
27187 })
27188 .collect();
27189
27190 if scopes.is_empty() {
27191 return Task::ready(Ok(CompletionResponse {
27192 completions: vec![],
27193 display_options: CompletionDisplayOptions::default(),
27194 is_incomplete: false,
27195 }));
27196 }
27197
27198 let snapshot = buffer.read(cx).text_snapshot();
27199 let executor = cx.background_executor().clone();
27200
27201 cx.background_spawn(async move {
27202 let is_word_char = |c| classifier.is_word(c);
27203
27204 let mut is_incomplete = false;
27205 let mut completions: Vec<Completion> = Vec::new();
27206
27207 const MAX_PREFIX_LEN: usize = 128;
27208 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27209 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27210 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27211
27212 let max_buffer_window: String = snapshot
27213 .text_for_range(window_start..buffer_offset)
27214 .collect();
27215
27216 if max_buffer_window.is_empty() {
27217 return Ok(CompletionResponse {
27218 completions: vec![],
27219 display_options: CompletionDisplayOptions::default(),
27220 is_incomplete: true,
27221 });
27222 }
27223
27224 for (_scope, snippets) in scopes.into_iter() {
27225 // Sort snippets by word count to match longer snippet prefixes first.
27226 let mut sorted_snippet_candidates = snippets
27227 .iter()
27228 .enumerate()
27229 .flat_map(|(snippet_ix, snippet)| {
27230 snippet
27231 .prefix
27232 .iter()
27233 .enumerate()
27234 .map(move |(prefix_ix, prefix)| {
27235 let word_count =
27236 snippet_candidate_suffixes(prefix, &is_word_char).count();
27237 ((snippet_ix, prefix_ix), prefix, word_count)
27238 })
27239 })
27240 .collect_vec();
27241 sorted_snippet_candidates
27242 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27243
27244 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27245
27246 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27247 .take(
27248 sorted_snippet_candidates
27249 .first()
27250 .map(|(_, _, word_count)| *word_count)
27251 .unwrap_or_default(),
27252 )
27253 .collect_vec();
27254
27255 const MAX_RESULTS: usize = 100;
27256 // Each match also remembers how many characters from the buffer it consumed
27257 let mut matches: Vec<(StringMatch, usize)> = vec![];
27258
27259 let mut snippet_list_cutoff_index = 0;
27260 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27261 let word_count = buffer_index + 1;
27262 // Increase `snippet_list_cutoff_index` until we have all of the
27263 // snippets with sufficiently many words.
27264 while sorted_snippet_candidates
27265 .get(snippet_list_cutoff_index)
27266 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27267 *snippet_word_count >= word_count
27268 })
27269 {
27270 snippet_list_cutoff_index += 1;
27271 }
27272
27273 // Take only the candidates with at least `word_count` many words
27274 let snippet_candidates_at_word_len =
27275 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27276
27277 let candidates = snippet_candidates_at_word_len
27278 .iter()
27279 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27280 .enumerate() // index in `sorted_snippet_candidates`
27281 // First char must match
27282 .filter(|(_ix, prefix)| {
27283 itertools::equal(
27284 prefix
27285 .chars()
27286 .next()
27287 .into_iter()
27288 .flat_map(|c| c.to_lowercase()),
27289 buffer_window
27290 .chars()
27291 .next()
27292 .into_iter()
27293 .flat_map(|c| c.to_lowercase()),
27294 )
27295 })
27296 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27297 .collect::<Vec<StringMatchCandidate>>();
27298
27299 matches.extend(
27300 fuzzy::match_strings(
27301 &candidates,
27302 &buffer_window,
27303 buffer_window.chars().any(|c| c.is_uppercase()),
27304 true,
27305 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27306 &Default::default(),
27307 executor.clone(),
27308 )
27309 .await
27310 .into_iter()
27311 .map(|string_match| (string_match, buffer_window.len())),
27312 );
27313
27314 if matches.len() >= MAX_RESULTS {
27315 break;
27316 }
27317 }
27318
27319 let to_lsp = |point: &text::Anchor| {
27320 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27321 point_to_lsp(end)
27322 };
27323 let lsp_end = to_lsp(&buffer_anchor);
27324
27325 if matches.len() >= MAX_RESULTS {
27326 is_incomplete = true;
27327 }
27328
27329 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27330 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27331 sorted_snippet_candidates[string_match.candidate_id];
27332 let snippet = &snippets[snippet_index];
27333 let start = buffer_offset - buffer_window_len;
27334 let start = snapshot.anchor_before(start);
27335 let range = start..buffer_anchor;
27336 let lsp_start = to_lsp(&start);
27337 let lsp_range = lsp::Range {
27338 start: lsp_start,
27339 end: lsp_end,
27340 };
27341 Completion {
27342 replace_range: range,
27343 new_text: snippet.body.clone(),
27344 source: CompletionSource::Lsp {
27345 insert_range: None,
27346 server_id: LanguageServerId(usize::MAX),
27347 resolved: true,
27348 lsp_completion: Box::new(lsp::CompletionItem {
27349 label: snippet.prefix.first().unwrap().clone(),
27350 kind: Some(CompletionItemKind::SNIPPET),
27351 label_details: snippet.description.as_ref().map(|description| {
27352 lsp::CompletionItemLabelDetails {
27353 detail: Some(description.clone()),
27354 description: None,
27355 }
27356 }),
27357 insert_text_format: Some(InsertTextFormat::SNIPPET),
27358 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27359 lsp::InsertReplaceEdit {
27360 new_text: snippet.body.clone(),
27361 insert: lsp_range,
27362 replace: lsp_range,
27363 },
27364 )),
27365 filter_text: Some(snippet.body.clone()),
27366 sort_text: Some(char::MAX.to_string()),
27367 ..lsp::CompletionItem::default()
27368 }),
27369 lsp_defaults: None,
27370 },
27371 label: CodeLabel {
27372 text: matching_prefix.clone(),
27373 runs: Vec::new(),
27374 filter_range: 0..matching_prefix.len(),
27375 },
27376 icon_path: None,
27377 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27378 single_line: snippet.name.clone().into(),
27379 plain_text: snippet
27380 .description
27381 .clone()
27382 .map(|description| description.into()),
27383 }),
27384 insert_text_mode: None,
27385 confirm: None,
27386 match_start: Some(start),
27387 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27388 }
27389 }));
27390 }
27391
27392 Ok(CompletionResponse {
27393 completions,
27394 display_options: CompletionDisplayOptions::default(),
27395 is_incomplete,
27396 })
27397 })
27398}
27399
27400impl CompletionProvider for Entity<Project> {
27401 fn completions(
27402 &self,
27403 buffer: &Entity<Buffer>,
27404 buffer_position: text::Anchor,
27405 options: CompletionContext,
27406 _window: &mut Window,
27407 cx: &mut Context<Editor>,
27408 ) -> Task<Result<Vec<CompletionResponse>>> {
27409 self.update(cx, |project, cx| {
27410 let task = project.completions(buffer, buffer_position, options, cx);
27411 cx.background_spawn(task)
27412 })
27413 }
27414
27415 fn resolve_completions(
27416 &self,
27417 buffer: Entity<Buffer>,
27418 completion_indices: Vec<usize>,
27419 completions: Rc<RefCell<Box<[Completion]>>>,
27420 cx: &mut Context<Editor>,
27421 ) -> Task<Result<bool>> {
27422 self.update(cx, |project, cx| {
27423 project.lsp_store().update(cx, |lsp_store, cx| {
27424 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27425 })
27426 })
27427 }
27428
27429 fn apply_additional_edits_for_completion(
27430 &self,
27431 buffer: Entity<Buffer>,
27432 completions: Rc<RefCell<Box<[Completion]>>>,
27433 completion_index: usize,
27434 push_to_history: bool,
27435 all_commit_ranges: Vec<Range<language::Anchor>>,
27436 cx: &mut Context<Editor>,
27437 ) -> Task<Result<Option<language::Transaction>>> {
27438 self.update(cx, |project, cx| {
27439 project.lsp_store().update(cx, |lsp_store, cx| {
27440 lsp_store.apply_additional_edits_for_completion(
27441 buffer,
27442 completions,
27443 completion_index,
27444 push_to_history,
27445 all_commit_ranges,
27446 cx,
27447 )
27448 })
27449 })
27450 }
27451
27452 fn is_completion_trigger(
27453 &self,
27454 buffer: &Entity<Buffer>,
27455 position: language::Anchor,
27456 text: &str,
27457 trigger_in_words: bool,
27458 cx: &mut Context<Editor>,
27459 ) -> bool {
27460 let mut chars = text.chars();
27461 let char = if let Some(char) = chars.next() {
27462 char
27463 } else {
27464 return false;
27465 };
27466 if chars.next().is_some() {
27467 return false;
27468 }
27469
27470 let buffer = buffer.read(cx);
27471 let snapshot = buffer.snapshot();
27472 let classifier = snapshot
27473 .char_classifier_at(position)
27474 .scope_context(Some(CharScopeContext::Completion));
27475 if trigger_in_words && classifier.is_word(char) {
27476 return true;
27477 }
27478
27479 buffer.completion_triggers().contains(text)
27480 }
27481
27482 fn show_snippets(&self) -> bool {
27483 true
27484 }
27485}
27486
27487impl SemanticsProvider for WeakEntity<Project> {
27488 fn hover(
27489 &self,
27490 buffer: &Entity<Buffer>,
27491 position: text::Anchor,
27492 cx: &mut App,
27493 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27494 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27495 .ok()
27496 }
27497
27498 fn document_highlights(
27499 &self,
27500 buffer: &Entity<Buffer>,
27501 position: text::Anchor,
27502 cx: &mut App,
27503 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27504 self.update(cx, |project, cx| {
27505 project.document_highlights(buffer, position, cx)
27506 })
27507 .ok()
27508 }
27509
27510 fn definitions(
27511 &self,
27512 buffer: &Entity<Buffer>,
27513 position: text::Anchor,
27514 kind: GotoDefinitionKind,
27515 cx: &mut App,
27516 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27517 self.update(cx, |project, cx| match kind {
27518 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27519 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27520 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27521 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27522 })
27523 .ok()
27524 }
27525
27526 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27527 self.update(cx, |project, cx| {
27528 if project
27529 .active_debug_session(cx)
27530 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27531 {
27532 return true;
27533 }
27534
27535 buffer.update(cx, |buffer, cx| {
27536 project.any_language_server_supports_inlay_hints(buffer, cx)
27537 })
27538 })
27539 .unwrap_or(false)
27540 }
27541
27542 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27543 self.update(cx, |project, cx| {
27544 buffer.update(cx, |buffer, cx| {
27545 project.any_language_server_supports_semantic_tokens(buffer, cx)
27546 })
27547 })
27548 .unwrap_or(false)
27549 }
27550
27551 fn inline_values(
27552 &self,
27553 buffer_handle: Entity<Buffer>,
27554 range: Range<text::Anchor>,
27555 cx: &mut App,
27556 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27557 self.update(cx, |project, cx| {
27558 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27559
27560 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27561 })
27562 .ok()
27563 .flatten()
27564 }
27565
27566 fn applicable_inlay_chunks(
27567 &self,
27568 buffer: &Entity<Buffer>,
27569 ranges: &[Range<text::Anchor>],
27570 cx: &mut App,
27571 ) -> Vec<Range<BufferRow>> {
27572 self.update(cx, |project, cx| {
27573 project.lsp_store().update(cx, |lsp_store, cx| {
27574 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27575 })
27576 })
27577 .unwrap_or_default()
27578 }
27579
27580 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27581 self.update(cx, |project, cx| {
27582 project.lsp_store().update(cx, |lsp_store, _| {
27583 lsp_store.invalidate_inlay_hints(for_buffers)
27584 })
27585 })
27586 .ok();
27587 }
27588
27589 fn inlay_hints(
27590 &self,
27591 invalidate: InvalidationStrategy,
27592 buffer: Entity<Buffer>,
27593 ranges: Vec<Range<text::Anchor>>,
27594 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27595 cx: &mut App,
27596 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27597 self.update(cx, |project, cx| {
27598 project.lsp_store().update(cx, |lsp_store, cx| {
27599 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27600 })
27601 })
27602 .ok()
27603 }
27604
27605 fn semantic_tokens(
27606 &self,
27607 buffer: Entity<Buffer>,
27608 refresh: Option<RefreshForServer>,
27609 cx: &mut App,
27610 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27611 self.update(cx, |this, cx| {
27612 this.lsp_store().update(cx, |lsp_store, cx| {
27613 lsp_store.semantic_tokens(buffer, refresh, cx)
27614 })
27615 })
27616 .ok()
27617 }
27618
27619 fn range_for_rename(
27620 &self,
27621 buffer: &Entity<Buffer>,
27622 position: text::Anchor,
27623 cx: &mut App,
27624 ) -> Task<Result<Option<Range<text::Anchor>>>> {
27625 let Some(this) = self.upgrade() else {
27626 return Task::ready(Ok(None));
27627 };
27628
27629 this.update(cx, |project, cx| {
27630 let buffer = buffer.clone();
27631 let task = project.prepare_rename(buffer.clone(), position, cx);
27632 cx.spawn(async move |_, cx| {
27633 Ok(match task.await? {
27634 PrepareRenameResponse::Success(range) => Some(range),
27635 PrepareRenameResponse::InvalidPosition => None,
27636 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27637 // Fallback on using TreeSitter info to determine identifier range
27638 buffer.read_with(cx, |buffer, _| {
27639 let snapshot = buffer.snapshot();
27640 let (range, kind) = snapshot.surrounding_word(position, None);
27641 if kind != Some(CharKind::Word) {
27642 return None;
27643 }
27644 Some(
27645 snapshot.anchor_before(range.start)
27646 ..snapshot.anchor_after(range.end),
27647 )
27648 })
27649 }
27650 })
27651 })
27652 })
27653 }
27654
27655 fn perform_rename(
27656 &self,
27657 buffer: &Entity<Buffer>,
27658 position: text::Anchor,
27659 new_name: String,
27660 cx: &mut App,
27661 ) -> Option<Task<Result<ProjectTransaction>>> {
27662 self.update(cx, |project, cx| {
27663 project.perform_rename(buffer.clone(), position, new_name, cx)
27664 })
27665 .ok()
27666 }
27667}
27668
27669fn consume_contiguous_rows(
27670 contiguous_row_selections: &mut Vec<Selection<Point>>,
27671 selection: &Selection<Point>,
27672 display_map: &DisplaySnapshot,
27673 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27674) -> (MultiBufferRow, MultiBufferRow) {
27675 contiguous_row_selections.push(selection.clone());
27676 let start_row = starting_row(selection, display_map);
27677 let mut end_row = ending_row(selection, display_map);
27678
27679 while let Some(next_selection) = selections.peek() {
27680 if next_selection.start.row <= end_row.0 {
27681 end_row = ending_row(next_selection, display_map);
27682 contiguous_row_selections.push(selections.next().unwrap().clone());
27683 } else {
27684 break;
27685 }
27686 }
27687 (start_row, end_row)
27688}
27689
27690fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27691 if selection.start.column > 0 {
27692 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27693 } else {
27694 MultiBufferRow(selection.start.row)
27695 }
27696}
27697
27698fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27699 if next_selection.end.column > 0 || next_selection.is_empty() {
27700 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27701 } else {
27702 MultiBufferRow(next_selection.end.row)
27703 }
27704}
27705
27706impl EditorSnapshot {
27707 pub fn remote_selections_in_range<'a>(
27708 &'a self,
27709 range: &'a Range<Anchor>,
27710 collaboration_hub: &dyn CollaborationHub,
27711 cx: &'a App,
27712 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27713 let participant_names = collaboration_hub.user_names(cx);
27714 let participant_indices = collaboration_hub.user_participant_indices(cx);
27715 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27716 let collaborators_by_replica_id = collaborators_by_peer_id
27717 .values()
27718 .map(|collaborator| (collaborator.replica_id, collaborator))
27719 .collect::<HashMap<_, _>>();
27720 self.buffer_snapshot()
27721 .selections_in_range(range, false)
27722 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27723 if replica_id == ReplicaId::AGENT {
27724 Some(RemoteSelection {
27725 replica_id,
27726 selection,
27727 cursor_shape,
27728 line_mode,
27729 collaborator_id: CollaboratorId::Agent,
27730 user_name: Some("Agent".into()),
27731 color: cx.theme().players().agent(),
27732 })
27733 } else {
27734 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27735 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27736 let user_name = participant_names.get(&collaborator.user_id).cloned();
27737 Some(RemoteSelection {
27738 replica_id,
27739 selection,
27740 cursor_shape,
27741 line_mode,
27742 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27743 user_name,
27744 color: if let Some(index) = participant_index {
27745 cx.theme().players().color_for_participant(index.0)
27746 } else {
27747 cx.theme().players().absent()
27748 },
27749 })
27750 }
27751 })
27752 }
27753
27754 pub fn hunks_for_ranges(
27755 &self,
27756 ranges: impl IntoIterator<Item = Range<Point>>,
27757 ) -> Vec<MultiBufferDiffHunk> {
27758 let mut hunks = Vec::new();
27759 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27760 HashMap::default();
27761 for query_range in ranges {
27762 let query_rows =
27763 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27764 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27765 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27766 ) {
27767 // Include deleted hunks that are adjacent to the query range, because
27768 // otherwise they would be missed.
27769 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27770 if hunk.status().is_deleted() {
27771 intersects_range |= hunk.row_range.start == query_rows.end;
27772 intersects_range |= hunk.row_range.end == query_rows.start;
27773 }
27774 if intersects_range {
27775 if !processed_buffer_rows
27776 .entry(hunk.buffer_id)
27777 .or_default()
27778 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27779 {
27780 continue;
27781 }
27782 hunks.push(hunk);
27783 }
27784 }
27785 }
27786
27787 hunks
27788 }
27789
27790 fn display_diff_hunks_for_rows<'a>(
27791 &'a self,
27792 display_rows: Range<DisplayRow>,
27793 folded_buffers: &'a HashSet<BufferId>,
27794 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27795 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27796 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27797
27798 self.buffer_snapshot()
27799 .diff_hunks_in_range(buffer_start..buffer_end)
27800 .filter_map(|hunk| {
27801 if folded_buffers.contains(&hunk.buffer_id)
27802 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27803 {
27804 return None;
27805 }
27806
27807 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27808 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27809 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27810 let line_len = self.buffer_snapshot().line_len(last_row);
27811 Point::new(last_row.0, line_len)
27812 } else {
27813 Point::new(hunk.row_range.end.0, 0)
27814 };
27815
27816 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27817 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27818
27819 let display_hunk = if hunk_display_start.column() != 0 {
27820 DisplayDiffHunk::Folded {
27821 display_row: hunk_display_start.row(),
27822 }
27823 } else {
27824 let mut end_row = hunk_display_end.row();
27825 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27826 end_row.0 += 1;
27827 }
27828 let is_created_file = hunk.is_created_file();
27829 let multi_buffer_range = hunk.multi_buffer_range.clone();
27830
27831 DisplayDiffHunk::Unfolded {
27832 status: hunk.status(),
27833 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27834 ..hunk.diff_base_byte_range.end.0,
27835 word_diffs: hunk.word_diffs,
27836 display_row_range: hunk_display_start.row()..end_row,
27837 multi_buffer_range,
27838 is_created_file,
27839 }
27840 };
27841
27842 Some(display_hunk)
27843 })
27844 }
27845
27846 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27847 self.display_snapshot
27848 .buffer_snapshot()
27849 .language_at(position)
27850 }
27851
27852 pub fn is_focused(&self) -> bool {
27853 self.is_focused
27854 }
27855
27856 pub fn placeholder_text(&self) -> Option<String> {
27857 self.placeholder_display_snapshot
27858 .as_ref()
27859 .map(|display_map| display_map.text())
27860 }
27861
27862 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27863 self.scroll_anchor.scroll_position(&self.display_snapshot)
27864 }
27865
27866 pub fn gutter_dimensions(
27867 &self,
27868 font_id: FontId,
27869 font_size: Pixels,
27870 style: &EditorStyle,
27871 window: &mut Window,
27872 cx: &App,
27873 ) -> GutterDimensions {
27874 if self.show_gutter
27875 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27876 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27877 {
27878 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27879 matches!(
27880 ProjectSettings::get_global(cx).git.git_gutter,
27881 GitGutterSetting::TrackedFiles
27882 )
27883 });
27884 let gutter_settings = EditorSettings::get_global(cx).gutter;
27885 let show_line_numbers = self
27886 .show_line_numbers
27887 .unwrap_or(gutter_settings.line_numbers);
27888 let line_gutter_width = if show_line_numbers {
27889 // Avoid flicker-like gutter resizes when the line number gains another digit by
27890 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27891 let min_width_for_number_on_gutter =
27892 ch_advance * gutter_settings.min_line_number_digits as f32;
27893 self.max_line_number_width(style, window)
27894 .max(min_width_for_number_on_gutter)
27895 } else {
27896 0.0.into()
27897 };
27898
27899 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27900 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27901
27902 let git_blame_entries_width =
27903 self.git_blame_gutter_max_author_length
27904 .map(|max_author_length| {
27905 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27906 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27907
27908 /// The number of characters to dedicate to gaps and margins.
27909 const SPACING_WIDTH: usize = 4;
27910
27911 let max_char_count = max_author_length.min(renderer.max_author_length())
27912 + ::git::SHORT_SHA_LENGTH
27913 + MAX_RELATIVE_TIMESTAMP.len()
27914 + SPACING_WIDTH;
27915
27916 ch_advance * max_char_count
27917 });
27918
27919 let is_singleton = self.buffer_snapshot().is_singleton();
27920
27921 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27922 left_padding += if !is_singleton {
27923 ch_width * 4.0
27924 } else if show_runnables || show_breakpoints {
27925 ch_width * 3.0
27926 } else if show_git_gutter && show_line_numbers {
27927 ch_width * 2.0
27928 } else if show_git_gutter || show_line_numbers {
27929 ch_width
27930 } else {
27931 px(0.)
27932 };
27933
27934 let shows_folds = is_singleton && gutter_settings.folds;
27935
27936 let right_padding = if shows_folds && show_line_numbers {
27937 ch_width * 4.0
27938 } else if shows_folds || (!is_singleton && show_line_numbers) {
27939 ch_width * 3.0
27940 } else if show_line_numbers {
27941 ch_width
27942 } else {
27943 px(0.)
27944 };
27945
27946 GutterDimensions {
27947 left_padding,
27948 right_padding,
27949 width: line_gutter_width + left_padding + right_padding,
27950 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27951 git_blame_entries_width,
27952 }
27953 } else if self.offset_content {
27954 GutterDimensions::default_with_margin(font_id, font_size, cx)
27955 } else {
27956 GutterDimensions::default()
27957 }
27958 }
27959
27960 pub fn render_crease_toggle(
27961 &self,
27962 buffer_row: MultiBufferRow,
27963 row_contains_cursor: bool,
27964 editor: Entity<Editor>,
27965 window: &mut Window,
27966 cx: &mut App,
27967 ) -> Option<AnyElement> {
27968 let folded = self.is_line_folded(buffer_row);
27969 let mut is_foldable = false;
27970
27971 if let Some(crease) = self
27972 .crease_snapshot
27973 .query_row(buffer_row, self.buffer_snapshot())
27974 {
27975 is_foldable = true;
27976 match crease {
27977 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
27978 if let Some(render_toggle) = render_toggle {
27979 let toggle_callback =
27980 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
27981 if folded {
27982 editor.update(cx, |editor, cx| {
27983 editor.fold_at(buffer_row, window, cx)
27984 });
27985 } else {
27986 editor.update(cx, |editor, cx| {
27987 editor.unfold_at(buffer_row, window, cx)
27988 });
27989 }
27990 });
27991 return Some((render_toggle)(
27992 buffer_row,
27993 folded,
27994 toggle_callback,
27995 window,
27996 cx,
27997 ));
27998 }
27999 }
28000 }
28001 }
28002
28003 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
28004
28005 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
28006 Some(
28007 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
28008 .toggle_state(folded)
28009 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
28010 if folded {
28011 this.unfold_at(buffer_row, window, cx);
28012 } else {
28013 this.fold_at(buffer_row, window, cx);
28014 }
28015 }))
28016 .into_any_element(),
28017 )
28018 } else {
28019 None
28020 }
28021 }
28022
28023 pub fn render_crease_trailer(
28024 &self,
28025 buffer_row: MultiBufferRow,
28026 window: &mut Window,
28027 cx: &mut App,
28028 ) -> Option<AnyElement> {
28029 let folded = self.is_line_folded(buffer_row);
28030 if let Crease::Inline { render_trailer, .. } = self
28031 .crease_snapshot
28032 .query_row(buffer_row, self.buffer_snapshot())?
28033 {
28034 let render_trailer = render_trailer.as_ref()?;
28035 Some(render_trailer(buffer_row, folded, window, cx))
28036 } else {
28037 None
28038 }
28039 }
28040
28041 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
28042 let digit_count = self.widest_line_number().ilog10() + 1;
28043 column_pixels(style, digit_count as usize, window)
28044 }
28045
28046 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
28047 ///
28048 /// This is positive if `base` is before `line`.
28049 fn relative_line_delta(
28050 &self,
28051 current_selection_head: DisplayRow,
28052 first_visible_row: DisplayRow,
28053 consider_wrapped_lines: bool,
28054 ) -> i64 {
28055 let current_selection_head = current_selection_head.as_display_point().to_point(self);
28056 let first_visible_row = first_visible_row.as_display_point().to_point(self);
28057
28058 if consider_wrapped_lines {
28059 let wrap_snapshot = self.wrap_snapshot();
28060 let base_wrap_row = wrap_snapshot
28061 .make_wrap_point(current_selection_head, Bias::Left)
28062 .row();
28063 let wrap_row = wrap_snapshot
28064 .make_wrap_point(first_visible_row, Bias::Left)
28065 .row();
28066
28067 wrap_row.0 as i64 - base_wrap_row.0 as i64
28068 } else {
28069 let fold_snapshot = self.fold_snapshot();
28070 let base_fold_row = fold_snapshot
28071 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
28072 .row();
28073 let fold_row = fold_snapshot
28074 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
28075 .row();
28076
28077 fold_row as i64 - base_fold_row as i64
28078 }
28079 }
28080
28081 /// Returns the unsigned relative line number to display for each row in `rows`.
28082 ///
28083 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
28084 pub fn calculate_relative_line_numbers(
28085 &self,
28086 rows: &Range<DisplayRow>,
28087 current_selection_head: DisplayRow,
28088 count_wrapped_lines: bool,
28089 ) -> HashMap<DisplayRow, u32> {
28090 let initial_offset =
28091 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
28092
28093 self.row_infos(rows.start)
28094 .take(rows.len())
28095 .enumerate()
28096 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
28097 .filter(|(_row, row_info)| {
28098 row_info.buffer_row.is_some()
28099 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
28100 })
28101 .enumerate()
28102 .filter_map(|(i, (row, row_info))| {
28103 // We want to ensure here that the current line has absolute
28104 // numbering, even if we are in a soft-wrapped line. With the
28105 // exception that if we are in a deleted line, we should number this
28106 // relative with 0, as otherwise it would have no line number at all
28107 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
28108
28109 (relative_line_number != 0
28110 || row_info
28111 .diff_status
28112 .is_some_and(|status| status.is_deleted()))
28113 .then_some((row, relative_line_number))
28114 })
28115 .collect()
28116 }
28117}
28118
28119pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28120 let font_size = style.text.font_size.to_pixels(window.rem_size());
28121 let layout = window.text_system().shape_line(
28122 SharedString::from(" ".repeat(column)),
28123 font_size,
28124 &[TextRun {
28125 len: column,
28126 font: style.text.font(),
28127 color: Hsla::default(),
28128 ..Default::default()
28129 }],
28130 None,
28131 );
28132
28133 layout.width
28134}
28135
28136impl Deref for EditorSnapshot {
28137 type Target = DisplaySnapshot;
28138
28139 fn deref(&self) -> &Self::Target {
28140 &self.display_snapshot
28141 }
28142}
28143
28144#[derive(Clone, Debug, PartialEq, Eq)]
28145pub enum EditorEvent {
28146 /// Emitted when the stored review comments change (added, removed, or updated).
28147 ReviewCommentsChanged {
28148 /// The new total count of review comments.
28149 total_count: usize,
28150 },
28151 InputIgnored {
28152 text: Arc<str>,
28153 },
28154 InputHandled {
28155 utf16_range_to_replace: Option<Range<isize>>,
28156 text: Arc<str>,
28157 },
28158 BufferRangesUpdated {
28159 buffer: Entity<Buffer>,
28160 path_key: PathKey,
28161 ranges: Vec<ExcerptRange<text::Anchor>>,
28162 },
28163 BuffersRemoved {
28164 removed_buffer_ids: Vec<BufferId>,
28165 },
28166 BuffersEdited {
28167 buffer_ids: Vec<BufferId>,
28168 },
28169 BufferFoldToggled {
28170 ids: Vec<BufferId>,
28171 folded: bool,
28172 },
28173 ExpandExcerptsRequested {
28174 excerpt_anchors: Vec<Anchor>,
28175 lines: u32,
28176 direction: ExpandExcerptDirection,
28177 },
28178 StageOrUnstageRequested {
28179 stage: bool,
28180 hunks: Vec<MultiBufferDiffHunk>,
28181 },
28182 OpenExcerptsRequested {
28183 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28184 split: bool,
28185 },
28186 RestoreRequested {
28187 hunks: Vec<MultiBufferDiffHunk>,
28188 },
28189 BufferEdited,
28190 Edited {
28191 transaction_id: clock::Lamport,
28192 },
28193 Reparsed(BufferId),
28194 Focused,
28195 FocusedIn,
28196 Blurred,
28197 DirtyChanged,
28198 Saved,
28199 TitleChanged,
28200 SelectionsChanged {
28201 local: bool,
28202 },
28203 ScrollPositionChanged {
28204 local: bool,
28205 autoscroll: bool,
28206 },
28207 TransactionUndone {
28208 transaction_id: clock::Lamport,
28209 },
28210 TransactionBegun {
28211 transaction_id: clock::Lamport,
28212 },
28213 CursorShapeChanged,
28214 BreadcrumbsChanged,
28215 OutlineSymbolsChanged,
28216 PushedToNavHistory {
28217 anchor: Anchor,
28218 is_deactivate: bool,
28219 },
28220}
28221
28222impl EventEmitter<EditorEvent> for Editor {}
28223
28224impl Focusable for Editor {
28225 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28226 self.focus_handle.clone()
28227 }
28228}
28229
28230impl Render for Editor {
28231 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28232 EditorElement::new(&cx.entity(), self.create_style(cx))
28233 }
28234}
28235
28236impl EntityInputHandler for Editor {
28237 fn text_for_range(
28238 &mut self,
28239 range_utf16: Range<usize>,
28240 adjusted_range: &mut Option<Range<usize>>,
28241 _: &mut Window,
28242 cx: &mut Context<Self>,
28243 ) -> Option<String> {
28244 let snapshot = self.buffer.read(cx).read(cx);
28245 let start = snapshot.clip_offset_utf16(
28246 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28247 Bias::Left,
28248 );
28249 let end = snapshot.clip_offset_utf16(
28250 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28251 Bias::Right,
28252 );
28253 if (start.0.0..end.0.0) != range_utf16 {
28254 adjusted_range.replace(start.0.0..end.0.0);
28255 }
28256 Some(snapshot.text_for_range(start..end).collect())
28257 }
28258
28259 fn selected_text_range(
28260 &mut self,
28261 ignore_disabled_input: bool,
28262 _: &mut Window,
28263 cx: &mut Context<Self>,
28264 ) -> Option<UTF16Selection> {
28265 // Prevent the IME menu from appearing when holding down an alphabetic key
28266 // while input is disabled.
28267 if !ignore_disabled_input && !self.input_enabled {
28268 return None;
28269 }
28270
28271 let selection = self
28272 .selections
28273 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28274 let range = selection.range();
28275
28276 Some(UTF16Selection {
28277 range: range.start.0.0..range.end.0.0,
28278 reversed: selection.reversed,
28279 })
28280 }
28281
28282 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28283 let snapshot = self.buffer.read(cx).read(cx);
28284 let range = self
28285 .text_highlights(HighlightKey::InputComposition, cx)?
28286 .1
28287 .first()?;
28288 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28289 }
28290
28291 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28292 self.clear_highlights(HighlightKey::InputComposition, cx);
28293 self.ime_transaction.take();
28294 }
28295
28296 fn replace_text_in_range(
28297 &mut self,
28298 range_utf16: Option<Range<usize>>,
28299 text: &str,
28300 window: &mut Window,
28301 cx: &mut Context<Self>,
28302 ) {
28303 if !self.input_enabled {
28304 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28305 return;
28306 }
28307
28308 self.transact(window, cx, |this, window, cx| {
28309 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28310 if let Some(marked_ranges) = this.marked_text_ranges(cx) {
28311 // During IME composition, macOS reports the replacement range
28312 // relative to the first marked region (the only one visible via
28313 // marked_text_range). The correct targets for replacement are the
28314 // marked ranges themselves — one per cursor — so use them directly.
28315 Some(marked_ranges)
28316 } else if range_utf16.start == range_utf16.end {
28317 // An empty replacement range means "insert at cursor" with no text
28318 // to replace. macOS reports the cursor position from its own
28319 // (single-cursor) view of the buffer, which diverges from our actual
28320 // cursor positions after multi-cursor edits have shifted offsets.
28321 // Treating this as range_utf16=None lets each cursor insert in place.
28322 None
28323 } else {
28324 // Outside of IME composition (e.g. Accessibility Keyboard word
28325 // completion), the range is an absolute document offset for the
28326 // newest cursor. Fan it out to all cursors via
28327 // selection_replacement_ranges, which applies the delta relative
28328 // to the newest selection to every cursor.
28329 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28330 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28331 Some(this.selection_replacement_ranges(range_utf16, cx))
28332 }
28333 } else {
28334 this.marked_text_ranges(cx)
28335 };
28336
28337 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28338 let newest_selection_id = this.selections.newest_anchor().id;
28339 this.selections
28340 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28341 .iter()
28342 .zip(ranges_to_replace.iter())
28343 .find_map(|(selection, range)| {
28344 if selection.id == newest_selection_id {
28345 Some(
28346 (range.start.0.0 as isize - selection.head().0.0 as isize)
28347 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28348 )
28349 } else {
28350 None
28351 }
28352 })
28353 });
28354
28355 cx.emit(EditorEvent::InputHandled {
28356 utf16_range_to_replace: range_to_replace,
28357 text: text.into(),
28358 });
28359
28360 if let Some(new_selected_ranges) = new_selected_ranges {
28361 // Only backspace if at least one range covers actual text. When all
28362 // ranges are empty (e.g. a trailing-space insertion from Accessibility
28363 // Keyboard sends replacementRange=cursor..cursor), backspace would
28364 // incorrectly delete the character just before the cursor.
28365 let should_backspace = new_selected_ranges.iter().any(|r| r.start != r.end);
28366 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28367 selections.select_ranges(new_selected_ranges)
28368 });
28369 if should_backspace {
28370 this.backspace(&Default::default(), window, cx);
28371 }
28372 }
28373
28374 this.handle_input(text, window, cx);
28375 });
28376
28377 if let Some(transaction) = self.ime_transaction {
28378 self.buffer.update(cx, |buffer, cx| {
28379 buffer.group_until_transaction(transaction, cx);
28380 });
28381 }
28382
28383 self.unmark_text(window, cx);
28384 }
28385
28386 fn replace_and_mark_text_in_range(
28387 &mut self,
28388 range_utf16: Option<Range<usize>>,
28389 text: &str,
28390 new_selected_range_utf16: Option<Range<usize>>,
28391 window: &mut Window,
28392 cx: &mut Context<Self>,
28393 ) {
28394 if !self.input_enabled {
28395 return;
28396 }
28397
28398 let transaction = self.transact(window, cx, |this, window, cx| {
28399 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28400 let snapshot = this.buffer.read(cx).read(cx);
28401 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28402 for marked_range in &mut marked_ranges {
28403 marked_range.end = marked_range.start + relative_range_utf16.end;
28404 marked_range.start += relative_range_utf16.start;
28405 marked_range.start =
28406 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28407 marked_range.end =
28408 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28409 }
28410 }
28411 Some(marked_ranges)
28412 } else if let Some(range_utf16) = range_utf16 {
28413 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28414 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28415 Some(this.selection_replacement_ranges(range_utf16, cx))
28416 } else {
28417 None
28418 };
28419
28420 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28421 let newest_selection_id = this.selections.newest_anchor().id;
28422 this.selections
28423 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28424 .iter()
28425 .zip(ranges_to_replace.iter())
28426 .find_map(|(selection, range)| {
28427 if selection.id == newest_selection_id {
28428 Some(
28429 (range.start.0.0 as isize - selection.head().0.0 as isize)
28430 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28431 )
28432 } else {
28433 None
28434 }
28435 })
28436 });
28437
28438 cx.emit(EditorEvent::InputHandled {
28439 utf16_range_to_replace: range_to_replace,
28440 text: text.into(),
28441 });
28442
28443 if let Some(ranges) = ranges_to_replace {
28444 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28445 s.select_ranges(ranges)
28446 });
28447 }
28448
28449 let marked_ranges = {
28450 let snapshot = this.buffer.read(cx).read(cx);
28451 this.selections
28452 .disjoint_anchors_arc()
28453 .iter()
28454 .map(|selection| {
28455 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28456 })
28457 .collect::<Vec<_>>()
28458 };
28459
28460 if text.is_empty() {
28461 this.unmark_text(window, cx);
28462 } else {
28463 this.highlight_text(
28464 HighlightKey::InputComposition,
28465 marked_ranges.clone(),
28466 HighlightStyle {
28467 underline: Some(UnderlineStyle {
28468 thickness: px(1.),
28469 color: None,
28470 wavy: false,
28471 }),
28472 ..Default::default()
28473 },
28474 cx,
28475 );
28476 }
28477
28478 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28479 let use_autoclose = this.use_autoclose;
28480 let use_auto_surround = this.use_auto_surround;
28481 this.set_use_autoclose(false);
28482 this.set_use_auto_surround(false);
28483 this.handle_input(text, window, cx);
28484 this.set_use_autoclose(use_autoclose);
28485 this.set_use_auto_surround(use_auto_surround);
28486
28487 if let Some(new_selected_range) = new_selected_range_utf16 {
28488 let snapshot = this.buffer.read(cx).read(cx);
28489 let new_selected_ranges = marked_ranges
28490 .into_iter()
28491 .map(|marked_range| {
28492 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28493 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28494 insertion_start.0 + new_selected_range.start,
28495 ));
28496 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28497 insertion_start.0 + new_selected_range.end,
28498 ));
28499 snapshot.clip_offset_utf16(new_start, Bias::Left)
28500 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28501 })
28502 .collect::<Vec<_>>();
28503
28504 drop(snapshot);
28505 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28506 selections.select_ranges(new_selected_ranges)
28507 });
28508 }
28509 });
28510
28511 self.ime_transaction = self.ime_transaction.or(transaction);
28512 if let Some(transaction) = self.ime_transaction {
28513 self.buffer.update(cx, |buffer, cx| {
28514 buffer.group_until_transaction(transaction, cx);
28515 });
28516 }
28517
28518 if self
28519 .text_highlights(HighlightKey::InputComposition, cx)
28520 .is_none()
28521 {
28522 self.ime_transaction.take();
28523 }
28524 }
28525
28526 fn bounds_for_range(
28527 &mut self,
28528 range_utf16: Range<usize>,
28529 element_bounds: gpui::Bounds<Pixels>,
28530 window: &mut Window,
28531 cx: &mut Context<Self>,
28532 ) -> Option<gpui::Bounds<Pixels>> {
28533 let text_layout_details = self.text_layout_details(window, cx);
28534 let CharacterDimensions {
28535 em_width,
28536 em_advance,
28537 line_height,
28538 } = self.character_dimensions(window, cx);
28539
28540 let snapshot = self.snapshot(window, cx);
28541 let scroll_position = snapshot.scroll_position();
28542 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28543
28544 let start =
28545 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28546 let x = Pixels::from(
28547 ScrollOffset::from(
28548 snapshot.x_for_display_point(start, &text_layout_details)
28549 + self.gutter_dimensions.full_width(),
28550 ) - scroll_left,
28551 );
28552 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28553
28554 Some(Bounds {
28555 origin: element_bounds.origin + point(x, y),
28556 size: size(em_width, line_height),
28557 })
28558 }
28559
28560 fn character_index_for_point(
28561 &mut self,
28562 point: gpui::Point<Pixels>,
28563 _window: &mut Window,
28564 _cx: &mut Context<Self>,
28565 ) -> Option<usize> {
28566 let position_map = self.last_position_map.as_ref()?;
28567 if !position_map.text_hitbox.contains(&point) {
28568 return None;
28569 }
28570 let display_point = position_map.point_for_position(point).previous_valid;
28571 let anchor = position_map
28572 .snapshot
28573 .display_point_to_anchor(display_point, Bias::Left);
28574 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28575 Some(utf16_offset.0.0)
28576 }
28577
28578 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28579 self.expects_character_input
28580 }
28581}
28582
28583trait SelectionExt {
28584 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28585 fn spanned_rows(
28586 &self,
28587 include_end_if_at_line_start: bool,
28588 map: &DisplaySnapshot,
28589 ) -> Range<MultiBufferRow>;
28590}
28591
28592impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28593 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28594 let start = self
28595 .start
28596 .to_point(map.buffer_snapshot())
28597 .to_display_point(map);
28598 let end = self
28599 .end
28600 .to_point(map.buffer_snapshot())
28601 .to_display_point(map);
28602 if self.reversed {
28603 end..start
28604 } else {
28605 start..end
28606 }
28607 }
28608
28609 fn spanned_rows(
28610 &self,
28611 include_end_if_at_line_start: bool,
28612 map: &DisplaySnapshot,
28613 ) -> Range<MultiBufferRow> {
28614 let start = self.start.to_point(map.buffer_snapshot());
28615 let mut end = self.end.to_point(map.buffer_snapshot());
28616 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28617 end.row -= 1;
28618 }
28619
28620 let buffer_start = map.prev_line_boundary(start).0;
28621 let buffer_end = map.next_line_boundary(end).0;
28622 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28623 }
28624}
28625
28626impl<T: InvalidationRegion> InvalidationStack<T> {
28627 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28628 where
28629 S: Clone + ToOffset,
28630 {
28631 while let Some(region) = self.last() {
28632 let all_selections_inside_invalidation_ranges =
28633 if selections.len() == region.ranges().len() {
28634 selections
28635 .iter()
28636 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28637 .all(|(selection, invalidation_range)| {
28638 let head = selection.head().to_offset(buffer);
28639 invalidation_range.start <= head && invalidation_range.end >= head
28640 })
28641 } else {
28642 false
28643 };
28644
28645 if all_selections_inside_invalidation_ranges {
28646 break;
28647 } else {
28648 self.pop();
28649 }
28650 }
28651 }
28652}
28653
28654#[derive(Clone)]
28655struct ErasedEditorImpl(Entity<Editor>);
28656
28657impl ui_input::ErasedEditor for ErasedEditorImpl {
28658 fn text(&self, cx: &App) -> String {
28659 self.0.read(cx).text(cx)
28660 }
28661
28662 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28663 self.0.update(cx, |this, cx| {
28664 this.set_text(text, window, cx);
28665 })
28666 }
28667
28668 fn clear(&self, window: &mut Window, cx: &mut App) {
28669 self.0.update(cx, |this, cx| this.clear(window, cx));
28670 }
28671
28672 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28673 self.0.update(cx, |this, cx| {
28674 this.set_placeholder_text(text, window, cx);
28675 });
28676 }
28677
28678 fn focus_handle(&self, cx: &App) -> FocusHandle {
28679 self.0.read(cx).focus_handle(cx)
28680 }
28681
28682 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28683 let settings = ThemeSettings::get_global(cx);
28684 let theme_color = cx.theme().colors();
28685
28686 let text_style = TextStyle {
28687 font_family: settings.ui_font.family.clone(),
28688 font_features: settings.ui_font.features.clone(),
28689 font_size: rems(0.875).into(),
28690 font_weight: settings.ui_font.weight,
28691 font_style: FontStyle::Normal,
28692 line_height: relative(1.2),
28693 color: theme_color.text,
28694 ..Default::default()
28695 };
28696 let editor_style = EditorStyle {
28697 background: theme_color.ghost_element_background,
28698 local_player: cx.theme().players().local(),
28699 syntax: cx.theme().syntax().clone(),
28700 text: text_style,
28701 ..Default::default()
28702 };
28703 EditorElement::new(&self.0, editor_style).into_any()
28704 }
28705
28706 fn as_any(&self) -> &dyn Any {
28707 &self.0
28708 }
28709
28710 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28711 self.0.update(cx, |editor, cx| {
28712 let editor_offset = editor.buffer().read(cx).len(cx);
28713 editor.change_selections(
28714 SelectionEffects::scroll(Autoscroll::Next),
28715 window,
28716 cx,
28717 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28718 );
28719 });
28720 }
28721
28722 fn subscribe(
28723 &self,
28724 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28725 window: &mut Window,
28726 cx: &mut App,
28727 ) -> Subscription {
28728 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28729 let event = match event {
28730 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28731 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28732 _ => return,
28733 };
28734 (callback)(event, window, cx);
28735 })
28736 }
28737
28738 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28739 self.0.update(cx, |editor, cx| {
28740 editor.set_masked(masked, cx);
28741 });
28742 }
28743}
28744impl<T> Default for InvalidationStack<T> {
28745 fn default() -> Self {
28746 Self(Default::default())
28747 }
28748}
28749
28750impl<T> Deref for InvalidationStack<T> {
28751 type Target = Vec<T>;
28752
28753 fn deref(&self) -> &Self::Target {
28754 &self.0
28755 }
28756}
28757
28758impl<T> DerefMut for InvalidationStack<T> {
28759 fn deref_mut(&mut self) -> &mut Self::Target {
28760 &mut self.0
28761 }
28762}
28763
28764impl InvalidationRegion for SnippetState {
28765 fn ranges(&self) -> &[Range<Anchor>] {
28766 &self.ranges[self.active_index]
28767 }
28768}
28769
28770fn edit_prediction_edit_text(
28771 current_snapshot: &BufferSnapshot,
28772 edits: &[(Range<Anchor>, impl AsRef<str>)],
28773 edit_preview: &EditPreview,
28774 include_deletions: bool,
28775 multibuffer_snapshot: &MultiBufferSnapshot,
28776 cx: &App,
28777) -> HighlightedText {
28778 let edits = edits
28779 .iter()
28780 .filter_map(|(anchor, text)| {
28781 Some((
28782 multibuffer_snapshot
28783 .anchor_range_to_buffer_anchor_range(anchor.clone())?
28784 .1,
28785 text,
28786 ))
28787 })
28788 .collect::<Vec<_>>();
28789
28790 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28791}
28792
28793fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28794 // Fallback for providers that don't provide edit_preview (like Copilot)
28795 // Just show the raw edit text with basic styling
28796 let mut text = String::new();
28797 let mut highlights = Vec::new();
28798
28799 let insertion_highlight_style = HighlightStyle {
28800 color: Some(cx.theme().colors().text),
28801 ..Default::default()
28802 };
28803
28804 for (_, edit_text) in edits {
28805 let start_offset = text.len();
28806 text.push_str(edit_text);
28807 let end_offset = text.len();
28808
28809 if start_offset < end_offset {
28810 highlights.push((start_offset..end_offset, insertion_highlight_style));
28811 }
28812 }
28813
28814 HighlightedText {
28815 text: text.into(),
28816 highlights,
28817 }
28818}
28819
28820pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28821 match severity {
28822 lsp::DiagnosticSeverity::ERROR => colors.error,
28823 lsp::DiagnosticSeverity::WARNING => colors.warning,
28824 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28825 lsp::DiagnosticSeverity::HINT => colors.info,
28826 _ => colors.ignored,
28827 }
28828}
28829
28830pub fn styled_runs_for_code_label<'a>(
28831 label: &'a CodeLabel,
28832 syntax_theme: &'a theme::SyntaxTheme,
28833 local_player: &'a theme::PlayerColor,
28834) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28835 let fade_out = HighlightStyle {
28836 fade_out: Some(0.35),
28837 ..Default::default()
28838 };
28839
28840 if label.runs.is_empty() {
28841 let desc_start = label.filter_range.end;
28842 let fade_run =
28843 (desc_start < label.text.len()).then(|| (desc_start..label.text.len(), fade_out));
28844 return Either::Left(fade_run.into_iter());
28845 }
28846
28847 let mut prev_end = label.filter_range.end;
28848 Either::Right(
28849 label
28850 .runs
28851 .iter()
28852 .enumerate()
28853 .flat_map(move |(ix, (range, highlight_id))| {
28854 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28855 HighlightStyle {
28856 color: Some(local_player.cursor),
28857 ..Default::default()
28858 }
28859 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28860 HighlightStyle {
28861 background_color: Some(local_player.selection),
28862 ..Default::default()
28863 }
28864 } else if let Some(style) = syntax_theme.get(*highlight_id).cloned() {
28865 style
28866 } else {
28867 return Default::default();
28868 };
28869
28870 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28871 let muted_style = style.highlight(fade_out);
28872 if range.start >= label.filter_range.end {
28873 if range.start > prev_end {
28874 runs.push((prev_end..range.start, fade_out));
28875 }
28876 runs.push((range.clone(), muted_style));
28877 } else if range.end <= label.filter_range.end {
28878 runs.push((range.clone(), style));
28879 } else {
28880 runs.push((range.start..label.filter_range.end, style));
28881 runs.push((label.filter_range.end..range.end, muted_style));
28882 }
28883 prev_end = cmp::max(prev_end, range.end);
28884
28885 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28886 runs.push((prev_end..label.text.len(), fade_out));
28887 }
28888
28889 runs
28890 }),
28891 )
28892}
28893
28894pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28895 let mut prev_index = 0;
28896 let mut prev_codepoint: Option<char> = None;
28897 text.char_indices()
28898 .chain([(text.len(), '\0')])
28899 .filter_map(move |(index, codepoint)| {
28900 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28901 let is_boundary = index == text.len()
28902 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28903 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28904 if is_boundary {
28905 let chunk = &text[prev_index..index];
28906 prev_index = index;
28907 Some(chunk)
28908 } else {
28909 None
28910 }
28911 })
28912}
28913
28914/// Given a string of text immediately before the cursor, iterates over possible
28915/// strings a snippet could match to. More precisely: returns an iterator over
28916/// suffixes of `text` created by splitting at word boundaries (before & after
28917/// every non-word character).
28918///
28919/// Shorter suffixes are returned first.
28920pub(crate) fn snippet_candidate_suffixes<'a>(
28921 text: &'a str,
28922 is_word_char: &'a dyn Fn(char) -> bool,
28923) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28924 let mut prev_index = text.len();
28925 let mut prev_codepoint = None;
28926 text.char_indices()
28927 .rev()
28928 .chain([(0, '\0')])
28929 .filter_map(move |(index, codepoint)| {
28930 let prev_index = std::mem::replace(&mut prev_index, index);
28931 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28932 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28933 None
28934 } else {
28935 let chunk = &text[prev_index..]; // go to end of string
28936 Some(chunk)
28937 }
28938 })
28939}
28940
28941pub trait RangeToAnchorExt: Sized {
28942 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28943
28944 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28945 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28946 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28947 }
28948}
28949
28950impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28951 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28952 let start_offset = self.start.to_offset(snapshot);
28953 let end_offset = self.end.to_offset(snapshot);
28954 if start_offset == end_offset {
28955 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28956 } else {
28957 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28958 }
28959 }
28960}
28961
28962pub trait RowExt {
28963 fn as_f64(&self) -> f64;
28964
28965 fn next_row(&self) -> Self;
28966
28967 fn previous_row(&self) -> Self;
28968
28969 fn minus(&self, other: Self) -> u32;
28970}
28971
28972impl RowExt for DisplayRow {
28973 fn as_f64(&self) -> f64 {
28974 self.0 as _
28975 }
28976
28977 fn next_row(&self) -> Self {
28978 Self(self.0 + 1)
28979 }
28980
28981 fn previous_row(&self) -> Self {
28982 Self(self.0.saturating_sub(1))
28983 }
28984
28985 fn minus(&self, other: Self) -> u32 {
28986 self.0 - other.0
28987 }
28988}
28989
28990impl RowExt for MultiBufferRow {
28991 fn as_f64(&self) -> f64 {
28992 self.0 as _
28993 }
28994
28995 fn next_row(&self) -> Self {
28996 Self(self.0 + 1)
28997 }
28998
28999 fn previous_row(&self) -> Self {
29000 Self(self.0.saturating_sub(1))
29001 }
29002
29003 fn minus(&self, other: Self) -> u32 {
29004 self.0 - other.0
29005 }
29006}
29007
29008trait RowRangeExt {
29009 type Row;
29010
29011 fn len(&self) -> usize;
29012
29013 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
29014}
29015
29016impl RowRangeExt for Range<MultiBufferRow> {
29017 type Row = MultiBufferRow;
29018
29019 fn len(&self) -> usize {
29020 (self.end.0 - self.start.0) as usize
29021 }
29022
29023 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
29024 (self.start.0..self.end.0).map(MultiBufferRow)
29025 }
29026}
29027
29028impl RowRangeExt for Range<DisplayRow> {
29029 type Row = DisplayRow;
29030
29031 fn len(&self) -> usize {
29032 (self.end.0 - self.start.0) as usize
29033 }
29034
29035 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
29036 (self.start.0..self.end.0).map(DisplayRow)
29037 }
29038}
29039
29040/// If select range has more than one line, we
29041/// just point the cursor to range.start.
29042fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
29043 if range.start.row == range.end.row {
29044 range
29045 } else {
29046 range.start..range.start
29047 }
29048}
29049pub struct KillRing(ClipboardItem);
29050impl Global for KillRing {}
29051
29052const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
29053
29054enum BreakpointPromptEditAction {
29055 Log,
29056 Condition,
29057 HitCondition,
29058}
29059
29060struct BreakpointPromptEditor {
29061 pub(crate) prompt: Entity<Editor>,
29062 editor: WeakEntity<Editor>,
29063 breakpoint_anchor: Anchor,
29064 breakpoint: Breakpoint,
29065 edit_action: BreakpointPromptEditAction,
29066 block_ids: HashSet<CustomBlockId>,
29067 editor_margins: Arc<Mutex<EditorMargins>>,
29068 _subscriptions: Vec<Subscription>,
29069}
29070
29071impl BreakpointPromptEditor {
29072 const MAX_LINES: u8 = 4;
29073
29074 fn new(
29075 editor: WeakEntity<Editor>,
29076 breakpoint_anchor: Anchor,
29077 breakpoint: Breakpoint,
29078 edit_action: BreakpointPromptEditAction,
29079 window: &mut Window,
29080 cx: &mut Context<Self>,
29081 ) -> Self {
29082 let base_text = match edit_action {
29083 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
29084 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
29085 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
29086 }
29087 .map(|msg| msg.to_string())
29088 .unwrap_or_default();
29089
29090 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
29091 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29092
29093 let prompt = cx.new(|cx| {
29094 let mut prompt = Editor::new(
29095 EditorMode::AutoHeight {
29096 min_lines: 1,
29097 max_lines: Some(Self::MAX_LINES as usize),
29098 },
29099 buffer,
29100 None,
29101 window,
29102 cx,
29103 );
29104 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
29105 prompt.set_show_cursor_when_unfocused(false, cx);
29106 prompt.set_placeholder_text(
29107 match edit_action {
29108 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
29109 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
29110 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
29111 },
29112 window,
29113 cx,
29114 );
29115
29116 prompt
29117 });
29118
29119 Self {
29120 prompt,
29121 editor,
29122 breakpoint_anchor,
29123 breakpoint,
29124 edit_action,
29125 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
29126 block_ids: Default::default(),
29127 _subscriptions: vec![],
29128 }
29129 }
29130
29131 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
29132 self.block_ids.extend(block_ids)
29133 }
29134
29135 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
29136 if let Some(editor) = self.editor.upgrade() {
29137 let message = self
29138 .prompt
29139 .read(cx)
29140 .buffer
29141 .read(cx)
29142 .as_singleton()
29143 .expect("A multi buffer in breakpoint prompt isn't possible")
29144 .read(cx)
29145 .as_rope()
29146 .to_string();
29147
29148 editor.update(cx, |editor, cx| {
29149 editor.edit_breakpoint_at_anchor(
29150 self.breakpoint_anchor,
29151 self.breakpoint.clone(),
29152 match self.edit_action {
29153 BreakpointPromptEditAction::Log => {
29154 BreakpointEditAction::EditLogMessage(message.into())
29155 }
29156 BreakpointPromptEditAction::Condition => {
29157 BreakpointEditAction::EditCondition(message.into())
29158 }
29159 BreakpointPromptEditAction::HitCondition => {
29160 BreakpointEditAction::EditHitCondition(message.into())
29161 }
29162 },
29163 cx,
29164 );
29165
29166 editor.remove_blocks(self.block_ids.clone(), None, cx);
29167 cx.focus_self(window);
29168 });
29169 }
29170 }
29171
29172 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29173 self.editor
29174 .update(cx, |editor, cx| {
29175 editor.remove_blocks(self.block_ids.clone(), None, cx);
29176 window.focus(&editor.focus_handle, cx);
29177 })
29178 .log_err();
29179 }
29180
29181 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29182 let settings = ThemeSettings::get_global(cx);
29183 let text_style = TextStyle {
29184 color: if self.prompt.read(cx).read_only(cx) {
29185 cx.theme().colors().text_disabled
29186 } else {
29187 cx.theme().colors().text
29188 },
29189 font_family: settings.buffer_font.family.clone(),
29190 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29191 font_size: settings.buffer_font_size(cx).into(),
29192 font_weight: settings.buffer_font.weight,
29193 line_height: relative(settings.buffer_line_height.value()),
29194 ..Default::default()
29195 };
29196 EditorElement::new(
29197 &self.prompt,
29198 EditorStyle {
29199 background: cx.theme().colors().editor_background,
29200 local_player: cx.theme().players().local(),
29201 text: text_style,
29202 ..Default::default()
29203 },
29204 )
29205 }
29206
29207 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29208 let focus_handle = self.prompt.focus_handle(cx);
29209 IconButton::new("cancel", IconName::Close)
29210 .icon_color(Color::Muted)
29211 .shape(IconButtonShape::Square)
29212 .tooltip(move |_window, cx| {
29213 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29214 })
29215 .on_click(cx.listener(|this, _, window, cx| {
29216 this.cancel(&menu::Cancel, window, cx);
29217 }))
29218 }
29219
29220 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29221 let focus_handle = self.prompt.focus_handle(cx);
29222 IconButton::new("confirm", IconName::Return)
29223 .icon_color(Color::Muted)
29224 .shape(IconButtonShape::Square)
29225 .tooltip(move |_window, cx| {
29226 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29227 })
29228 .on_click(cx.listener(|this, _, window, cx| {
29229 this.confirm(&menu::Confirm, window, cx);
29230 }))
29231 }
29232}
29233
29234impl Render for BreakpointPromptEditor {
29235 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29236 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29237 let editor_margins = *self.editor_margins.lock();
29238 let gutter_dimensions = editor_margins.gutter;
29239 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29240 let right_padding = editor_margins.right + px(9.);
29241 h_flex()
29242 .key_context("Editor")
29243 .bg(cx.theme().colors().editor_background)
29244 .border_y_1()
29245 .border_color(cx.theme().status().info_border)
29246 .size_full()
29247 .py(window.line_height() / 2.5)
29248 .pr(right_padding)
29249 .on_action(cx.listener(Self::confirm))
29250 .on_action(cx.listener(Self::cancel))
29251 .child(
29252 WithRemSize::new(ui_font_size)
29253 .h_full()
29254 .w(left_gutter_width)
29255 .flex()
29256 .flex_row()
29257 .flex_shrink_0()
29258 .items_center()
29259 .justify_center()
29260 .gap_1()
29261 .child(self.render_close_button(cx)),
29262 )
29263 .child(
29264 h_flex()
29265 .w_full()
29266 .justify_between()
29267 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29268 .child(
29269 WithRemSize::new(ui_font_size)
29270 .flex()
29271 .flex_row()
29272 .items_center()
29273 .child(self.render_confirm_button(cx)),
29274 ),
29275 )
29276 }
29277}
29278
29279impl Focusable for BreakpointPromptEditor {
29280 fn focus_handle(&self, cx: &App) -> FocusHandle {
29281 self.prompt.focus_handle(cx)
29282 }
29283}
29284
29285fn all_edits_insertions_or_deletions(
29286 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29287 snapshot: &MultiBufferSnapshot,
29288) -> bool {
29289 let mut all_insertions = true;
29290 let mut all_deletions = true;
29291
29292 for (range, new_text) in edits.iter() {
29293 let range_is_empty = range.to_offset(snapshot).is_empty();
29294 let text_is_empty = new_text.is_empty();
29295
29296 if range_is_empty != text_is_empty {
29297 if range_is_empty {
29298 all_deletions = false;
29299 } else {
29300 all_insertions = false;
29301 }
29302 } else {
29303 return false;
29304 }
29305
29306 if !all_insertions && !all_deletions {
29307 return false;
29308 }
29309 }
29310 all_insertions || all_deletions
29311}
29312
29313struct MissingEditPredictionKeybindingTooltip;
29314
29315impl Render for MissingEditPredictionKeybindingTooltip {
29316 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29317 ui::tooltip_container(cx, |container, cx| {
29318 container
29319 .flex_shrink_0()
29320 .max_w_80()
29321 .min_h(rems_from_px(124.))
29322 .justify_between()
29323 .child(
29324 v_flex()
29325 .flex_1()
29326 .text_ui_sm(cx)
29327 .child(Label::new("Conflict with Accept Keybinding"))
29328 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29329 )
29330 .child(
29331 h_flex()
29332 .pb_1()
29333 .gap_1()
29334 .items_end()
29335 .w_full()
29336 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29337 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29338 }))
29339 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29340 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29341 })),
29342 )
29343 })
29344 }
29345}
29346
29347#[derive(Debug, Clone, Copy, PartialEq)]
29348pub struct LineHighlight {
29349 pub background: Background,
29350 pub border: Option<gpui::Hsla>,
29351 pub include_gutter: bool,
29352 pub type_id: Option<TypeId>,
29353}
29354
29355struct LineManipulationResult {
29356 pub new_text: String,
29357 pub line_count_before: usize,
29358 pub line_count_after: usize,
29359}
29360
29361fn render_diff_hunk_controls(
29362 row: u32,
29363 status: &DiffHunkStatus,
29364 hunk_range: Range<Anchor>,
29365 is_created_file: bool,
29366 line_height: Pixels,
29367 editor: &Entity<Editor>,
29368 _window: &mut Window,
29369 cx: &mut App,
29370) -> AnyElement {
29371 h_flex()
29372 .h(line_height)
29373 .mr_1()
29374 .gap_1()
29375 .px_0p5()
29376 .pb_1()
29377 .border_x_1()
29378 .border_b_1()
29379 .border_color(cx.theme().colors().border_variant)
29380 .rounded_b_lg()
29381 .bg(cx.theme().colors().editor_background)
29382 .gap_1()
29383 .block_mouse_except_scroll()
29384 .shadow_md()
29385 .child(if status.has_secondary_hunk() {
29386 Button::new(("stage", row as u64), "Stage")
29387 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29388 .tooltip({
29389 let focus_handle = editor.focus_handle(cx);
29390 move |_window, cx| {
29391 Tooltip::for_action_in(
29392 "Stage Hunk",
29393 &::git::ToggleStaged,
29394 &focus_handle,
29395 cx,
29396 )
29397 }
29398 })
29399 .on_click({
29400 let editor = editor.clone();
29401 move |_event, _window, cx| {
29402 editor.update(cx, |editor, cx| {
29403 editor.stage_or_unstage_diff_hunks(
29404 true,
29405 vec![hunk_range.start..hunk_range.start],
29406 cx,
29407 );
29408 });
29409 }
29410 })
29411 } else {
29412 Button::new(("unstage", row as u64), "Unstage")
29413 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29414 .tooltip({
29415 let focus_handle = editor.focus_handle(cx);
29416 move |_window, cx| {
29417 Tooltip::for_action_in(
29418 "Unstage Hunk",
29419 &::git::ToggleStaged,
29420 &focus_handle,
29421 cx,
29422 )
29423 }
29424 })
29425 .on_click({
29426 let editor = editor.clone();
29427 move |_event, _window, cx| {
29428 editor.update(cx, |editor, cx| {
29429 editor.stage_or_unstage_diff_hunks(
29430 false,
29431 vec![hunk_range.start..hunk_range.start],
29432 cx,
29433 );
29434 });
29435 }
29436 })
29437 })
29438 .child(
29439 Button::new(("restore", row as u64), "Restore")
29440 .tooltip({
29441 let focus_handle = editor.focus_handle(cx);
29442 move |_window, cx| {
29443 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29444 }
29445 })
29446 .on_click({
29447 let editor = editor.clone();
29448 move |_event, window, cx| {
29449 editor.update(cx, |editor, cx| {
29450 let snapshot = editor.snapshot(window, cx);
29451 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29452 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29453 });
29454 }
29455 })
29456 .disabled(is_created_file),
29457 )
29458 .when(
29459 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29460 |el| {
29461 el.child(
29462 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29463 .shape(IconButtonShape::Square)
29464 .icon_size(IconSize::Small)
29465 // .disabled(!has_multiple_hunks)
29466 .tooltip({
29467 let focus_handle = editor.focus_handle(cx);
29468 move |_window, cx| {
29469 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29470 }
29471 })
29472 .on_click({
29473 let editor = editor.clone();
29474 move |_event, window, cx| {
29475 editor.update(cx, |editor, cx| {
29476 let snapshot = editor.snapshot(window, cx);
29477 let position =
29478 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29479 editor.go_to_hunk_before_or_after_position(
29480 &snapshot,
29481 position,
29482 Direction::Next,
29483 true,
29484 window,
29485 cx,
29486 );
29487 editor.expand_selected_diff_hunks(cx);
29488 });
29489 }
29490 }),
29491 )
29492 .child(
29493 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29494 .shape(IconButtonShape::Square)
29495 .icon_size(IconSize::Small)
29496 // .disabled(!has_multiple_hunks)
29497 .tooltip({
29498 let focus_handle = editor.focus_handle(cx);
29499 move |_window, cx| {
29500 Tooltip::for_action_in(
29501 "Previous Hunk",
29502 &GoToPreviousHunk,
29503 &focus_handle,
29504 cx,
29505 )
29506 }
29507 })
29508 .on_click({
29509 let editor = editor.clone();
29510 move |_event, window, cx| {
29511 editor.update(cx, |editor, cx| {
29512 let snapshot = editor.snapshot(window, cx);
29513 let point =
29514 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29515 editor.go_to_hunk_before_or_after_position(
29516 &snapshot,
29517 point,
29518 Direction::Prev,
29519 true,
29520 window,
29521 cx,
29522 );
29523 editor.expand_selected_diff_hunks(cx);
29524 });
29525 }
29526 }),
29527 )
29528 },
29529 )
29530 .into_any_element()
29531}
29532
29533pub fn multibuffer_context_lines(cx: &App) -> u32 {
29534 EditorSettings::try_get(cx)
29535 .map(|settings| settings.excerpt_context_lines)
29536 .unwrap_or(2)
29537 .min(32)
29538}