1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod document_colors;
21mod document_symbols;
22mod editor_settings;
23mod element;
24mod folding_ranges;
25mod git;
26mod highlight_matching_bracket;
27mod hover_links;
28pub mod hover_popover;
29mod indent_guides;
30mod inlays;
31pub mod items;
32mod jsx_tag_auto_close;
33mod linked_editing_ranges;
34mod lsp_ext;
35mod mouse_context_menu;
36pub mod movement;
37mod persistence;
38mod runnables;
39mod rust_analyzer_ext;
40pub mod scroll;
41mod selections_collection;
42pub mod semantic_tokens;
43mod split;
44pub mod split_editor_view;
45
46#[cfg(test)]
47mod code_completion_tests;
48#[cfg(test)]
49mod edit_prediction_tests;
50#[cfg(test)]
51mod editor_tests;
52mod signature_help;
53#[cfg(any(test, feature = "test-support"))]
54pub mod test;
55
56pub(crate) use actions::*;
57pub use display_map::{
58 ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder, HighlightKey,
59 SemanticTokenHighlight,
60};
61pub use edit_prediction_types::Direction;
62pub use editor_settings::{
63 CompletionDetailAlignment, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode,
64 EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings,
65 ShowMinimap,
66};
67pub use element::{
68 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
69 render_breadcrumb_text,
70};
71pub use git::blame::BlameRenderer;
72pub use hover_popover::hover_markdown_style;
73pub use inlays::Inlay;
74pub use items::MAX_TAB_TITLE_LEN;
75pub use linked_editing_ranges::LinkedEdits;
76pub use lsp::CompletionContext;
77pub use lsp_ext::lsp_tasks;
78pub use multi_buffer::{
79 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
80 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
81 ToPoint,
82};
83pub use split::{SplittableEditor, ToggleSplitDiff};
84pub use split_editor_view::SplitEditorView;
85pub use text::Bias;
86
87use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
88use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
89use anyhow::{Context as _, Result, anyhow, bail};
90use blink_manager::BlinkManager;
91use buffer_diff::DiffHunkStatus;
92use client::{Collaborator, ParticipantIndex, parse_zed_link};
93use clock::ReplicaId;
94use code_context_menus::{
95 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
96 CompletionsMenu, ContextMenuOrigin,
97};
98use collections::{BTreeMap, HashMap, HashSet, VecDeque};
99use convert_case::{Case, Casing};
100use dap::TelemetrySpawnLocation;
101use display_map::*;
102use document_colors::LspColorData;
103use edit_prediction_types::{
104 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDiscardReason,
105 EditPredictionGranularity, SuggestionDisplayType,
106};
107use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
108use element::{LineWithInvisibles, PositionMap, layout_line};
109use futures::{
110 FutureExt,
111 future::{self, Shared, join},
112};
113use fuzzy::{StringMatch, StringMatchCandidate};
114use git::blame::{GitBlame, GlobalBlameRenderer};
115use gpui::{
116 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
117 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
118 DispatchPhase, Edges, Entity, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
119 FocusOutEvent, Focusable, FontId, FontStyle, FontWeight, Global, HighlightStyle, Hsla,
120 KeyContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement,
121 Pixels, PressureStage, Render, ScrollHandle, SharedString, SharedUri, Size, Stateful, Styled,
122 Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
123 UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window, div, point, prelude::*,
124 pulsating_between, px, relative, size,
125};
126use hover_links::{HoverLink, HoveredLinkState, find_file};
127use hover_popover::{HoverState, hide_hover};
128use indent_guides::ActiveIndentGuidesState;
129use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
130use itertools::{Either, Itertools};
131use language::{
132 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
133 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
134 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
135 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, LocalFile, OffsetRangeExt,
136 OutlineItem, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
137 WordsQuery,
138 language_settings::{
139 self, AllLanguageSettings, LanguageSettings, LspInsertMode, RewrapBehavior,
140 WordsCompletionMode, all_language_settings,
141 },
142 point_from_lsp, point_to_lsp, text_diff_with_options,
143};
144use linked_editing_ranges::refresh_linked_ranges;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId,
148};
149use markdown::Markdown;
150use mouse_context_menu::MouseContextMenu;
151use movement::TextLayoutDetails;
152use multi_buffer::{
153 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
154};
155use parking_lot::Mutex;
156use persistence::EditorDb;
157use project::{
158 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
159 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
160 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
161 ProjectItem, ProjectPath, ProjectTransaction,
162 debugger::{
163 breakpoint_store::{
164 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
165 BreakpointStore, BreakpointStoreEvent,
166 },
167 session::{Session, SessionEvent},
168 },
169 git_store::GitStoreEvent,
170 lsp_store::{
171 BufferSemanticTokens, CacheInlayHints, CompletionDocumentation, FormatTrigger,
172 LspFormatTarget, OpenLspBufferHandle, RefreshForServer,
173 },
174 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
175};
176use rand::seq::SliceRandom;
177use regex::Regex;
178use rpc::{ErrorCode, ErrorExt, proto::PeerId};
179use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, SharedScrollAnchor};
180use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
181use serde::{Deserialize, Serialize};
182use settings::{
183 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
184 update_settings_file,
185};
186use smallvec::{SmallVec, smallvec};
187use snippet::Snippet;
188use std::{
189 any::{Any, TypeId},
190 borrow::Cow,
191 cell::{OnceCell, RefCell},
192 cmp::{self, Ordering, Reverse},
193 collections::hash_map,
194 iter::{self, Peekable},
195 mem,
196 num::NonZeroU32,
197 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
198 path::{Path, PathBuf},
199 rc::Rc,
200 sync::Arc,
201 time::{Duration, Instant},
202};
203use task::TaskVariables;
204use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
205use theme::{
206 AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
207};
208use theme_settings::{ThemeSettings, observe_buffer_font_size_adjustment};
209use ui::{
210 Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
211 IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
212 utils::WithRemSize,
213};
214use ui_input::ErasedEditor;
215use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
216use workspace::{
217 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
218 OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
219 TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
220 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
221 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
222 searchable::SearchEvent,
223};
224pub use zed_actions::editor::RevealInFileManager;
225use zed_actions::editor::{MoveDown, MoveUp};
226
227use crate::{
228 code_context_menus::CompletionsMenuSource,
229 editor_settings::MultiCursorModifier,
230 hover_links::{find_url, find_url_from_range},
231 inlays::{
232 InlineValueCache,
233 inlay_hints::{LspInlayHintData, inlay_hint_settings},
234 },
235 runnables::{ResolvedTasks, RunnableData, RunnableTasks},
236 scroll::{ScrollOffset, ScrollPixelOffset},
237 selections_collection::resolve_selections_wrapping_blocks,
238 semantic_tokens::SemanticTokenState,
239 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
240};
241
242pub const FILE_HEADER_HEIGHT: u32 = 2;
243pub const BUFFER_HEADER_PADDING: Rems = rems(0.25);
244pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
245const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
246const MAX_LINE_LEN: usize = 1024;
247const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
248const MAX_SELECTION_HISTORY_LEN: usize = 1024;
249pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
250#[doc(hidden)]
251pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
252pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
253
254pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
255pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
256pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
257pub const LSP_REQUEST_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(50);
258
259pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
260pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
261
262pub type RenderDiffHunkControlsFn = Arc<
263 dyn Fn(
264 u32,
265 &DiffHunkStatus,
266 Range<Anchor>,
267 bool,
268 Pixels,
269 &Entity<Editor>,
270 &mut Window,
271 &mut App,
272 ) -> AnyElement,
273>;
274
275enum ReportEditorEvent {
276 Saved { auto_saved: bool },
277 EditorOpened,
278 Closed,
279}
280
281impl ReportEditorEvent {
282 pub fn event_type(&self) -> &'static str {
283 match self {
284 Self::Saved { .. } => "Editor Saved",
285 Self::EditorOpened => "Editor Opened",
286 Self::Closed => "Editor Closed",
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293
294pub enum ConflictsOuter {}
295pub enum ConflictsOurs {}
296pub enum ConflictsTheirs {}
297pub enum ConflictsOursMarker {}
298pub enum ConflictsTheirsMarker {}
299
300pub struct HunkAddedColor;
301pub struct HunkRemovedColor;
302
303#[derive(Debug, Copy, Clone, PartialEq, Eq)]
304pub enum Navigated {
305 Yes,
306 No,
307}
308
309impl Navigated {
310 pub fn from_bool(yes: bool) -> Navigated {
311 if yes { Navigated::Yes } else { Navigated::No }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316enum DisplayDiffHunk {
317 Folded {
318 display_row: DisplayRow,
319 },
320 Unfolded {
321 is_created_file: bool,
322 diff_base_byte_range: Range<usize>,
323 display_row_range: Range<DisplayRow>,
324 multi_buffer_range: Range<Anchor>,
325 status: DiffHunkStatus,
326 word_diffs: Vec<Range<MultiBufferOffset>>,
327 },
328}
329
330pub enum HideMouseCursorOrigin {
331 TypingAction,
332 MovementAction,
333}
334
335pub fn init(cx: &mut App) {
336 cx.set_global(GlobalBlameRenderer(Arc::new(())));
337 cx.set_global(breadcrumbs::RenderBreadcrumbText(render_breadcrumb_text));
338
339 workspace::register_project_item::<Editor>(cx);
340 workspace::FollowableViewRegistry::register::<Editor>(cx);
341 workspace::register_serializable_item::<Editor>(cx);
342
343 cx.observe_new(
344 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
345 workspace.register_action(Editor::new_file);
346 workspace.register_action(Editor::new_file_split);
347 workspace.register_action(Editor::new_file_vertical);
348 workspace.register_action(Editor::new_file_horizontal);
349 workspace.register_action(Editor::cancel_language_server_work);
350 workspace.register_action(Editor::toggle_focus);
351 },
352 )
353 .detach();
354
355 cx.on_action(move |_: &workspace::NewFile, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 Editor::new_file(workspace, &Default::default(), window, cx)
364 },
365 )
366 .detach_and_log_err(cx);
367 }
368 })
369 .on_action(move |_: &workspace::NewWindow, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 cx.activate(true);
378 Editor::new_file(workspace, &Default::default(), window, cx)
379 },
380 )
381 .detach_and_log_err(cx);
382 }
383 });
384 _ = ui_input::ERASED_EDITOR_FACTORY.set(|window, cx| {
385 Arc::new(ErasedEditorImpl(
386 cx.new(|cx| Editor::single_line(window, cx)),
387 )) as Arc<dyn ErasedEditor>
388 });
389 _ = multi_buffer::EXCERPT_CONTEXT_LINES.set(multibuffer_context_lines);
390}
391
392pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
393 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
394}
395
396pub trait DiagnosticRenderer {
397 fn render_group(
398 &self,
399 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
400 buffer_id: BufferId,
401 snapshot: EditorSnapshot,
402 editor: WeakEntity<Editor>,
403 language_registry: Option<Arc<LanguageRegistry>>,
404 cx: &mut App,
405 ) -> Vec<BlockProperties<Anchor>>;
406
407 fn render_hover(
408 &self,
409 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
410 range: Range<Point>,
411 buffer_id: BufferId,
412 language_registry: Option<Arc<LanguageRegistry>>,
413 cx: &mut App,
414 ) -> Option<Entity<markdown::Markdown>>;
415
416 fn open_link(
417 &self,
418 editor: &mut Editor,
419 link: SharedString,
420 window: &mut Window,
421 cx: &mut Context<Editor>,
422 );
423}
424
425pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
426
427impl GlobalDiagnosticRenderer {
428 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
429 cx.try_global::<Self>().map(|g| g.0.clone())
430 }
431}
432
433impl gpui::Global for GlobalDiagnosticRenderer {}
434pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
435 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
436}
437
438pub struct SearchWithinRange;
439
440trait InvalidationRegion {
441 fn ranges(&self) -> &[Range<Anchor>];
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum SelectPhase {
446 Begin {
447 position: DisplayPoint,
448 add: bool,
449 click_count: usize,
450 },
451 BeginColumnar {
452 position: DisplayPoint,
453 reset: bool,
454 mode: ColumnarMode,
455 goal_column: u32,
456 },
457 Extend {
458 position: DisplayPoint,
459 click_count: usize,
460 },
461 Update {
462 position: DisplayPoint,
463 goal_column: u32,
464 scroll_delta: gpui::Point<f32>,
465 },
466 End,
467}
468
469#[derive(Clone, Debug, PartialEq)]
470pub enum ColumnarMode {
471 FromMouse,
472 FromSelection,
473}
474
475#[derive(Clone, Debug)]
476pub enum SelectMode {
477 Character,
478 Word(Range<Anchor>),
479 Line(Range<Anchor>),
480 All,
481}
482
483#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
484pub enum SizingBehavior {
485 /// The editor will layout itself using `size_full` and will include the vertical
486 /// scroll margin as requested by user settings.
487 #[default]
488 Default,
489 /// The editor will layout itself using `size_full`, but will not have any
490 /// vertical overscroll.
491 ExcludeOverscrollMargin,
492 /// The editor will request a vertical size according to its content and will be
493 /// layouted without a vertical scroll margin.
494 SizeByContent,
495}
496
497#[derive(Clone, PartialEq, Eq, Debug)]
498pub enum EditorMode {
499 SingleLine,
500 AutoHeight {
501 min_lines: usize,
502 max_lines: Option<usize>,
503 },
504 Full {
505 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
506 scale_ui_elements_with_buffer_font_size: bool,
507 /// When set to `true`, the editor will render a background for the active line.
508 show_active_line_background: bool,
509 /// Determines the sizing behavior for this editor
510 sizing_behavior: SizingBehavior,
511 },
512 Minimap {
513 parent: WeakEntity<Editor>,
514 },
515}
516
517impl EditorMode {
518 pub fn full() -> Self {
519 Self::Full {
520 scale_ui_elements_with_buffer_font_size: true,
521 show_active_line_background: true,
522 sizing_behavior: SizingBehavior::Default,
523 }
524 }
525
526 #[inline]
527 pub fn is_full(&self) -> bool {
528 matches!(self, Self::Full { .. })
529 }
530
531 #[inline]
532 pub fn is_single_line(&self) -> bool {
533 matches!(self, Self::SingleLine { .. })
534 }
535
536 #[inline]
537 fn is_minimap(&self) -> bool {
538 matches!(self, Self::Minimap { .. })
539 }
540}
541
542#[derive(Copy, Clone, Debug)]
543pub enum SoftWrap {
544 /// Prefer not to wrap at all.
545 ///
546 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
547 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
548 GitDiff,
549 /// Prefer a single line generally, unless an overly long line is encountered.
550 None,
551 /// Soft wrap lines that exceed the editor width.
552 EditorWidth,
553 /// Soft wrap lines at the preferred line length.
554 Column(u32),
555 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
556 Bounded(u32),
557}
558
559#[derive(Clone)]
560pub struct EditorStyle {
561 pub background: Hsla,
562 pub border: Hsla,
563 pub local_player: PlayerColor,
564 pub text: TextStyle,
565 pub scrollbar_width: Pixels,
566 pub syntax: Arc<SyntaxTheme>,
567 pub status: StatusColors,
568 pub inlay_hints_style: HighlightStyle,
569 pub edit_prediction_styles: EditPredictionStyles,
570 pub unnecessary_code_fade: f32,
571 pub show_underlines: bool,
572}
573
574impl Default for EditorStyle {
575 fn default() -> Self {
576 Self {
577 background: Hsla::default(),
578 border: Hsla::default(),
579 local_player: PlayerColor::default(),
580 text: TextStyle::default(),
581 scrollbar_width: Pixels::default(),
582 syntax: Default::default(),
583 // HACK: Status colors don't have a real default.
584 // We should look into removing the status colors from the editor
585 // style and retrieve them directly from the theme.
586 status: StatusColors::dark(),
587 inlay_hints_style: HighlightStyle::default(),
588 edit_prediction_styles: EditPredictionStyles {
589 insertion: HighlightStyle::default(),
590 whitespace: HighlightStyle::default(),
591 },
592 unnecessary_code_fade: Default::default(),
593 show_underlines: true,
594 }
595 }
596}
597
598pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
599 let show_background = AllLanguageSettings::get_global(cx)
600 .defaults
601 .inlay_hints
602 .show_background;
603
604 let mut style = cx
605 .theme()
606 .syntax()
607 .style_for_name("hint")
608 .unwrap_or_default();
609
610 if style.color.is_none() {
611 style.color = Some(cx.theme().status().hint);
612 }
613
614 if !show_background {
615 style.background_color = None;
616 return style;
617 }
618
619 if style.background_color.is_none() {
620 style.background_color = Some(cx.theme().status().hint_background);
621 }
622
623 style
624}
625
626pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
627 EditPredictionStyles {
628 insertion: HighlightStyle {
629 color: Some(cx.theme().status().predictive),
630 ..HighlightStyle::default()
631 },
632 whitespace: HighlightStyle {
633 background_color: Some(cx.theme().status().created_background),
634 ..HighlightStyle::default()
635 },
636 }
637}
638
639type CompletionId = usize;
640
641pub(crate) enum EditDisplayMode {
642 TabAccept,
643 DiffPopover,
644 Inline,
645}
646
647enum EditPrediction {
648 Edit {
649 edits: Vec<(Range<Anchor>, Arc<str>)>,
650 /// Predicted cursor position as (anchor, offset_from_anchor).
651 /// The anchor is in multibuffer coordinates; after applying edits,
652 /// resolve the anchor and add the offset to get the final cursor position.
653 cursor_position: Option<(Anchor, usize)>,
654 edit_preview: Option<EditPreview>,
655 display_mode: EditDisplayMode,
656 snapshot: BufferSnapshot,
657 },
658 /// Move to a specific location in the active editor
659 MoveWithin {
660 target: Anchor,
661 snapshot: BufferSnapshot,
662 },
663 /// Move to a specific location in a different editor (not the active one)
664 MoveOutside {
665 target: language::Anchor,
666 snapshot: BufferSnapshot,
667 },
668}
669
670struct EditPredictionState {
671 inlay_ids: Vec<InlayId>,
672 completion: EditPrediction,
673 completion_id: Option<SharedString>,
674 invalidation_range: Option<Range<Anchor>>,
675}
676
677enum EditPredictionSettings {
678 Disabled,
679 Enabled {
680 show_in_menu: bool,
681 preview_requires_modifier: bool,
682 },
683}
684
685#[derive(Debug, Clone)]
686struct InlineDiagnostic {
687 message: SharedString,
688 group_id: usize,
689 is_primary: bool,
690 start: Point,
691 severity: lsp::DiagnosticSeverity,
692}
693
694pub enum MenuEditPredictionsPolicy {
695 Never,
696 ByProvider,
697}
698
699pub enum EditPredictionPreview {
700 /// Modifier is not pressed
701 Inactive { released_too_fast: bool },
702 /// Modifier pressed
703 Active {
704 since: Instant,
705 previous_scroll_position: Option<SharedScrollAnchor>,
706 },
707}
708
709#[derive(Copy, Clone, Eq, PartialEq)]
710enum EditPredictionKeybindSurface {
711 Inline,
712 CursorPopoverCompact,
713 CursorPopoverExpanded,
714}
715
716#[derive(Copy, Clone, Eq, PartialEq, Debug)]
717enum EditPredictionKeybindAction {
718 Accept,
719 Preview,
720}
721
722struct EditPredictionKeybindDisplay {
723 #[cfg(test)]
724 accept_keystroke: Option<gpui::KeybindingKeystroke>,
725 #[cfg(test)]
726 preview_keystroke: Option<gpui::KeybindingKeystroke>,
727 displayed_keystroke: Option<gpui::KeybindingKeystroke>,
728 action: EditPredictionKeybindAction,
729 missing_accept_keystroke: bool,
730 show_hold_label: bool,
731}
732
733impl EditPredictionPreview {
734 pub fn released_too_fast(&self) -> bool {
735 match self {
736 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
737 EditPredictionPreview::Active { .. } => false,
738 }
739 }
740
741 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<SharedScrollAnchor>) {
742 if let EditPredictionPreview::Active {
743 previous_scroll_position,
744 ..
745 } = self
746 {
747 *previous_scroll_position = scroll_position;
748 }
749 }
750}
751
752pub struct ContextMenuOptions {
753 pub min_entries_visible: usize,
754 pub max_entries_visible: usize,
755 pub placement: Option<ContextMenuPlacement>,
756}
757
758#[derive(Debug, Clone, PartialEq, Eq)]
759pub enum ContextMenuPlacement {
760 Above,
761 Below,
762}
763
764#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
765struct EditorActionId(usize);
766
767impl EditorActionId {
768 pub fn post_inc(&mut self) -> Self {
769 let answer = self.0;
770
771 *self = Self(answer + 1);
772
773 Self(answer)
774 }
775}
776
777// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
778// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
779
780type BackgroundHighlight = (
781 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
782 Arc<[Range<Anchor>]>,
783);
784type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
785
786#[derive(Default)]
787struct ScrollbarMarkerState {
788 scrollbar_size: Size<Pixels>,
789 dirty: bool,
790 markers: Arc<[PaintQuad]>,
791 pending_refresh: Option<Task<Result<()>>>,
792}
793
794impl ScrollbarMarkerState {
795 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
796 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
797 }
798}
799
800#[derive(Clone, Copy, PartialEq, Eq)]
801pub enum MinimapVisibility {
802 Disabled,
803 Enabled {
804 /// The configuration currently present in the users settings.
805 setting_configuration: bool,
806 /// Whether to override the currently set visibility from the users setting.
807 toggle_override: bool,
808 },
809}
810
811impl MinimapVisibility {
812 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
813 if mode.is_full() {
814 Self::Enabled {
815 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
816 toggle_override: false,
817 }
818 } else {
819 Self::Disabled
820 }
821 }
822
823 fn hidden(&self) -> Self {
824 match *self {
825 Self::Enabled {
826 setting_configuration,
827 ..
828 } => Self::Enabled {
829 setting_configuration,
830 toggle_override: setting_configuration,
831 },
832 Self::Disabled => Self::Disabled,
833 }
834 }
835
836 fn disabled(&self) -> bool {
837 matches!(*self, Self::Disabled)
838 }
839
840 fn settings_visibility(&self) -> bool {
841 match *self {
842 Self::Enabled {
843 setting_configuration,
844 ..
845 } => setting_configuration,
846 _ => false,
847 }
848 }
849
850 fn visible(&self) -> bool {
851 match *self {
852 Self::Enabled {
853 setting_configuration,
854 toggle_override,
855 } => setting_configuration ^ toggle_override,
856 _ => false,
857 }
858 }
859
860 fn toggle_visibility(&self) -> Self {
861 match *self {
862 Self::Enabled {
863 toggle_override,
864 setting_configuration,
865 } => Self::Enabled {
866 setting_configuration,
867 toggle_override: !toggle_override,
868 },
869 Self::Disabled => Self::Disabled,
870 }
871 }
872}
873
874#[derive(Debug, Clone, Copy, PartialEq, Eq)]
875pub enum BufferSerialization {
876 All,
877 NonDirtyBuffers,
878}
879
880impl BufferSerialization {
881 fn new(restore_unsaved_buffers: bool) -> Self {
882 if restore_unsaved_buffers {
883 Self::All
884 } else {
885 Self::NonDirtyBuffers
886 }
887 }
888}
889
890/// Addons allow storing per-editor state in other crates (e.g. Vim)
891pub trait Addon: 'static {
892 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
893
894 fn render_buffer_header_controls(
895 &self,
896 _: &ExcerptInfo,
897 _: &Window,
898 _: &App,
899 ) -> Option<AnyElement> {
900 None
901 }
902
903 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
904 None
905 }
906
907 fn to_any(&self) -> &dyn std::any::Any;
908
909 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
910 None
911 }
912}
913
914struct ChangeLocation {
915 current: Option<Vec<Anchor>>,
916 original: Vec<Anchor>,
917}
918impl ChangeLocation {
919 fn locations(&self) -> &[Anchor] {
920 self.current.as_ref().unwrap_or(&self.original)
921 }
922}
923
924/// A set of caret positions, registered when the editor was edited.
925pub struct ChangeList {
926 changes: Vec<ChangeLocation>,
927 /// Currently "selected" change.
928 position: Option<usize>,
929}
930
931impl ChangeList {
932 pub fn new() -> Self {
933 Self {
934 changes: Vec::new(),
935 position: None,
936 }
937 }
938
939 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
940 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
941 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
942 if self.changes.is_empty() {
943 return None;
944 }
945
946 let prev = self.position.unwrap_or(self.changes.len());
947 let next = if direction == Direction::Prev {
948 prev.saturating_sub(count)
949 } else {
950 (prev + count).min(self.changes.len() - 1)
951 };
952 self.position = Some(next);
953 self.changes.get(next).map(|change| change.locations())
954 }
955
956 /// Adds a new change to the list, resetting the change list position.
957 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
958 self.position.take();
959 if let Some(last) = self.changes.last_mut()
960 && group
961 {
962 last.current = Some(new_positions)
963 } else {
964 self.changes.push(ChangeLocation {
965 original: new_positions,
966 current: None,
967 });
968 }
969 }
970
971 pub fn last(&self) -> Option<&[Anchor]> {
972 self.changes.last().map(|change| change.locations())
973 }
974
975 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
976 self.changes.last().map(|change| change.original.as_slice())
977 }
978
979 pub fn invert_last_group(&mut self) {
980 if let Some(last) = self.changes.last_mut()
981 && let Some(current) = last.current.as_mut()
982 {
983 mem::swap(&mut last.original, current);
984 }
985 }
986}
987
988#[derive(Clone)]
989struct InlineBlamePopoverState {
990 scroll_handle: ScrollHandle,
991 commit_message: Option<ParsedCommitMessage>,
992 markdown: Entity<Markdown>,
993}
994
995struct InlineBlamePopover {
996 position: gpui::Point<Pixels>,
997 hide_task: Option<Task<()>>,
998 popover_bounds: Option<Bounds<Pixels>>,
999 popover_state: InlineBlamePopoverState,
1000 keyboard_grace: bool,
1001}
1002
1003enum SelectionDragState {
1004 /// State when no drag related activity is detected.
1005 None,
1006 /// State when the mouse is down on a selection that is about to be dragged.
1007 ReadyToDrag {
1008 selection: Selection<Anchor>,
1009 click_position: gpui::Point<Pixels>,
1010 mouse_down_time: Instant,
1011 },
1012 /// State when the mouse is dragging the selection in the editor.
1013 Dragging {
1014 selection: Selection<Anchor>,
1015 drop_cursor: Selection<Anchor>,
1016 hide_drop_cursor: bool,
1017 },
1018}
1019
1020enum ColumnarSelectionState {
1021 FromMouse {
1022 selection_tail: Anchor,
1023 display_point: Option<DisplayPoint>,
1024 },
1025 FromSelection {
1026 selection_tail: Anchor,
1027 },
1028}
1029
1030/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1031/// a breakpoint on them.
1032#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1033struct PhantomBreakpointIndicator {
1034 display_row: DisplayRow,
1035 /// There's a small debounce between hovering over the line and showing the indicator.
1036 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1037 is_active: bool,
1038 collides_with_existing_breakpoint: bool,
1039}
1040
1041/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1042/// in diff view mode.
1043#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1044pub(crate) struct PhantomDiffReviewIndicator {
1045 /// The starting anchor of the selection (or the only row if not dragging).
1046 pub start: Anchor,
1047 /// The ending anchor of the selection. Equal to start_anchor for single-line selection.
1048 pub end: Anchor,
1049 /// There's a small debounce between hovering over the line and showing the indicator.
1050 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1051 pub is_active: bool,
1052}
1053
1054#[derive(Clone, Debug)]
1055pub(crate) struct DiffReviewDragState {
1056 pub start_anchor: Anchor,
1057 pub current_anchor: Anchor,
1058}
1059
1060impl DiffReviewDragState {
1061 pub fn row_range(&self, snapshot: &DisplaySnapshot) -> std::ops::RangeInclusive<DisplayRow> {
1062 let start = self.start_anchor.to_display_point(snapshot).row();
1063 let current = self.current_anchor.to_display_point(snapshot).row();
1064
1065 (start..=current).sorted()
1066 }
1067}
1068
1069/// Identifies a specific hunk in the diff buffer.
1070/// Used as a key to group comments by their location.
1071#[derive(Clone, Debug)]
1072pub struct DiffHunkKey {
1073 /// The file path (relative to worktree) this hunk belongs to.
1074 pub file_path: Arc<util::rel_path::RelPath>,
1075 /// An anchor at the start of the hunk. This tracks position as the buffer changes.
1076 pub hunk_start_anchor: Anchor,
1077}
1078
1079/// A review comment stored locally before being sent to the Agent panel.
1080#[derive(Clone)]
1081pub struct StoredReviewComment {
1082 /// Unique identifier for this comment (for edit/delete operations).
1083 pub id: usize,
1084 /// The comment text entered by the user.
1085 pub comment: String,
1086 /// Anchors for the code range being reviewed.
1087 pub range: Range<Anchor>,
1088 /// Timestamp when the comment was created (for chronological ordering).
1089 pub created_at: Instant,
1090 /// Whether this comment is currently being edited inline.
1091 pub is_editing: bool,
1092}
1093
1094impl StoredReviewComment {
1095 pub fn new(id: usize, comment: String, anchor_range: Range<Anchor>) -> Self {
1096 Self {
1097 id,
1098 comment,
1099 range: anchor_range,
1100 created_at: Instant::now(),
1101 is_editing: false,
1102 }
1103 }
1104}
1105
1106/// Represents an active diff review overlay that appears when clicking the "Add Review" button.
1107pub(crate) struct DiffReviewOverlay {
1108 pub anchor_range: Range<Anchor>,
1109 /// The block ID for the overlay.
1110 pub block_id: CustomBlockId,
1111 /// The editor entity for the review input.
1112 pub prompt_editor: Entity<Editor>,
1113 /// The hunk key this overlay belongs to.
1114 pub hunk_key: DiffHunkKey,
1115 /// Whether the comments section is expanded.
1116 pub comments_expanded: bool,
1117 /// Editors for comments currently being edited inline.
1118 /// Key: comment ID, Value: Editor entity for inline editing.
1119 pub inline_edit_editors: HashMap<usize, Entity<Editor>>,
1120 /// Subscriptions for inline edit editors' action handlers.
1121 /// Key: comment ID, Value: Subscription keeping the Newline action handler alive.
1122 pub inline_edit_subscriptions: HashMap<usize, Subscription>,
1123 /// The current user's avatar URI for display in comment rows.
1124 pub user_avatar_uri: Option<SharedUri>,
1125 /// Subscription to keep the action handler alive.
1126 _subscription: Subscription,
1127}
1128
1129/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1130///
1131/// See the [module level documentation](self) for more information.
1132pub struct Editor {
1133 focus_handle: FocusHandle,
1134 last_focused_descendant: Option<WeakFocusHandle>,
1135 /// The text buffer being edited
1136 buffer: Entity<MultiBuffer>,
1137 /// Map of how text in the buffer should be displayed.
1138 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1139 pub display_map: Entity<DisplayMap>,
1140 placeholder_display_map: Option<Entity<DisplayMap>>,
1141 pub selections: SelectionsCollection,
1142 pub scroll_manager: ScrollManager,
1143 /// When inline assist editors are linked, they all render cursors because
1144 /// typing enters text into each of them, even the ones that aren't focused.
1145 pub(crate) show_cursor_when_unfocused: bool,
1146 columnar_selection_state: Option<ColumnarSelectionState>,
1147 add_selections_state: Option<AddSelectionsState>,
1148 select_next_state: Option<SelectNextState>,
1149 select_prev_state: Option<SelectNextState>,
1150 selection_history: SelectionHistory,
1151 defer_selection_effects: bool,
1152 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1153 autoclose_regions: Vec<AutocloseRegion>,
1154 snippet_stack: InvalidationStack<SnippetState>,
1155 select_syntax_node_history: SelectSyntaxNodeHistory,
1156 ime_transaction: Option<TransactionId>,
1157 pub diagnostics_max_severity: DiagnosticSeverity,
1158 active_diagnostics: ActiveDiagnostic,
1159 show_inline_diagnostics: bool,
1160 inline_diagnostics_update: Task<()>,
1161 inline_diagnostics_enabled: bool,
1162 diagnostics_enabled: bool,
1163 word_completions_enabled: bool,
1164 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1165 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1166 hard_wrap: Option<usize>,
1167 project: Option<Entity<Project>>,
1168 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1169 completion_provider: Option<Rc<dyn CompletionProvider>>,
1170 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1171 blink_manager: Entity<BlinkManager>,
1172 show_cursor_names: bool,
1173 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1174 pub show_local_selections: bool,
1175 mode: EditorMode,
1176 show_breadcrumbs: bool,
1177 show_gutter: bool,
1178 show_scrollbars: ScrollbarAxes,
1179 minimap_visibility: MinimapVisibility,
1180 offset_content: bool,
1181 disable_expand_excerpt_buttons: bool,
1182 delegate_expand_excerpts: bool,
1183 delegate_stage_and_restore: bool,
1184 delegate_open_excerpts: bool,
1185 enable_lsp_data: bool,
1186 enable_runnables: bool,
1187 show_line_numbers: Option<bool>,
1188 use_relative_line_numbers: Option<bool>,
1189 show_git_diff_gutter: Option<bool>,
1190 show_code_actions: Option<bool>,
1191 show_runnables: Option<bool>,
1192 show_breakpoints: Option<bool>,
1193 show_diff_review_button: bool,
1194 show_wrap_guides: Option<bool>,
1195 show_indent_guides: Option<bool>,
1196 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1197 highlight_order: usize,
1198 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1199 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1200 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1201 scrollbar_marker_state: ScrollbarMarkerState,
1202 active_indent_guides_state: ActiveIndentGuidesState,
1203 nav_history: Option<ItemNavHistory>,
1204 context_menu: RefCell<Option<CodeContextMenu>>,
1205 context_menu_options: Option<ContextMenuOptions>,
1206 mouse_context_menu: Option<MouseContextMenu>,
1207 completion_tasks: Vec<(CompletionId, Task<()>)>,
1208 inline_blame_popover: Option<InlineBlamePopover>,
1209 inline_blame_popover_show_task: Option<Task<()>>,
1210 signature_help_state: SignatureHelpState,
1211 auto_signature_help: Option<bool>,
1212 find_all_references_task_sources: Vec<Anchor>,
1213 next_completion_id: CompletionId,
1214 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1215 code_actions_task: Option<Task<Result<()>>>,
1216 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1217 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1218 debounced_selection_highlight_complete: bool,
1219 document_highlights_task: Option<Task<()>>,
1220 linked_editing_range_task: Option<Task<Option<()>>>,
1221 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1222 pending_rename: Option<RenameState>,
1223 searchable: bool,
1224 cursor_shape: CursorShape,
1225 /// Whether the cursor is offset one character to the left when something is
1226 /// selected (needed for vim visual mode)
1227 cursor_offset_on_selection: bool,
1228 current_line_highlight: Option<CurrentLineHighlight>,
1229 /// Whether to collapse search match ranges to just their start position.
1230 /// When true, navigating to a match positions the cursor at the match
1231 /// without selecting the matched text.
1232 collapse_matches: bool,
1233 autoindent_mode: Option<AutoindentMode>,
1234 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1235 input_enabled: bool,
1236 expects_character_input: bool,
1237 use_modal_editing: bool,
1238 read_only: bool,
1239 leader_id: Option<CollaboratorId>,
1240 remote_id: Option<ViewId>,
1241 pub hover_state: HoverState,
1242 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1243 prev_pressure_stage: Option<PressureStage>,
1244 gutter_hovered: bool,
1245 hovered_link_state: Option<HoveredLinkState>,
1246 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1247 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1248 active_edit_prediction: Option<EditPredictionState>,
1249 /// Used to prevent flickering as the user types while the menu is open
1250 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1251 edit_prediction_settings: EditPredictionSettings,
1252 edit_predictions_hidden_for_vim_mode: bool,
1253 show_edit_predictions_override: Option<bool>,
1254 show_completions_on_input_override: Option<bool>,
1255 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1256 edit_prediction_preview: EditPredictionPreview,
1257 in_leading_whitespace: bool,
1258 next_inlay_id: usize,
1259 next_color_inlay_id: usize,
1260 _subscriptions: Vec<Subscription>,
1261 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1262 gutter_dimensions: GutterDimensions,
1263 style: Option<EditorStyle>,
1264 text_style_refinement: Option<TextStyleRefinement>,
1265 next_editor_action_id: EditorActionId,
1266 editor_actions: Rc<
1267 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1268 >,
1269 use_autoclose: bool,
1270 use_auto_surround: bool,
1271 auto_replace_emoji_shortcode: bool,
1272 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1273 show_git_blame_gutter: bool,
1274 show_git_blame_inline: bool,
1275 show_git_blame_inline_delay_task: Option<Task<()>>,
1276 git_blame_inline_enabled: bool,
1277 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1278 buffer_serialization: Option<BufferSerialization>,
1279 show_selection_menu: Option<bool>,
1280 blame: Option<Entity<GitBlame>>,
1281 blame_subscription: Option<Subscription>,
1282 custom_context_menu: Option<
1283 Box<
1284 dyn 'static
1285 + Fn(
1286 &mut Self,
1287 DisplayPoint,
1288 &mut Window,
1289 &mut Context<Self>,
1290 ) -> Option<Entity<ui::ContextMenu>>,
1291 >,
1292 >,
1293 last_bounds: Option<Bounds<Pixels>>,
1294 last_position_map: Option<Rc<PositionMap>>,
1295 expect_bounds_change: Option<Bounds<Pixels>>,
1296 runnables: RunnableData,
1297 breakpoint_store: Option<Entity<BreakpointStore>>,
1298 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1299 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1300 pub(crate) diff_review_drag_state: Option<DiffReviewDragState>,
1301 /// Active diff review overlays. Multiple overlays can be open simultaneously
1302 /// when hunks have comments stored.
1303 pub(crate) diff_review_overlays: Vec<DiffReviewOverlay>,
1304 /// Stored review comments grouped by hunk.
1305 /// Uses a Vec instead of HashMap because DiffHunkKey contains an Anchor
1306 /// which doesn't implement Hash/Eq in a way suitable for HashMap keys.
1307 stored_review_comments: Vec<(DiffHunkKey, Vec<StoredReviewComment>)>,
1308 /// Counter for generating unique comment IDs.
1309 next_review_comment_id: usize,
1310 hovered_diff_hunk_row: Option<DisplayRow>,
1311 pull_diagnostics_task: Task<()>,
1312 in_project_search: bool,
1313 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1314 breadcrumb_header: Option<String>,
1315 focused_block: Option<FocusedBlock>,
1316 next_scroll_position: NextScrollCursorCenterTopBottom,
1317 addons: HashMap<TypeId, Box<dyn Addon>>,
1318 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1319 load_diff_task: Option<Shared<Task<()>>>,
1320 /// Whether we are temporarily displaying a diff other than git's
1321 temporary_diff_override: bool,
1322 selection_mark_mode: bool,
1323 toggle_fold_multiple_buffers: Task<()>,
1324 _scroll_cursor_center_top_bottom_task: Task<()>,
1325 serialize_selections: Task<()>,
1326 serialize_folds: Task<()>,
1327 mouse_cursor_hidden: bool,
1328 minimap: Option<Entity<Self>>,
1329 hide_mouse_mode: HideMouseMode,
1330 pub change_list: ChangeList,
1331 inline_value_cache: InlineValueCache,
1332 number_deleted_lines: bool,
1333
1334 selection_drag_state: SelectionDragState,
1335 colors: Option<LspColorData>,
1336 post_scroll_update: Task<()>,
1337 refresh_colors_task: Task<()>,
1338 use_document_folding_ranges: bool,
1339 refresh_folding_ranges_task: Task<()>,
1340 inlay_hints: Option<LspInlayHintData>,
1341 folding_newlines: Task<()>,
1342 select_next_is_case_sensitive: Option<bool>,
1343 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1344 on_local_selections_changed:
1345 Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
1346 suppress_selection_callback: bool,
1347 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1348 accent_data: Option<AccentData>,
1349 bracket_fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1350 semantic_token_state: SemanticTokenState,
1351 pub(crate) refresh_matching_bracket_highlights_task: Task<()>,
1352 refresh_document_symbols_task: Shared<Task<()>>,
1353 lsp_document_symbols: HashMap<BufferId, Vec<OutlineItem<text::Anchor>>>,
1354 refresh_outline_symbols_at_cursor_at_cursor_task: Task<()>,
1355 outline_symbols_at_cursor: Option<(BufferId, Vec<OutlineItem<Anchor>>)>,
1356 sticky_headers_task: Task<()>,
1357 sticky_headers: Option<Vec<OutlineItem<Anchor>>>,
1358 pub(crate) colorize_brackets_task: Task<()>,
1359}
1360
1361#[derive(Debug, PartialEq)]
1362struct AccentData {
1363 colors: AccentColors,
1364 overrides: Vec<SharedString>,
1365}
1366
1367fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1368 if debounce_ms > 0 {
1369 Some(Duration::from_millis(debounce_ms))
1370 } else {
1371 None
1372 }
1373}
1374
1375#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1376enum NextScrollCursorCenterTopBottom {
1377 #[default]
1378 Center,
1379 Top,
1380 Bottom,
1381}
1382
1383impl NextScrollCursorCenterTopBottom {
1384 fn next(&self) -> Self {
1385 match self {
1386 Self::Center => Self::Top,
1387 Self::Top => Self::Bottom,
1388 Self::Bottom => Self::Center,
1389 }
1390 }
1391}
1392
1393#[derive(Clone)]
1394pub struct EditorSnapshot {
1395 pub mode: EditorMode,
1396 show_gutter: bool,
1397 offset_content: bool,
1398 show_line_numbers: Option<bool>,
1399 number_deleted_lines: bool,
1400 show_git_diff_gutter: Option<bool>,
1401 show_code_actions: Option<bool>,
1402 show_runnables: Option<bool>,
1403 show_breakpoints: Option<bool>,
1404 git_blame_gutter_max_author_length: Option<usize>,
1405 pub display_snapshot: DisplaySnapshot,
1406 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1407 is_focused: bool,
1408 scroll_anchor: SharedScrollAnchor,
1409 ongoing_scroll: OngoingScroll,
1410 current_line_highlight: CurrentLineHighlight,
1411 gutter_hovered: bool,
1412 semantic_tokens_enabled: bool,
1413}
1414
1415#[derive(Default, Debug, Clone, Copy)]
1416pub struct GutterDimensions {
1417 pub left_padding: Pixels,
1418 pub right_padding: Pixels,
1419 pub width: Pixels,
1420 pub margin: Pixels,
1421 pub git_blame_entries_width: Option<Pixels>,
1422}
1423
1424impl GutterDimensions {
1425 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1426 Self {
1427 margin: Self::default_gutter_margin(font_id, font_size, cx),
1428 ..Default::default()
1429 }
1430 }
1431
1432 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1433 -cx.text_system().descent(font_id, font_size)
1434 }
1435 /// The full width of the space taken up by the gutter.
1436 pub fn full_width(&self) -> Pixels {
1437 self.margin + self.width
1438 }
1439
1440 /// The width of the space reserved for the fold indicators,
1441 /// use alongside 'justify_end' and `gutter_width` to
1442 /// right align content with the line numbers
1443 pub fn fold_area_width(&self) -> Pixels {
1444 self.margin + self.right_padding
1445 }
1446}
1447
1448struct CharacterDimensions {
1449 em_width: Pixels,
1450 em_advance: Pixels,
1451 line_height: Pixels,
1452}
1453
1454#[derive(Debug)]
1455pub struct RemoteSelection {
1456 pub replica_id: ReplicaId,
1457 pub selection: Selection<Anchor>,
1458 pub cursor_shape: CursorShape,
1459 pub collaborator_id: CollaboratorId,
1460 pub line_mode: bool,
1461 pub user_name: Option<SharedString>,
1462 pub color: PlayerColor,
1463}
1464
1465#[derive(Clone, Debug)]
1466struct SelectionHistoryEntry {
1467 selections: Arc<[Selection<Anchor>]>,
1468 select_next_state: Option<SelectNextState>,
1469 select_prev_state: Option<SelectNextState>,
1470 add_selections_state: Option<AddSelectionsState>,
1471}
1472
1473#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1474enum SelectionHistoryMode {
1475 #[default]
1476 Normal,
1477 Undoing,
1478 Redoing,
1479 Skipping,
1480}
1481
1482#[derive(Clone, PartialEq, Eq, Hash)]
1483struct HoveredCursor {
1484 replica_id: ReplicaId,
1485 selection_id: usize,
1486}
1487
1488#[derive(Debug)]
1489/// SelectionEffects controls the side-effects of updating the selection.
1490///
1491/// The default behaviour does "what you mostly want":
1492/// - it pushes to the nav history if the cursor moved by >10 lines
1493/// - it re-triggers completion requests
1494/// - it scrolls to fit
1495///
1496/// You might want to modify these behaviours. For example when doing a "jump"
1497/// like go to definition, we always want to add to nav history; but when scrolling
1498/// in vim mode we never do.
1499///
1500/// Similarly, you might want to disable scrolling if you don't want the viewport to
1501/// move.
1502#[derive(Clone)]
1503pub struct SelectionEffects {
1504 nav_history: Option<bool>,
1505 completions: bool,
1506 scroll: Option<Autoscroll>,
1507}
1508
1509impl Default for SelectionEffects {
1510 fn default() -> Self {
1511 Self {
1512 nav_history: None,
1513 completions: true,
1514 scroll: Some(Autoscroll::fit()),
1515 }
1516 }
1517}
1518impl SelectionEffects {
1519 pub fn scroll(scroll: Autoscroll) -> Self {
1520 Self {
1521 scroll: Some(scroll),
1522 ..Default::default()
1523 }
1524 }
1525
1526 pub fn no_scroll() -> Self {
1527 Self {
1528 scroll: None,
1529 ..Default::default()
1530 }
1531 }
1532
1533 pub fn completions(self, completions: bool) -> Self {
1534 Self {
1535 completions,
1536 ..self
1537 }
1538 }
1539
1540 pub fn nav_history(self, nav_history: bool) -> Self {
1541 Self {
1542 nav_history: Some(nav_history),
1543 ..self
1544 }
1545 }
1546}
1547
1548struct DeferredSelectionEffectsState {
1549 changed: bool,
1550 effects: SelectionEffects,
1551 old_cursor_position: Anchor,
1552 history_entry: SelectionHistoryEntry,
1553}
1554
1555#[derive(Default)]
1556struct SelectionHistory {
1557 #[allow(clippy::type_complexity)]
1558 selections_by_transaction:
1559 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1560 mode: SelectionHistoryMode,
1561 undo_stack: VecDeque<SelectionHistoryEntry>,
1562 redo_stack: VecDeque<SelectionHistoryEntry>,
1563}
1564
1565impl SelectionHistory {
1566 #[track_caller]
1567 fn insert_transaction(
1568 &mut self,
1569 transaction_id: TransactionId,
1570 selections: Arc<[Selection<Anchor>]>,
1571 ) {
1572 if selections.is_empty() {
1573 log::error!(
1574 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1575 std::panic::Location::caller()
1576 );
1577 return;
1578 }
1579 self.selections_by_transaction
1580 .insert(transaction_id, (selections, None));
1581 }
1582
1583 #[allow(clippy::type_complexity)]
1584 fn transaction(
1585 &self,
1586 transaction_id: TransactionId,
1587 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1588 self.selections_by_transaction.get(&transaction_id)
1589 }
1590
1591 #[allow(clippy::type_complexity)]
1592 fn transaction_mut(
1593 &mut self,
1594 transaction_id: TransactionId,
1595 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1596 self.selections_by_transaction.get_mut(&transaction_id)
1597 }
1598
1599 fn push(&mut self, entry: SelectionHistoryEntry) {
1600 if !entry.selections.is_empty() {
1601 match self.mode {
1602 SelectionHistoryMode::Normal => {
1603 self.push_undo(entry);
1604 self.redo_stack.clear();
1605 }
1606 SelectionHistoryMode::Undoing => self.push_redo(entry),
1607 SelectionHistoryMode::Redoing => self.push_undo(entry),
1608 SelectionHistoryMode::Skipping => {}
1609 }
1610 }
1611 }
1612
1613 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1614 if self
1615 .undo_stack
1616 .back()
1617 .is_none_or(|e| e.selections != entry.selections)
1618 {
1619 self.undo_stack.push_back(entry);
1620 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1621 self.undo_stack.pop_front();
1622 }
1623 }
1624 }
1625
1626 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1627 if self
1628 .redo_stack
1629 .back()
1630 .is_none_or(|e| e.selections != entry.selections)
1631 {
1632 self.redo_stack.push_back(entry);
1633 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1634 self.redo_stack.pop_front();
1635 }
1636 }
1637 }
1638}
1639
1640#[derive(Clone, Copy)]
1641pub struct RowHighlightOptions {
1642 pub autoscroll: bool,
1643 pub include_gutter: bool,
1644}
1645
1646impl Default for RowHighlightOptions {
1647 fn default() -> Self {
1648 Self {
1649 autoscroll: Default::default(),
1650 include_gutter: true,
1651 }
1652 }
1653}
1654
1655struct RowHighlight {
1656 index: usize,
1657 range: Range<Anchor>,
1658 color: Hsla,
1659 options: RowHighlightOptions,
1660 type_id: TypeId,
1661}
1662
1663#[derive(Clone, Debug)]
1664struct AddSelectionsState {
1665 groups: Vec<AddSelectionsGroup>,
1666}
1667
1668#[derive(Clone, Debug)]
1669struct AddSelectionsGroup {
1670 above: bool,
1671 stack: Vec<usize>,
1672}
1673
1674#[derive(Clone)]
1675struct SelectNextState {
1676 query: AhoCorasick,
1677 wordwise: bool,
1678 done: bool,
1679}
1680
1681impl std::fmt::Debug for SelectNextState {
1682 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1683 f.debug_struct(std::any::type_name::<Self>())
1684 .field("wordwise", &self.wordwise)
1685 .field("done", &self.done)
1686 .finish()
1687 }
1688}
1689
1690#[derive(Debug)]
1691struct AutocloseRegion {
1692 selection_id: usize,
1693 range: Range<Anchor>,
1694 pair: BracketPair,
1695}
1696
1697#[derive(Debug)]
1698struct SnippetState {
1699 ranges: Vec<Vec<Range<Anchor>>>,
1700 active_index: usize,
1701 choices: Vec<Option<Vec<String>>>,
1702}
1703
1704#[doc(hidden)]
1705pub struct RenameState {
1706 pub range: Range<Anchor>,
1707 pub old_name: Arc<str>,
1708 pub editor: Entity<Editor>,
1709 block_id: CustomBlockId,
1710}
1711
1712struct InvalidationStack<T>(Vec<T>);
1713
1714struct RegisteredEditPredictionDelegate {
1715 provider: Arc<dyn EditPredictionDelegateHandle>,
1716 _subscription: Subscription,
1717}
1718
1719#[derive(Debug, PartialEq, Eq)]
1720pub struct ActiveDiagnosticGroup {
1721 pub active_range: Range<Anchor>,
1722 pub active_message: String,
1723 pub group_id: usize,
1724 pub blocks: HashSet<CustomBlockId>,
1725}
1726
1727#[derive(Debug, PartialEq, Eq)]
1728
1729pub(crate) enum ActiveDiagnostic {
1730 None,
1731 All,
1732 Group(ActiveDiagnosticGroup),
1733}
1734
1735#[derive(Serialize, Deserialize, Clone, Debug)]
1736pub struct ClipboardSelection {
1737 /// The number of bytes in this selection.
1738 pub len: usize,
1739 /// Whether this was a full-line selection.
1740 pub is_entire_line: bool,
1741 /// The indentation of the first line when this content was originally copied.
1742 pub first_line_indent: u32,
1743 #[serde(default)]
1744 pub file_path: Option<PathBuf>,
1745 #[serde(default)]
1746 pub line_range: Option<RangeInclusive<u32>>,
1747}
1748
1749impl ClipboardSelection {
1750 pub fn for_buffer(
1751 len: usize,
1752 is_entire_line: bool,
1753 range: Range<Point>,
1754 buffer: &MultiBufferSnapshot,
1755 project: Option<&Entity<Project>>,
1756 cx: &App,
1757 ) -> Self {
1758 let first_line_indent = buffer
1759 .indent_size_for_line(MultiBufferRow(range.start.row))
1760 .len;
1761
1762 let file_path = util::maybe!({
1763 let project = project?.read(cx);
1764 let file = buffer.file_at(range.start)?;
1765 let project_path = ProjectPath {
1766 worktree_id: file.worktree_id(cx),
1767 path: file.path().clone(),
1768 };
1769 project.absolute_path(&project_path, cx)
1770 });
1771
1772 let line_range = file_path.as_ref().and_then(|_| {
1773 let (_, start_point, start_excerpt_id) = buffer.point_to_buffer_point(range.start)?;
1774 let (_, end_point, end_excerpt_id) = buffer.point_to_buffer_point(range.end)?;
1775 if start_excerpt_id == end_excerpt_id {
1776 Some(start_point.row..=end_point.row)
1777 } else {
1778 None
1779 }
1780 });
1781
1782 Self {
1783 len,
1784 is_entire_line,
1785 first_line_indent,
1786 file_path,
1787 line_range,
1788 }
1789 }
1790}
1791
1792// selections, scroll behavior, was newest selection reversed
1793type SelectSyntaxNodeHistoryState = (
1794 Box<[Selection<Anchor>]>,
1795 SelectSyntaxNodeScrollBehavior,
1796 bool,
1797);
1798
1799#[derive(Default)]
1800struct SelectSyntaxNodeHistory {
1801 stack: Vec<SelectSyntaxNodeHistoryState>,
1802 // disable temporarily to allow changing selections without losing the stack
1803 pub disable_clearing: bool,
1804}
1805
1806impl SelectSyntaxNodeHistory {
1807 pub fn try_clear(&mut self) {
1808 if !self.disable_clearing {
1809 self.stack.clear();
1810 }
1811 }
1812
1813 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1814 self.stack.push(selection);
1815 }
1816
1817 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1818 self.stack.pop()
1819 }
1820}
1821
1822enum SelectSyntaxNodeScrollBehavior {
1823 CursorTop,
1824 FitSelection,
1825 CursorBottom,
1826}
1827
1828#[derive(Debug, Clone, Copy)]
1829pub(crate) struct NavigationData {
1830 cursor_anchor: Anchor,
1831 cursor_position: Point,
1832 scroll_anchor: ScrollAnchor,
1833 scroll_top_row: u32,
1834}
1835
1836#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1837pub enum GotoDefinitionKind {
1838 Symbol,
1839 Declaration,
1840 Type,
1841 Implementation,
1842}
1843
1844pub enum FormatTarget {
1845 Buffers(HashSet<Entity<Buffer>>),
1846 Ranges(Vec<Range<MultiBufferPoint>>),
1847}
1848
1849pub(crate) struct FocusedBlock {
1850 id: BlockId,
1851 focus_handle: WeakFocusHandle,
1852}
1853
1854#[derive(Clone, Debug)]
1855pub enum JumpData {
1856 MultiBufferRow {
1857 row: MultiBufferRow,
1858 line_offset_from_top: u32,
1859 },
1860 MultiBufferPoint {
1861 excerpt_id: ExcerptId,
1862 position: Point,
1863 anchor: text::Anchor,
1864 line_offset_from_top: u32,
1865 },
1866}
1867
1868pub enum MultibufferSelectionMode {
1869 First,
1870 All,
1871}
1872
1873#[derive(Clone, Copy, Debug, Default)]
1874pub struct RewrapOptions {
1875 pub override_language_settings: bool,
1876 pub preserve_existing_whitespace: bool,
1877 pub line_length: Option<usize>,
1878}
1879
1880impl Editor {
1881 pub fn single_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::SingleLine, buffer, None, window, cx)
1885 }
1886
1887 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1888 let buffer = cx.new(|cx| Buffer::local("", cx));
1889 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1890 Self::new(EditorMode::full(), buffer, None, window, cx)
1891 }
1892
1893 pub fn auto_height(
1894 min_lines: usize,
1895 max_lines: usize,
1896 window: &mut Window,
1897 cx: &mut Context<Self>,
1898 ) -> Self {
1899 let buffer = cx.new(|cx| Buffer::local("", cx));
1900 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1901 Self::new(
1902 EditorMode::AutoHeight {
1903 min_lines,
1904 max_lines: Some(max_lines),
1905 },
1906 buffer,
1907 None,
1908 window,
1909 cx,
1910 )
1911 }
1912
1913 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1914 /// The editor grows as tall as needed to fit its content.
1915 pub fn auto_height_unbounded(
1916 min_lines: usize,
1917 window: &mut Window,
1918 cx: &mut Context<Self>,
1919 ) -> Self {
1920 let buffer = cx.new(|cx| Buffer::local("", cx));
1921 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1922 Self::new(
1923 EditorMode::AutoHeight {
1924 min_lines,
1925 max_lines: None,
1926 },
1927 buffer,
1928 None,
1929 window,
1930 cx,
1931 )
1932 }
1933
1934 pub fn for_buffer(
1935 buffer: Entity<Buffer>,
1936 project: Option<Entity<Project>>,
1937 window: &mut Window,
1938 cx: &mut Context<Self>,
1939 ) -> Self {
1940 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1941 Self::new(EditorMode::full(), buffer, project, window, cx)
1942 }
1943
1944 pub fn for_multibuffer(
1945 buffer: Entity<MultiBuffer>,
1946 project: Option<Entity<Project>>,
1947 window: &mut Window,
1948 cx: &mut Context<Self>,
1949 ) -> Self {
1950 Self::new(EditorMode::full(), buffer, project, window, cx)
1951 }
1952
1953 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1954 let mut clone = Self::new(
1955 self.mode.clone(),
1956 self.buffer.clone(),
1957 self.project.clone(),
1958 window,
1959 cx,
1960 );
1961 let my_snapshot = self.display_map.update(cx, |display_map, cx| {
1962 let snapshot = display_map.snapshot(cx);
1963 clone.display_map.update(cx, |display_map, cx| {
1964 display_map.set_state(&snapshot, cx);
1965 });
1966 snapshot
1967 });
1968 let clone_snapshot = clone.display_map.update(cx, |map, cx| map.snapshot(cx));
1969 clone.folds_did_change(cx);
1970 clone.selections.clone_state(&self.selections);
1971 clone
1972 .scroll_manager
1973 .clone_state(&self.scroll_manager, &my_snapshot, &clone_snapshot, cx);
1974 clone.searchable = self.searchable;
1975 clone.read_only = self.read_only;
1976 clone.buffers_with_disabled_indent_guides =
1977 self.buffers_with_disabled_indent_guides.clone();
1978 clone
1979 }
1980
1981 pub fn new(
1982 mode: EditorMode,
1983 buffer: Entity<MultiBuffer>,
1984 project: Option<Entity<Project>>,
1985 window: &mut Window,
1986 cx: &mut Context<Self>,
1987 ) -> Self {
1988 Editor::new_internal(mode, buffer, project, None, window, cx)
1989 }
1990
1991 pub fn refresh_sticky_headers(
1992 &mut self,
1993 display_snapshot: &DisplaySnapshot,
1994 cx: &mut Context<Editor>,
1995 ) {
1996 if !self.mode.is_full() {
1997 return;
1998 }
1999 let multi_buffer = display_snapshot.buffer_snapshot();
2000 let scroll_anchor = self
2001 .scroll_manager
2002 .native_anchor(display_snapshot, cx)
2003 .anchor;
2004 let Some((excerpt_id, _, buffer)) = multi_buffer.as_singleton() else {
2005 return;
2006 };
2007 let buffer = buffer.clone();
2008
2009 let buffer_visible_start = scroll_anchor.text_anchor.to_point(&buffer);
2010 let max_row = buffer.max_point().row;
2011 let start_row = buffer_visible_start.row.min(max_row);
2012 let end_row = (buffer_visible_start.row + 10).min(max_row);
2013
2014 let syntax = self.style(cx).syntax.clone();
2015 let background_task = cx.background_spawn(async move {
2016 buffer
2017 .outline_items_containing(
2018 Point::new(start_row, 0)..Point::new(end_row, 0),
2019 true,
2020 Some(syntax.as_ref()),
2021 )
2022 .into_iter()
2023 .map(|outline_item| OutlineItem {
2024 depth: outline_item.depth,
2025 range: Anchor::range_in_buffer(excerpt_id, outline_item.range),
2026 source_range_for_text: Anchor::range_in_buffer(
2027 excerpt_id,
2028 outline_item.source_range_for_text,
2029 ),
2030 text: outline_item.text,
2031 highlight_ranges: outline_item.highlight_ranges,
2032 name_ranges: outline_item.name_ranges,
2033 body_range: outline_item
2034 .body_range
2035 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
2036 annotation_range: outline_item
2037 .annotation_range
2038 .map(|range| Anchor::range_in_buffer(excerpt_id, range)),
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 ) -> bool {
3034 match &completion.completion {
3035 EditPrediction::Edit {
3036 edits, snapshot, ..
3037 } => {
3038 let mut start_row: Option<u32> = None;
3039 let mut end_row: Option<u32> = None;
3040
3041 for (range, text) in edits {
3042 let edit_start_row = range.start.text_anchor.to_point(snapshot).row;
3043 let old_end_row = range.end.text_anchor.to_point(snapshot).row;
3044 let inserted_newline_count = text
3045 .as_ref()
3046 .chars()
3047 .filter(|character| *character == '\n')
3048 .count() as u32;
3049 let deleted_newline_count = old_end_row - edit_start_row;
3050 let preview_end_row = edit_start_row + inserted_newline_count;
3051
3052 start_row =
3053 Some(start_row.map_or(edit_start_row, |row| row.min(edit_start_row)));
3054 end_row = Some(end_row.map_or(preview_end_row, |row| row.max(preview_end_row)));
3055
3056 if deleted_newline_count > 1 {
3057 end_row = Some(end_row.map_or(old_end_row, |row| row.max(old_end_row)));
3058 }
3059 }
3060
3061 start_row
3062 .zip(end_row)
3063 .is_some_and(|(start_row, end_row)| end_row > start_row)
3064 }
3065 EditPrediction::MoveWithin { .. } | EditPrediction::MoveOutside { .. } => false,
3066 }
3067 }
3068
3069 fn edit_prediction_keybind_display(
3070 &self,
3071 surface: EditPredictionKeybindSurface,
3072 window: &mut Window,
3073 cx: &mut App,
3074 ) -> EditPredictionKeybindDisplay {
3075 let accept_keystroke =
3076 self.accept_edit_prediction_keystroke(EditPredictionGranularity::Full, window, cx);
3077 let preview_keystroke = self.preview_edit_prediction_keystroke(window, cx);
3078
3079 let action = match surface {
3080 EditPredictionKeybindSurface::Inline
3081 | EditPredictionKeybindSurface::CursorPopoverCompact => {
3082 if self.edit_prediction_requires_modifier() {
3083 EditPredictionKeybindAction::Preview
3084 } else {
3085 EditPredictionKeybindAction::Accept
3086 }
3087 }
3088 EditPredictionKeybindSurface::CursorPopoverExpanded => self
3089 .active_edit_prediction
3090 .as_ref()
3091 .filter(|completion| {
3092 self.edit_prediction_cursor_popover_prefers_preview(completion)
3093 })
3094 .map_or(EditPredictionKeybindAction::Accept, |_| {
3095 EditPredictionKeybindAction::Preview
3096 }),
3097 };
3098 #[cfg(test)]
3099 let preview_copy = preview_keystroke.clone();
3100 #[cfg(test)]
3101 let accept_copy = accept_keystroke.clone();
3102
3103 let displayed_keystroke = match surface {
3104 EditPredictionKeybindSurface::Inline => match action {
3105 EditPredictionKeybindAction::Accept => accept_keystroke,
3106 EditPredictionKeybindAction::Preview => preview_keystroke,
3107 },
3108 EditPredictionKeybindSurface::CursorPopoverCompact
3109 | EditPredictionKeybindSurface::CursorPopoverExpanded => match action {
3110 EditPredictionKeybindAction::Accept => accept_keystroke,
3111 EditPredictionKeybindAction::Preview => {
3112 preview_keystroke.or_else(|| accept_keystroke.clone())
3113 }
3114 },
3115 };
3116
3117 let missing_accept_keystroke = displayed_keystroke.is_none();
3118
3119 EditPredictionKeybindDisplay {
3120 #[cfg(test)]
3121 accept_keystroke: accept_copy,
3122 #[cfg(test)]
3123 preview_keystroke: preview_copy,
3124 displayed_keystroke,
3125 action,
3126 missing_accept_keystroke,
3127 show_hold_label: matches!(surface, EditPredictionKeybindSurface::CursorPopoverCompact)
3128 && self.edit_prediction_preview.released_too_fast(),
3129 }
3130 }
3131
3132 pub fn new_file(
3133 workspace: &mut Workspace,
3134 _: &workspace::NewFile,
3135 window: &mut Window,
3136 cx: &mut Context<Workspace>,
3137 ) {
3138 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
3139 "Failed to create buffer",
3140 window,
3141 cx,
3142 |e, _, _| match e.error_code() {
3143 ErrorCode::RemoteUpgradeRequired => Some(format!(
3144 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3145 e.error_tag("required").unwrap_or("the latest version")
3146 )),
3147 _ => None,
3148 },
3149 );
3150 }
3151
3152 pub fn new_in_workspace(
3153 workspace: &mut Workspace,
3154 window: &mut Window,
3155 cx: &mut Context<Workspace>,
3156 ) -> Task<Result<Entity<Editor>>> {
3157 let project = workspace.project().clone();
3158 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3159
3160 cx.spawn_in(window, async move |workspace, cx| {
3161 let buffer = create.await?;
3162 workspace.update_in(cx, |workspace, window, cx| {
3163 let editor =
3164 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
3165 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
3166 editor
3167 })
3168 })
3169 }
3170
3171 fn new_file_vertical(
3172 workspace: &mut Workspace,
3173 _: &workspace::NewFileSplitVertical,
3174 window: &mut Window,
3175 cx: &mut Context<Workspace>,
3176 ) {
3177 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
3178 }
3179
3180 fn new_file_horizontal(
3181 workspace: &mut Workspace,
3182 _: &workspace::NewFileSplitHorizontal,
3183 window: &mut Window,
3184 cx: &mut Context<Workspace>,
3185 ) {
3186 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
3187 }
3188
3189 fn new_file_split(
3190 workspace: &mut Workspace,
3191 action: &workspace::NewFileSplit,
3192 window: &mut Window,
3193 cx: &mut Context<Workspace>,
3194 ) {
3195 Self::new_file_in_direction(workspace, action.0, window, cx)
3196 }
3197
3198 fn new_file_in_direction(
3199 workspace: &mut Workspace,
3200 direction: SplitDirection,
3201 window: &mut Window,
3202 cx: &mut Context<Workspace>,
3203 ) {
3204 let project = workspace.project().clone();
3205 let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx));
3206
3207 cx.spawn_in(window, async move |workspace, cx| {
3208 let buffer = create.await?;
3209 workspace.update_in(cx, move |workspace, window, cx| {
3210 workspace.split_item(
3211 direction,
3212 Box::new(
3213 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
3214 ),
3215 window,
3216 cx,
3217 )
3218 })?;
3219 anyhow::Ok(())
3220 })
3221 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
3222 match e.error_code() {
3223 ErrorCode::RemoteUpgradeRequired => Some(format!(
3224 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
3225 e.error_tag("required").unwrap_or("the latest version")
3226 )),
3227 _ => None,
3228 }
3229 });
3230 }
3231
3232 pub fn leader_id(&self) -> Option<CollaboratorId> {
3233 self.leader_id
3234 }
3235
3236 pub fn buffer(&self) -> &Entity<MultiBuffer> {
3237 &self.buffer
3238 }
3239
3240 pub fn project(&self) -> Option<&Entity<Project>> {
3241 self.project.as_ref()
3242 }
3243
3244 pub fn workspace(&self) -> Option<Entity<Workspace>> {
3245 self.workspace.as_ref()?.0.upgrade()
3246 }
3247
3248 /// Detaches a task and shows an error notification in the workspace if available,
3249 /// otherwise just logs the error.
3250 pub fn detach_and_notify_err<R, E>(
3251 &self,
3252 task: Task<Result<R, E>>,
3253 window: &mut Window,
3254 cx: &mut App,
3255 ) where
3256 E: std::fmt::Debug + std::fmt::Display + 'static,
3257 R: 'static,
3258 {
3259 if let Some(workspace) = self.workspace() {
3260 task.detach_and_notify_err(workspace.downgrade(), window, cx);
3261 } else {
3262 task.detach_and_log_err(cx);
3263 }
3264 }
3265
3266 /// Returns the workspace serialization ID if this editor should be serialized.
3267 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
3268 self.workspace
3269 .as_ref()
3270 .filter(|_| self.should_serialize_buffer())
3271 .and_then(|workspace| workspace.1)
3272 }
3273
3274 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
3275 self.buffer().read(cx).title(cx)
3276 }
3277
3278 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
3279 let git_blame_gutter_max_author_length = self
3280 .render_git_blame_gutter(cx)
3281 .then(|| {
3282 if let Some(blame) = self.blame.as_ref() {
3283 let max_author_length =
3284 blame.update(cx, |blame, cx| blame.max_author_length(cx));
3285 Some(max_author_length)
3286 } else {
3287 None
3288 }
3289 })
3290 .flatten();
3291
3292 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3293
3294 EditorSnapshot {
3295 mode: self.mode.clone(),
3296 show_gutter: self.show_gutter,
3297 offset_content: self.offset_content,
3298 show_line_numbers: self.show_line_numbers,
3299 number_deleted_lines: self.number_deleted_lines,
3300 show_git_diff_gutter: self.show_git_diff_gutter,
3301 semantic_tokens_enabled: self.semantic_token_state.enabled(),
3302 show_code_actions: self.show_code_actions,
3303 show_runnables: self.show_runnables,
3304 show_breakpoints: self.show_breakpoints,
3305 git_blame_gutter_max_author_length,
3306 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
3307 display_snapshot,
3308 placeholder_display_snapshot: self
3309 .placeholder_display_map
3310 .as_ref()
3311 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
3312 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
3313 is_focused: self.focus_handle.is_focused(window),
3314 current_line_highlight: self
3315 .current_line_highlight
3316 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
3317 gutter_hovered: self.gutter_hovered,
3318 }
3319 }
3320
3321 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
3322 self.buffer.read(cx).language_at(point, cx)
3323 }
3324
3325 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
3326 self.buffer.read(cx).read(cx).file_at(point).cloned()
3327 }
3328
3329 pub fn active_excerpt(
3330 &self,
3331 cx: &App,
3332 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
3333 self.buffer
3334 .read(cx)
3335 .excerpt_containing(self.selections.newest_anchor().head(), cx)
3336 }
3337
3338 pub fn mode(&self) -> &EditorMode {
3339 &self.mode
3340 }
3341
3342 pub fn set_mode(&mut self, mode: EditorMode) {
3343 self.mode = mode;
3344 }
3345
3346 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3347 self.collaboration_hub.as_deref()
3348 }
3349
3350 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3351 self.collaboration_hub = Some(hub);
3352 }
3353
3354 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3355 self.in_project_search = in_project_search;
3356 }
3357
3358 pub fn set_custom_context_menu(
3359 &mut self,
3360 f: impl 'static
3361 + Fn(
3362 &mut Self,
3363 DisplayPoint,
3364 &mut Window,
3365 &mut Context<Self>,
3366 ) -> Option<Entity<ui::ContextMenu>>,
3367 ) {
3368 self.custom_context_menu = Some(Box::new(f))
3369 }
3370
3371 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3372 self.completion_provider = provider;
3373 }
3374
3375 #[cfg(any(test, feature = "test-support"))]
3376 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3377 self.completion_provider.clone()
3378 }
3379
3380 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3381 self.semantics_provider.clone()
3382 }
3383
3384 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3385 self.semantics_provider = provider;
3386 }
3387
3388 pub fn set_edit_prediction_provider<T>(
3389 &mut self,
3390 provider: Option<Entity<T>>,
3391 window: &mut Window,
3392 cx: &mut Context<Self>,
3393 ) where
3394 T: EditPredictionDelegate,
3395 {
3396 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3397 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3398 if this.focus_handle.is_focused(window) {
3399 this.update_visible_edit_prediction(window, cx);
3400 }
3401 }),
3402 provider: Arc::new(provider),
3403 });
3404 self.update_edit_prediction_settings(cx);
3405 self.refresh_edit_prediction(false, false, window, cx);
3406 }
3407
3408 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3409 self.placeholder_display_map
3410 .as_ref()
3411 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3412 }
3413
3414 pub fn set_placeholder_text(
3415 &mut self,
3416 placeholder_text: &str,
3417 window: &mut Window,
3418 cx: &mut Context<Self>,
3419 ) {
3420 let multibuffer = cx
3421 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3422
3423 let style = window.text_style();
3424
3425 self.placeholder_display_map = Some(cx.new(|cx| {
3426 DisplayMap::new(
3427 multibuffer,
3428 style.font(),
3429 style.font_size.to_pixels(window.rem_size()),
3430 None,
3431 FILE_HEADER_HEIGHT,
3432 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3433 Default::default(),
3434 DiagnosticSeverity::Off,
3435 cx,
3436 )
3437 }));
3438 cx.notify();
3439 }
3440
3441 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3442 self.cursor_shape = cursor_shape;
3443
3444 // Disrupt blink for immediate user feedback that the cursor shape has changed
3445 self.blink_manager.update(cx, BlinkManager::show_cursor);
3446
3447 cx.notify();
3448 }
3449
3450 pub fn cursor_shape(&self) -> CursorShape {
3451 self.cursor_shape
3452 }
3453
3454 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3455 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3456 }
3457
3458 pub fn set_current_line_highlight(
3459 &mut self,
3460 current_line_highlight: Option<CurrentLineHighlight>,
3461 ) {
3462 self.current_line_highlight = current_line_highlight;
3463 }
3464
3465 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3466 self.collapse_matches = collapse_matches;
3467 }
3468
3469 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3470 if self.collapse_matches {
3471 return range.start..range.start;
3472 }
3473 range.clone()
3474 }
3475
3476 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3477 self.display_map.read(cx).clip_at_line_ends
3478 }
3479
3480 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3481 if self.display_map.read(cx).clip_at_line_ends != clip {
3482 self.display_map
3483 .update(cx, |map, _| map.clip_at_line_ends = clip);
3484 }
3485 }
3486
3487 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3488 self.input_enabled = input_enabled;
3489 }
3490
3491 pub fn set_expects_character_input(&mut self, expects_character_input: bool) {
3492 self.expects_character_input = expects_character_input;
3493 }
3494
3495 pub fn set_edit_predictions_hidden_for_vim_mode(
3496 &mut self,
3497 hidden: bool,
3498 window: &mut Window,
3499 cx: &mut Context<Self>,
3500 ) {
3501 if hidden != self.edit_predictions_hidden_for_vim_mode {
3502 self.edit_predictions_hidden_for_vim_mode = hidden;
3503 if hidden {
3504 self.update_visible_edit_prediction(window, cx);
3505 } else {
3506 self.refresh_edit_prediction(true, false, window, cx);
3507 }
3508 }
3509 }
3510
3511 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3512 self.menu_edit_predictions_policy = value;
3513 }
3514
3515 pub fn set_autoindent(&mut self, autoindent: bool) {
3516 if autoindent {
3517 self.autoindent_mode = Some(AutoindentMode::EachLine);
3518 } else {
3519 self.autoindent_mode = None;
3520 }
3521 }
3522
3523 pub fn capability(&self, cx: &App) -> Capability {
3524 if self.read_only {
3525 Capability::ReadOnly
3526 } else {
3527 self.buffer.read(cx).capability()
3528 }
3529 }
3530
3531 pub fn read_only(&self, cx: &App) -> bool {
3532 self.read_only || self.buffer.read(cx).read_only()
3533 }
3534
3535 pub fn set_read_only(&mut self, read_only: bool) {
3536 self.read_only = read_only;
3537 }
3538
3539 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3540 self.use_autoclose = autoclose;
3541 }
3542
3543 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3544 self.use_auto_surround = auto_surround;
3545 }
3546
3547 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3548 self.auto_replace_emoji_shortcode = auto_replace;
3549 }
3550
3551 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3552 self.buffer_serialization = should_serialize.then(|| {
3553 BufferSerialization::new(
3554 ProjectSettings::get_global(cx)
3555 .session
3556 .restore_unsaved_buffers,
3557 )
3558 })
3559 }
3560
3561 fn should_serialize_buffer(&self) -> bool {
3562 self.buffer_serialization.is_some()
3563 }
3564
3565 pub fn toggle_edit_predictions(
3566 &mut self,
3567 _: &ToggleEditPrediction,
3568 window: &mut Window,
3569 cx: &mut Context<Self>,
3570 ) {
3571 if self.show_edit_predictions_override.is_some() {
3572 self.set_show_edit_predictions(None, window, cx);
3573 } else {
3574 let show_edit_predictions = !self.edit_predictions_enabled();
3575 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3576 }
3577 }
3578
3579 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3580 self.show_completions_on_input_override = show_completions_on_input;
3581 }
3582
3583 pub fn set_show_edit_predictions(
3584 &mut self,
3585 show_edit_predictions: Option<bool>,
3586 window: &mut Window,
3587 cx: &mut Context<Self>,
3588 ) {
3589 self.show_edit_predictions_override = show_edit_predictions;
3590 self.update_edit_prediction_settings(cx);
3591
3592 if let Some(false) = show_edit_predictions {
3593 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
3594 } else {
3595 self.refresh_edit_prediction(false, true, window, cx);
3596 }
3597 }
3598
3599 fn edit_predictions_disabled_in_scope(
3600 &self,
3601 buffer: &Entity<Buffer>,
3602 buffer_position: language::Anchor,
3603 cx: &App,
3604 ) -> bool {
3605 let snapshot = buffer.read(cx).snapshot();
3606 let settings = snapshot.settings_at(buffer_position, cx);
3607
3608 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3609 return false;
3610 };
3611
3612 scope.override_name().is_some_and(|scope_name| {
3613 settings
3614 .edit_predictions_disabled_in
3615 .iter()
3616 .any(|s| s == scope_name)
3617 })
3618 }
3619
3620 pub fn set_use_modal_editing(&mut self, to: bool) {
3621 self.use_modal_editing = to;
3622 }
3623
3624 pub fn use_modal_editing(&self) -> bool {
3625 self.use_modal_editing
3626 }
3627
3628 fn selections_did_change(
3629 &mut self,
3630 local: bool,
3631 old_cursor_position: &Anchor,
3632 effects: SelectionEffects,
3633 window: &mut Window,
3634 cx: &mut Context<Self>,
3635 ) {
3636 window.invalidate_character_coordinates();
3637
3638 // Copy selections to primary selection buffer
3639 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3640 if local {
3641 let selections = self
3642 .selections
3643 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3644 let buffer_handle = self.buffer.read(cx).read(cx);
3645
3646 let mut text = String::new();
3647 for (index, selection) in selections.iter().enumerate() {
3648 let text_for_selection = buffer_handle
3649 .text_for_range(selection.start..selection.end)
3650 .collect::<String>();
3651
3652 text.push_str(&text_for_selection);
3653 if index != selections.len() - 1 {
3654 text.push('\n');
3655 }
3656 }
3657
3658 if !text.is_empty() {
3659 cx.write_to_primary(ClipboardItem::new_string(text));
3660 }
3661 }
3662
3663 let selection_anchors = self.selections.disjoint_anchors_arc();
3664
3665 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3666 self.buffer.update(cx, |buffer, cx| {
3667 buffer.set_active_selections(
3668 &selection_anchors,
3669 self.selections.line_mode(),
3670 self.cursor_shape,
3671 cx,
3672 )
3673 });
3674 }
3675 let display_map = self
3676 .display_map
3677 .update(cx, |display_map, cx| display_map.snapshot(cx));
3678 let buffer = display_map.buffer_snapshot();
3679 if self.selections.count() == 1 {
3680 self.add_selections_state = None;
3681 }
3682 self.select_next_state = None;
3683 self.select_prev_state = None;
3684 self.select_syntax_node_history.try_clear();
3685 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3686 self.snippet_stack.invalidate(&selection_anchors, buffer);
3687 self.take_rename(false, window, cx);
3688
3689 let newest_selection = self.selections.newest_anchor();
3690 let new_cursor_position = newest_selection.head();
3691 let selection_start = newest_selection.start;
3692
3693 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3694 self.push_to_nav_history(
3695 *old_cursor_position,
3696 Some(new_cursor_position.to_point(buffer)),
3697 false,
3698 effects.nav_history == Some(true),
3699 cx,
3700 );
3701 }
3702
3703 if local {
3704 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3705 self.register_buffer(buffer_id, cx);
3706 }
3707
3708 let mut context_menu = self.context_menu.borrow_mut();
3709 let completion_menu = match context_menu.as_ref() {
3710 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3711 Some(CodeContextMenu::CodeActions(_)) => {
3712 *context_menu = None;
3713 None
3714 }
3715 None => None,
3716 };
3717 let completion_position = completion_menu.map(|menu| menu.initial_position);
3718 drop(context_menu);
3719
3720 if effects.completions
3721 && let Some(completion_position) = completion_position
3722 {
3723 let start_offset = selection_start.to_offset(buffer);
3724 let position_matches = start_offset == completion_position.to_offset(buffer);
3725 let continue_showing = if let Some((snap, ..)) =
3726 buffer.point_to_buffer_offset(completion_position)
3727 && !snap.capability.editable()
3728 {
3729 false
3730 } else if position_matches {
3731 if self.snippet_stack.is_empty() {
3732 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3733 == Some(CharKind::Word)
3734 } else {
3735 // Snippet choices can be shown even when the cursor is in whitespace.
3736 // Dismissing the menu with actions like backspace is handled by
3737 // invalidation regions.
3738 true
3739 }
3740 } else {
3741 false
3742 };
3743
3744 if continue_showing {
3745 self.open_or_update_completions_menu(None, None, false, window, cx);
3746 } else {
3747 self.hide_context_menu(window, cx);
3748 }
3749 }
3750
3751 hide_hover(self, cx);
3752
3753 if old_cursor_position.to_display_point(&display_map).row()
3754 != new_cursor_position.to_display_point(&display_map).row()
3755 {
3756 self.available_code_actions.take();
3757 }
3758 self.refresh_code_actions(window, cx);
3759 self.refresh_document_highlights(cx);
3760 refresh_linked_ranges(self, window, cx);
3761
3762 self.refresh_selected_text_highlights(&display_map, false, window, cx);
3763 self.refresh_matching_bracket_highlights(&display_map, cx);
3764 self.refresh_outline_symbols_at_cursor(cx);
3765 self.update_visible_edit_prediction(window, cx);
3766 self.inline_blame_popover.take();
3767 if self.git_blame_inline_enabled {
3768 self.start_inline_blame_timer(window, cx);
3769 }
3770 }
3771
3772 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3773
3774 if local && !self.suppress_selection_callback {
3775 if let Some(callback) = self.on_local_selections_changed.as_ref() {
3776 let cursor_position = self.selections.newest::<Point>(&display_map).head();
3777 callback(cursor_position, window, cx);
3778 }
3779 }
3780
3781 cx.emit(EditorEvent::SelectionsChanged { local });
3782
3783 let selections = &self.selections.disjoint_anchors_arc();
3784 if selections.len() == 1 {
3785 cx.emit(SearchEvent::ActiveMatchChanged)
3786 }
3787 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3788 let inmemory_selections = selections
3789 .iter()
3790 .map(|s| {
3791 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3792 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3793 })
3794 .collect();
3795 self.update_restoration_data(cx, |data| {
3796 data.selections = inmemory_selections;
3797 });
3798
3799 if WorkspaceSettings::get(None, cx).restore_on_startup
3800 != RestoreOnStartupBehavior::EmptyTab
3801 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3802 {
3803 let snapshot = self.buffer().read(cx).snapshot(cx);
3804 let selections = selections.clone();
3805 let background_executor = cx.background_executor().clone();
3806 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3807 let db = EditorDb::global(cx);
3808 self.serialize_selections = cx.background_spawn(async move {
3809 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3810 let db_selections = selections
3811 .iter()
3812 .map(|selection| {
3813 (
3814 selection.start.to_offset(&snapshot).0,
3815 selection.end.to_offset(&snapshot).0,
3816 )
3817 })
3818 .collect();
3819
3820 db.save_editor_selections(editor_id, workspace_id, db_selections)
3821 .await
3822 .with_context(|| {
3823 format!(
3824 "persisting editor selections for editor {editor_id}, \
3825 workspace {workspace_id:?}"
3826 )
3827 })
3828 .log_err();
3829 });
3830 }
3831 }
3832
3833 cx.notify();
3834 }
3835
3836 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3837 use text::ToOffset as _;
3838 use text::ToPoint as _;
3839
3840 if self.mode.is_minimap()
3841 || WorkspaceSettings::get(None, cx).restore_on_startup
3842 == RestoreOnStartupBehavior::EmptyTab
3843 {
3844 return;
3845 }
3846
3847 if !self.buffer().read(cx).is_singleton() {
3848 return;
3849 }
3850
3851 let display_snapshot = self
3852 .display_map
3853 .update(cx, |display_map, cx| display_map.snapshot(cx));
3854 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3855 return;
3856 };
3857 let inmemory_folds = display_snapshot
3858 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3859 .map(|fold| {
3860 fold.range.start.text_anchor.to_point(&snapshot)
3861 ..fold.range.end.text_anchor.to_point(&snapshot)
3862 })
3863 .collect();
3864 self.update_restoration_data(cx, |data| {
3865 data.folds = inmemory_folds;
3866 });
3867
3868 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3869 return;
3870 };
3871
3872 // Get file path for path-based fold storage (survives tab close)
3873 let Some(file_path) = self.buffer().read(cx).as_singleton().and_then(|buffer| {
3874 project::File::from_dyn(buffer.read(cx).file())
3875 .map(|file| Arc::<Path>::from(file.abs_path(cx)))
3876 }) else {
3877 return;
3878 };
3879
3880 let background_executor = cx.background_executor().clone();
3881 const FINGERPRINT_LEN: usize = 32;
3882 let db_folds = display_snapshot
3883 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3884 .map(|fold| {
3885 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3886 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3887
3888 // Extract fingerprints - content at fold boundaries for validation on restore
3889 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3890 // content that might change independently.
3891 // start_fp: first min(32, fold_len) bytes of fold content
3892 // end_fp: last min(32, fold_len) bytes of fold content
3893 // Clip to character boundaries to handle multibyte UTF-8 characters.
3894 let fold_len = end - start;
3895 let start_fp_end = snapshot
3896 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3897 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3898 let end_fp_start = snapshot
3899 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3900 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3901
3902 (start, end, start_fp, end_fp)
3903 })
3904 .collect::<Vec<_>>();
3905 let db = EditorDb::global(cx);
3906 self.serialize_folds = cx.background_spawn(async move {
3907 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3908 if db_folds.is_empty() {
3909 // No folds - delete any persisted folds for this file
3910 db.delete_file_folds(workspace_id, file_path)
3911 .await
3912 .with_context(|| format!("deleting file folds for workspace {workspace_id:?}"))
3913 .log_err();
3914 } else {
3915 db.save_file_folds(workspace_id, file_path, db_folds)
3916 .await
3917 .with_context(|| {
3918 format!("persisting file folds for workspace {workspace_id:?}")
3919 })
3920 .log_err();
3921 }
3922 });
3923 }
3924
3925 pub fn sync_selections(
3926 &mut self,
3927 other: Entity<Editor>,
3928 cx: &mut Context<Self>,
3929 ) -> gpui::Subscription {
3930 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3931 if !other_selections.is_empty() {
3932 self.selections
3933 .change_with(&self.display_snapshot(cx), |selections| {
3934 selections.select_anchors(other_selections);
3935 });
3936 }
3937
3938 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3939 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3940 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3941 if other_selections.is_empty() {
3942 return;
3943 }
3944 let snapshot = this.display_snapshot(cx);
3945 this.selections.change_with(&snapshot, |selections| {
3946 selections.select_anchors(other_selections);
3947 });
3948 }
3949 });
3950
3951 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3952 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3953 let these_selections = this.selections.disjoint_anchors().to_vec();
3954 if these_selections.is_empty() {
3955 return;
3956 }
3957 other.update(cx, |other_editor, cx| {
3958 let snapshot = other_editor.display_snapshot(cx);
3959 other_editor
3960 .selections
3961 .change_with(&snapshot, |selections| {
3962 selections.select_anchors(these_selections);
3963 })
3964 });
3965 }
3966 });
3967
3968 Subscription::join(other_subscription, this_subscription)
3969 }
3970
3971 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3972 if self.buffer().read(cx).is_singleton() {
3973 return;
3974 }
3975 let snapshot = self.buffer.read(cx).snapshot(cx);
3976 let buffer_ids: HashSet<BufferId> = self
3977 .selections
3978 .disjoint_anchor_ranges()
3979 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3980 .collect();
3981 for buffer_id in buffer_ids {
3982 self.unfold_buffer(buffer_id, cx);
3983 }
3984 }
3985
3986 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3987 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3988 /// effects of selection change occur at the end of the transaction.
3989 pub fn change_selections<R>(
3990 &mut self,
3991 effects: SelectionEffects,
3992 window: &mut Window,
3993 cx: &mut Context<Self>,
3994 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3995 ) -> R {
3996 let snapshot = self.display_snapshot(cx);
3997 if let Some(state) = &mut self.deferred_selection_effects_state {
3998 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3999 state.effects.completions = effects.completions;
4000 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
4001 let (changed, result) = self.selections.change_with(&snapshot, change);
4002 state.changed |= changed;
4003 return result;
4004 }
4005 let mut state = DeferredSelectionEffectsState {
4006 changed: false,
4007 effects,
4008 old_cursor_position: self.selections.newest_anchor().head(),
4009 history_entry: SelectionHistoryEntry {
4010 selections: self.selections.disjoint_anchors_arc(),
4011 select_next_state: self.select_next_state.clone(),
4012 select_prev_state: self.select_prev_state.clone(),
4013 add_selections_state: self.add_selections_state.clone(),
4014 },
4015 };
4016 let (changed, result) = self.selections.change_with(&snapshot, change);
4017 state.changed = state.changed || changed;
4018 if self.defer_selection_effects {
4019 self.deferred_selection_effects_state = Some(state);
4020 } else {
4021 self.apply_selection_effects(state, window, cx);
4022 }
4023 result
4024 }
4025
4026 /// Defers the effects of selection change, so that the effects of multiple calls to
4027 /// `change_selections` are applied at the end. This way these intermediate states aren't added
4028 /// to selection history and the state of popovers based on selection position aren't
4029 /// erroneously updated.
4030 pub fn with_selection_effects_deferred<R>(
4031 &mut self,
4032 window: &mut Window,
4033 cx: &mut Context<Self>,
4034 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
4035 ) -> R {
4036 let already_deferred = self.defer_selection_effects;
4037 self.defer_selection_effects = true;
4038 let result = update(self, window, cx);
4039 if !already_deferred {
4040 self.defer_selection_effects = false;
4041 if let Some(state) = self.deferred_selection_effects_state.take() {
4042 self.apply_selection_effects(state, window, cx);
4043 }
4044 }
4045 result
4046 }
4047
4048 fn apply_selection_effects(
4049 &mut self,
4050 state: DeferredSelectionEffectsState,
4051 window: &mut Window,
4052 cx: &mut Context<Self>,
4053 ) {
4054 if state.changed {
4055 self.selection_history.push(state.history_entry);
4056
4057 if let Some(autoscroll) = state.effects.scroll {
4058 self.request_autoscroll(autoscroll, cx);
4059 }
4060
4061 let old_cursor_position = &state.old_cursor_position;
4062
4063 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
4064
4065 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
4066 self.show_signature_help_auto(window, cx);
4067 }
4068 }
4069 }
4070
4071 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
4072 where
4073 I: IntoIterator<Item = (Range<S>, T)>,
4074 S: ToOffset,
4075 T: Into<Arc<str>>,
4076 {
4077 if self.read_only(cx) {
4078 return;
4079 }
4080
4081 self.buffer
4082 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
4083 }
4084
4085 pub fn edit_with_autoindent<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.update(cx, |buffer, cx| {
4096 buffer.edit(edits, self.autoindent_mode.clone(), cx)
4097 });
4098 }
4099
4100 pub fn edit_with_block_indent<I, S, T>(
4101 &mut self,
4102 edits: I,
4103 original_indent_columns: Vec<Option<u32>>,
4104 cx: &mut Context<Self>,
4105 ) where
4106 I: IntoIterator<Item = (Range<S>, T)>,
4107 S: ToOffset,
4108 T: Into<Arc<str>>,
4109 {
4110 if self.read_only(cx) {
4111 return;
4112 }
4113
4114 self.buffer.update(cx, |buffer, cx| {
4115 buffer.edit(
4116 edits,
4117 Some(AutoindentMode::Block {
4118 original_indent_columns,
4119 }),
4120 cx,
4121 )
4122 });
4123 }
4124
4125 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
4126 self.hide_context_menu(window, cx);
4127
4128 match phase {
4129 SelectPhase::Begin {
4130 position,
4131 add,
4132 click_count,
4133 } => self.begin_selection(position, add, click_count, window, cx),
4134 SelectPhase::BeginColumnar {
4135 position,
4136 goal_column,
4137 reset,
4138 mode,
4139 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
4140 SelectPhase::Extend {
4141 position,
4142 click_count,
4143 } => self.extend_selection(position, click_count, window, cx),
4144 SelectPhase::Update {
4145 position,
4146 goal_column,
4147 scroll_delta,
4148 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
4149 SelectPhase::End => self.end_selection(window, cx),
4150 }
4151 }
4152
4153 fn extend_selection(
4154 &mut self,
4155 position: DisplayPoint,
4156 click_count: usize,
4157 window: &mut Window,
4158 cx: &mut Context<Self>,
4159 ) {
4160 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4161 let tail = self
4162 .selections
4163 .newest::<MultiBufferOffset>(&display_map)
4164 .tail();
4165 let click_count = click_count.max(match self.selections.select_mode() {
4166 SelectMode::Character => 1,
4167 SelectMode::Word(_) => 2,
4168 SelectMode::Line(_) => 3,
4169 SelectMode::All => 4,
4170 });
4171 self.begin_selection(position, false, click_count, window, cx);
4172
4173 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
4174
4175 let current_selection = match self.selections.select_mode() {
4176 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
4177 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
4178 };
4179
4180 let mut pending_selection = self
4181 .selections
4182 .pending_anchor()
4183 .cloned()
4184 .expect("extend_selection not called with pending selection");
4185
4186 if pending_selection
4187 .start
4188 .cmp(¤t_selection.start, display_map.buffer_snapshot())
4189 == Ordering::Greater
4190 {
4191 pending_selection.start = current_selection.start;
4192 }
4193 if pending_selection
4194 .end
4195 .cmp(¤t_selection.end, display_map.buffer_snapshot())
4196 == Ordering::Less
4197 {
4198 pending_selection.end = current_selection.end;
4199 pending_selection.reversed = true;
4200 }
4201
4202 let mut pending_mode = self.selections.pending_mode().unwrap();
4203 match &mut pending_mode {
4204 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
4205 _ => {}
4206 }
4207
4208 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
4209 SelectionEffects::scroll(Autoscroll::fit())
4210 } else {
4211 SelectionEffects::no_scroll()
4212 };
4213
4214 self.change_selections(effects, window, cx, |s| {
4215 s.set_pending(pending_selection.clone(), pending_mode);
4216 s.set_is_extending(true);
4217 });
4218 }
4219
4220 fn begin_selection(
4221 &mut self,
4222 position: DisplayPoint,
4223 add: bool,
4224 click_count: usize,
4225 window: &mut Window,
4226 cx: &mut Context<Self>,
4227 ) {
4228 if !self.focus_handle.is_focused(window) {
4229 self.last_focused_descendant = None;
4230 window.focus(&self.focus_handle, cx);
4231 }
4232
4233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4234 let buffer = display_map.buffer_snapshot();
4235 let position = display_map.clip_point(position, Bias::Left);
4236
4237 let start;
4238 let end;
4239 let mode;
4240 let mut auto_scroll;
4241 match click_count {
4242 1 => {
4243 start = buffer.anchor_before(position.to_point(&display_map));
4244 end = start;
4245 mode = SelectMode::Character;
4246 auto_scroll = true;
4247 }
4248 2 => {
4249 let position = display_map
4250 .clip_point(position, Bias::Left)
4251 .to_offset(&display_map, Bias::Left);
4252 let (range, _) = buffer.surrounding_word(position, None);
4253 start = buffer.anchor_before(range.start);
4254 end = buffer.anchor_before(range.end);
4255 mode = SelectMode::Word(start..end);
4256 auto_scroll = true;
4257 }
4258 3 => {
4259 let position = display_map
4260 .clip_point(position, Bias::Left)
4261 .to_point(&display_map);
4262 let line_start = display_map.prev_line_boundary(position).0;
4263 let next_line_start = buffer.clip_point(
4264 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4265 Bias::Left,
4266 );
4267 start = buffer.anchor_before(line_start);
4268 end = buffer.anchor_before(next_line_start);
4269 mode = SelectMode::Line(start..end);
4270 auto_scroll = true;
4271 }
4272 _ => {
4273 start = buffer.anchor_before(MultiBufferOffset(0));
4274 end = buffer.anchor_before(buffer.len());
4275 mode = SelectMode::All;
4276 auto_scroll = false;
4277 }
4278 }
4279 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
4280
4281 let point_to_delete: Option<usize> = {
4282 let selected_points: Vec<Selection<Point>> =
4283 self.selections.disjoint_in_range(start..end, &display_map);
4284
4285 if !add || click_count > 1 {
4286 None
4287 } else if !selected_points.is_empty() {
4288 Some(selected_points[0].id)
4289 } else {
4290 let clicked_point_already_selected =
4291 self.selections.disjoint_anchors().iter().find(|selection| {
4292 selection.start.to_point(buffer) == start.to_point(buffer)
4293 || selection.end.to_point(buffer) == end.to_point(buffer)
4294 });
4295
4296 clicked_point_already_selected.map(|selection| selection.id)
4297 }
4298 };
4299
4300 let selections_count = self.selections.count();
4301 let effects = if auto_scroll {
4302 SelectionEffects::default()
4303 } else {
4304 SelectionEffects::no_scroll()
4305 };
4306
4307 self.change_selections(effects, window, cx, |s| {
4308 if let Some(point_to_delete) = point_to_delete {
4309 s.delete(point_to_delete);
4310
4311 if selections_count == 1 {
4312 s.set_pending_anchor_range(start..end, mode);
4313 }
4314 } else {
4315 if !add {
4316 s.clear_disjoint();
4317 }
4318
4319 s.set_pending_anchor_range(start..end, mode);
4320 }
4321 });
4322 }
4323
4324 fn begin_columnar_selection(
4325 &mut self,
4326 position: DisplayPoint,
4327 goal_column: u32,
4328 reset: bool,
4329 mode: ColumnarMode,
4330 window: &mut Window,
4331 cx: &mut Context<Self>,
4332 ) {
4333 if !self.focus_handle.is_focused(window) {
4334 self.last_focused_descendant = None;
4335 window.focus(&self.focus_handle, cx);
4336 }
4337
4338 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4339
4340 if reset {
4341 let pointer_position = display_map
4342 .buffer_snapshot()
4343 .anchor_before(position.to_point(&display_map));
4344
4345 self.change_selections(
4346 SelectionEffects::scroll(Autoscroll::newest()),
4347 window,
4348 cx,
4349 |s| {
4350 s.clear_disjoint();
4351 s.set_pending_anchor_range(
4352 pointer_position..pointer_position,
4353 SelectMode::Character,
4354 );
4355 },
4356 );
4357 };
4358
4359 let tail = self.selections.newest::<Point>(&display_map).tail();
4360 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
4361 self.columnar_selection_state = match mode {
4362 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
4363 selection_tail: selection_anchor,
4364 display_point: if reset {
4365 if position.column() != goal_column {
4366 Some(DisplayPoint::new(position.row(), goal_column))
4367 } else {
4368 None
4369 }
4370 } else {
4371 None
4372 },
4373 }),
4374 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4375 selection_tail: selection_anchor,
4376 }),
4377 };
4378
4379 if !reset {
4380 self.select_columns(position, goal_column, &display_map, window, cx);
4381 }
4382 }
4383
4384 fn update_selection(
4385 &mut self,
4386 position: DisplayPoint,
4387 goal_column: u32,
4388 scroll_delta: gpui::Point<f32>,
4389 window: &mut Window,
4390 cx: &mut Context<Self>,
4391 ) {
4392 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4393
4394 if self.columnar_selection_state.is_some() {
4395 self.select_columns(position, goal_column, &display_map, window, cx);
4396 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4397 let buffer = display_map.buffer_snapshot();
4398 let head;
4399 let tail;
4400 let mode = self.selections.pending_mode().unwrap();
4401 match &mode {
4402 SelectMode::Character => {
4403 head = position.to_point(&display_map);
4404 tail = pending.tail().to_point(buffer);
4405 }
4406 SelectMode::Word(original_range) => {
4407 let offset = display_map
4408 .clip_point(position, Bias::Left)
4409 .to_offset(&display_map, Bias::Left);
4410 let original_range = original_range.to_offset(buffer);
4411
4412 let head_offset = if buffer.is_inside_word(offset, None)
4413 || original_range.contains(&offset)
4414 {
4415 let (word_range, _) = buffer.surrounding_word(offset, None);
4416 if word_range.start < original_range.start {
4417 word_range.start
4418 } else {
4419 word_range.end
4420 }
4421 } else {
4422 offset
4423 };
4424
4425 head = head_offset.to_point(buffer);
4426 if head_offset <= original_range.start {
4427 tail = original_range.end.to_point(buffer);
4428 } else {
4429 tail = original_range.start.to_point(buffer);
4430 }
4431 }
4432 SelectMode::Line(original_range) => {
4433 let original_range = original_range.to_point(display_map.buffer_snapshot());
4434
4435 let position = display_map
4436 .clip_point(position, Bias::Left)
4437 .to_point(&display_map);
4438 let line_start = display_map.prev_line_boundary(position).0;
4439 let next_line_start = buffer.clip_point(
4440 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4441 Bias::Left,
4442 );
4443
4444 if line_start < original_range.start {
4445 head = line_start
4446 } else {
4447 head = next_line_start
4448 }
4449
4450 if head <= original_range.start {
4451 tail = original_range.end;
4452 } else {
4453 tail = original_range.start;
4454 }
4455 }
4456 SelectMode::All => {
4457 return;
4458 }
4459 };
4460
4461 if head < tail {
4462 pending.start = buffer.anchor_before(head);
4463 pending.end = buffer.anchor_before(tail);
4464 pending.reversed = true;
4465 } else {
4466 pending.start = buffer.anchor_before(tail);
4467 pending.end = buffer.anchor_before(head);
4468 pending.reversed = false;
4469 }
4470
4471 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4472 s.set_pending(pending.clone(), mode);
4473 });
4474 } else {
4475 log::error!("update_selection dispatched with no pending selection");
4476 return;
4477 }
4478
4479 self.apply_scroll_delta(scroll_delta, window, cx);
4480 cx.notify();
4481 }
4482
4483 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4484 self.columnar_selection_state.take();
4485 if let Some(pending_mode) = self.selections.pending_mode() {
4486 let selections = self
4487 .selections
4488 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4489 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4490 s.select(selections);
4491 s.clear_pending();
4492 if s.is_extending() {
4493 s.set_is_extending(false);
4494 } else {
4495 s.set_select_mode(pending_mode);
4496 }
4497 });
4498 }
4499 }
4500
4501 fn select_columns(
4502 &mut self,
4503 head: DisplayPoint,
4504 goal_column: u32,
4505 display_map: &DisplaySnapshot,
4506 window: &mut Window,
4507 cx: &mut Context<Self>,
4508 ) {
4509 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4510 return;
4511 };
4512
4513 let tail = match columnar_state {
4514 ColumnarSelectionState::FromMouse {
4515 selection_tail,
4516 display_point,
4517 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4518 ColumnarSelectionState::FromSelection { selection_tail } => {
4519 selection_tail.to_display_point(display_map)
4520 }
4521 };
4522
4523 let start_row = cmp::min(tail.row(), head.row());
4524 let end_row = cmp::max(tail.row(), head.row());
4525 let start_column = cmp::min(tail.column(), goal_column);
4526 let end_column = cmp::max(tail.column(), goal_column);
4527 let reversed = start_column < tail.column();
4528
4529 let selection_ranges = (start_row.0..=end_row.0)
4530 .map(DisplayRow)
4531 .filter_map(|row| {
4532 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4533 || start_column <= display_map.line_len(row))
4534 && !display_map.is_block_line(row)
4535 {
4536 let start = display_map
4537 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4538 .to_point(display_map);
4539 let end = display_map
4540 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4541 .to_point(display_map);
4542 if reversed {
4543 Some(end..start)
4544 } else {
4545 Some(start..end)
4546 }
4547 } else {
4548 None
4549 }
4550 })
4551 .collect::<Vec<_>>();
4552 if selection_ranges.is_empty() {
4553 return;
4554 }
4555
4556 let ranges = match columnar_state {
4557 ColumnarSelectionState::FromMouse { .. } => {
4558 let mut non_empty_ranges = selection_ranges
4559 .iter()
4560 .filter(|selection_range| selection_range.start != selection_range.end)
4561 .peekable();
4562 if non_empty_ranges.peek().is_some() {
4563 non_empty_ranges.cloned().collect()
4564 } else {
4565 selection_ranges
4566 }
4567 }
4568 _ => selection_ranges,
4569 };
4570
4571 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4572 s.select_ranges(ranges);
4573 });
4574 cx.notify();
4575 }
4576
4577 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4578 self.selections
4579 .all_adjusted(snapshot)
4580 .iter()
4581 .any(|selection| !selection.is_empty())
4582 }
4583
4584 pub fn has_pending_nonempty_selection(&self) -> bool {
4585 let pending_nonempty_selection = match self.selections.pending_anchor() {
4586 Some(Selection { start, end, .. }) => start != end,
4587 None => false,
4588 };
4589
4590 pending_nonempty_selection
4591 || (self.columnar_selection_state.is_some()
4592 && self.selections.disjoint_anchors().len() > 1)
4593 }
4594
4595 pub fn has_pending_selection(&self) -> bool {
4596 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4597 }
4598
4599 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4600 self.selection_mark_mode = false;
4601 self.selection_drag_state = SelectionDragState::None;
4602
4603 if self.dismiss_menus_and_popups(true, window, cx) {
4604 cx.notify();
4605 return;
4606 }
4607 if self.clear_expanded_diff_hunks(cx) {
4608 cx.notify();
4609 return;
4610 }
4611 if self.show_git_blame_gutter {
4612 self.show_git_blame_gutter = false;
4613 cx.notify();
4614 return;
4615 }
4616
4617 if self.mode.is_full()
4618 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4619 {
4620 cx.notify();
4621 return;
4622 }
4623
4624 cx.propagate();
4625 }
4626
4627 pub fn dismiss_menus_and_popups(
4628 &mut self,
4629 is_user_requested: bool,
4630 window: &mut Window,
4631 cx: &mut Context<Self>,
4632 ) -> bool {
4633 let mut dismissed = false;
4634
4635 dismissed |= self.take_rename(false, window, cx).is_some();
4636 dismissed |= self.hide_blame_popover(true, cx);
4637 dismissed |= hide_hover(self, cx);
4638 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4639 dismissed |= self.hide_context_menu(window, cx).is_some();
4640 dismissed |= self.mouse_context_menu.take().is_some();
4641 dismissed |= is_user_requested
4642 && self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
4643 dismissed |= self.snippet_stack.pop().is_some();
4644 if self.diff_review_drag_state.is_some() {
4645 self.cancel_diff_review_drag(cx);
4646 dismissed = true;
4647 }
4648 if !self.diff_review_overlays.is_empty() {
4649 self.dismiss_all_diff_review_overlays(cx);
4650 dismissed = true;
4651 }
4652
4653 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4654 self.dismiss_diagnostics(cx);
4655 dismissed = true;
4656 }
4657
4658 dismissed
4659 }
4660
4661 fn linked_editing_ranges_for(
4662 &self,
4663 selection: Range<text::Anchor>,
4664 cx: &App,
4665 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4666 if self.linked_edit_ranges.is_empty() {
4667 return None;
4668 }
4669 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4670 selection.end.buffer_id.and_then(|end_buffer_id| {
4671 if selection.start.buffer_id != Some(end_buffer_id) {
4672 return None;
4673 }
4674 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4675 let snapshot = buffer.read(cx).snapshot();
4676 self.linked_edit_ranges
4677 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4678 .map(|ranges| (ranges, snapshot, buffer))
4679 })?;
4680 use text::ToOffset as TO;
4681 // find offset from the start of current range to current cursor position
4682 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4683
4684 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4685 let start_difference = start_offset - start_byte_offset;
4686 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4687 let end_difference = end_offset - start_byte_offset;
4688
4689 // Current range has associated linked ranges.
4690 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4691 for range in linked_ranges.iter() {
4692 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4693 let end_offset = start_offset + end_difference;
4694 let start_offset = start_offset + start_difference;
4695 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4696 continue;
4697 }
4698 if self.selections.disjoint_anchor_ranges().any(|s| {
4699 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4700 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4701 {
4702 return false;
4703 }
4704 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4705 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4706 }) {
4707 continue;
4708 }
4709 let start = buffer_snapshot.anchor_after(start_offset);
4710 let end = buffer_snapshot.anchor_after(end_offset);
4711 linked_edits
4712 .entry(buffer.clone())
4713 .or_default()
4714 .push(start..end);
4715 }
4716 Some(linked_edits)
4717 }
4718
4719 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4720 let text: Arc<str> = text.into();
4721
4722 if self.read_only(cx) {
4723 return;
4724 }
4725
4726 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4727
4728 self.unfold_buffers_with_selections(cx);
4729
4730 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4731 let mut bracket_inserted = false;
4732 let mut edits = Vec::new();
4733 let mut linked_edits = LinkedEdits::new();
4734 let mut new_selections = Vec::with_capacity(selections.len());
4735 let mut new_autoclose_regions = Vec::new();
4736 let snapshot = self.buffer.read(cx).read(cx);
4737 let mut clear_linked_edit_ranges = false;
4738 let mut all_selections_read_only = true;
4739 let mut has_adjacent_edits = false;
4740 let mut in_adjacent_group = false;
4741
4742 let mut regions = self
4743 .selections_with_autoclose_regions(selections, &snapshot)
4744 .peekable();
4745
4746 while let Some((selection, autoclose_region)) = regions.next() {
4747 if snapshot
4748 .point_to_buffer_point(selection.head())
4749 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4750 {
4751 continue;
4752 }
4753 if snapshot
4754 .point_to_buffer_point(selection.tail())
4755 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4756 {
4757 // note, ideally we'd clip the tail to the closest writeable region towards the head
4758 continue;
4759 }
4760 all_selections_read_only = false;
4761
4762 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4763 // Determine if the inserted text matches the opening or closing
4764 // bracket of any of this language's bracket pairs.
4765 let mut bracket_pair = None;
4766 let mut is_bracket_pair_start = false;
4767 let mut is_bracket_pair_end = false;
4768 if !text.is_empty() {
4769 let mut bracket_pair_matching_end = None;
4770 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4771 // and they are removing the character that triggered IME popup.
4772 for (pair, enabled) in scope.brackets() {
4773 if !pair.close && !pair.surround {
4774 continue;
4775 }
4776
4777 if enabled && pair.start.ends_with(text.as_ref()) {
4778 let prefix_len = pair.start.len() - text.len();
4779 let preceding_text_matches_prefix = prefix_len == 0
4780 || (selection.start.column >= (prefix_len as u32)
4781 && snapshot.contains_str_at(
4782 Point::new(
4783 selection.start.row,
4784 selection.start.column - (prefix_len as u32),
4785 ),
4786 &pair.start[..prefix_len],
4787 ));
4788 if preceding_text_matches_prefix {
4789 bracket_pair = Some(pair.clone());
4790 is_bracket_pair_start = true;
4791 break;
4792 }
4793 }
4794 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4795 {
4796 // take first bracket pair matching end, but don't break in case a later bracket
4797 // pair matches start
4798 bracket_pair_matching_end = Some(pair.clone());
4799 }
4800 }
4801 if let Some(end) = bracket_pair_matching_end
4802 && bracket_pair.is_none()
4803 {
4804 bracket_pair = Some(end);
4805 is_bracket_pair_end = true;
4806 }
4807 }
4808
4809 if let Some(bracket_pair) = bracket_pair {
4810 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4811 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4812 let auto_surround =
4813 self.use_auto_surround && snapshot_settings.use_auto_surround;
4814 if selection.is_empty() {
4815 if is_bracket_pair_start {
4816 // If the inserted text is a suffix of an opening bracket and the
4817 // selection is preceded by the rest of the opening bracket, then
4818 // insert the closing bracket.
4819 let following_text_allows_autoclose = snapshot
4820 .chars_at(selection.start)
4821 .next()
4822 .is_none_or(|c| scope.should_autoclose_before(c));
4823
4824 let preceding_text_allows_autoclose = selection.start.column == 0
4825 || snapshot
4826 .reversed_chars_at(selection.start)
4827 .next()
4828 .is_none_or(|c| {
4829 bracket_pair.start != bracket_pair.end
4830 || !snapshot
4831 .char_classifier_at(selection.start)
4832 .is_word(c)
4833 });
4834
4835 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4836 && bracket_pair.start.len() == 1
4837 {
4838 let target = bracket_pair.start.chars().next().unwrap();
4839 let mut byte_offset = 0u32;
4840 let current_line_count = snapshot
4841 .reversed_chars_at(selection.start)
4842 .take_while(|&c| c != '\n')
4843 .filter(|c| {
4844 byte_offset += c.len_utf8() as u32;
4845 if *c != target {
4846 return false;
4847 }
4848
4849 let point = Point::new(
4850 selection.start.row,
4851 selection.start.column.saturating_sub(byte_offset),
4852 );
4853
4854 let is_enabled = snapshot
4855 .language_scope_at(point)
4856 .and_then(|scope| {
4857 scope
4858 .brackets()
4859 .find(|(pair, _)| {
4860 pair.start == bracket_pair.start
4861 })
4862 .map(|(_, enabled)| enabled)
4863 })
4864 .unwrap_or(true);
4865
4866 let is_delimiter = snapshot
4867 .language_scope_at(Point::new(
4868 point.row,
4869 point.column + 1,
4870 ))
4871 .and_then(|scope| {
4872 scope
4873 .brackets()
4874 .find(|(pair, _)| {
4875 pair.start == bracket_pair.start
4876 })
4877 .map(|(_, enabled)| !enabled)
4878 })
4879 .unwrap_or(false);
4880
4881 is_enabled && !is_delimiter
4882 })
4883 .count();
4884 current_line_count % 2 == 1
4885 } else {
4886 false
4887 };
4888
4889 if autoclose
4890 && bracket_pair.close
4891 && following_text_allows_autoclose
4892 && preceding_text_allows_autoclose
4893 && !is_closing_quote
4894 {
4895 let anchor = snapshot.anchor_before(selection.end);
4896 new_selections.push((selection.map(|_| anchor), text.len()));
4897 new_autoclose_regions.push((
4898 anchor,
4899 text.len(),
4900 selection.id,
4901 bracket_pair.clone(),
4902 ));
4903 edits.push((
4904 selection.range(),
4905 format!("{}{}", text, bracket_pair.end).into(),
4906 ));
4907 bracket_inserted = true;
4908 continue;
4909 }
4910 }
4911
4912 if let Some(region) = autoclose_region {
4913 // If the selection is followed by an auto-inserted closing bracket,
4914 // then don't insert that closing bracket again; just move the selection
4915 // past the closing bracket.
4916 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4917 && text.as_ref() == region.pair.end.as_str()
4918 && snapshot.contains_str_at(region.range.end, text.as_ref());
4919 if should_skip {
4920 let anchor = snapshot.anchor_after(selection.end);
4921 new_selections
4922 .push((selection.map(|_| anchor), region.pair.end.len()));
4923 continue;
4924 }
4925 }
4926
4927 let always_treat_brackets_as_autoclosed = snapshot
4928 .language_settings_at(selection.start, cx)
4929 .always_treat_brackets_as_autoclosed;
4930 if always_treat_brackets_as_autoclosed
4931 && is_bracket_pair_end
4932 && snapshot.contains_str_at(selection.end, text.as_ref())
4933 {
4934 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4935 // and the inserted text is a closing bracket and the selection is followed
4936 // by the closing bracket then move the selection past the closing bracket.
4937 let anchor = snapshot.anchor_after(selection.end);
4938 new_selections.push((selection.map(|_| anchor), text.len()));
4939 continue;
4940 }
4941 }
4942 // If an opening bracket is 1 character long and is typed while
4943 // text is selected, then surround that text with the bracket pair.
4944 else if auto_surround
4945 && bracket_pair.surround
4946 && is_bracket_pair_start
4947 && bracket_pair.start.chars().count() == 1
4948 {
4949 edits.push((selection.start..selection.start, text.clone()));
4950 edits.push((
4951 selection.end..selection.end,
4952 bracket_pair.end.as_str().into(),
4953 ));
4954 bracket_inserted = true;
4955 new_selections.push((
4956 Selection {
4957 id: selection.id,
4958 start: snapshot.anchor_after(selection.start),
4959 end: snapshot.anchor_before(selection.end),
4960 reversed: selection.reversed,
4961 goal: selection.goal,
4962 },
4963 0,
4964 ));
4965 continue;
4966 }
4967 }
4968 }
4969
4970 if self.auto_replace_emoji_shortcode
4971 && selection.is_empty()
4972 && text.as_ref().ends_with(':')
4973 && let Some(possible_emoji_short_code) =
4974 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4975 && !possible_emoji_short_code.is_empty()
4976 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4977 {
4978 let emoji_shortcode_start = Point::new(
4979 selection.start.row,
4980 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4981 );
4982
4983 // Remove shortcode from buffer
4984 edits.push((
4985 emoji_shortcode_start..selection.start,
4986 "".to_string().into(),
4987 ));
4988 new_selections.push((
4989 Selection {
4990 id: selection.id,
4991 start: snapshot.anchor_after(emoji_shortcode_start),
4992 end: snapshot.anchor_before(selection.start),
4993 reversed: selection.reversed,
4994 goal: selection.goal,
4995 },
4996 0,
4997 ));
4998
4999 // Insert emoji
5000 let selection_start_anchor = snapshot.anchor_after(selection.start);
5001 new_selections.push((selection.map(|_| selection_start_anchor), 0));
5002 edits.push((selection.start..selection.end, emoji.to_string().into()));
5003
5004 continue;
5005 }
5006
5007 let next_is_adjacent = regions
5008 .peek()
5009 .is_some_and(|(next, _)| selection.end == next.start);
5010
5011 // If not handling any auto-close operation, then just replace the selected
5012 // text with the given input and move the selection to the end of the
5013 // newly inserted text.
5014 let anchor = if in_adjacent_group || next_is_adjacent {
5015 // After edits the right bias would shift those anchor to the next visible fragment
5016 // but we want to resolve to the previous one
5017 snapshot.anchor_before(selection.end)
5018 } else {
5019 snapshot.anchor_after(selection.end)
5020 };
5021
5022 if !self.linked_edit_ranges.is_empty() {
5023 let start_anchor = snapshot.anchor_before(selection.start);
5024
5025 let is_word_char = text.chars().next().is_none_or(|char| {
5026 let classifier = snapshot
5027 .char_classifier_at(start_anchor.to_offset(&snapshot))
5028 .scope_context(Some(CharScopeContext::LinkedEdit));
5029 classifier.is_word(char)
5030 });
5031 let is_dot = text.as_ref() == ".";
5032 let should_apply_linked_edit = is_word_char || is_dot;
5033
5034 if should_apply_linked_edit {
5035 let anchor_range = start_anchor.text_anchor..anchor.text_anchor;
5036 linked_edits.push(&self, anchor_range, text.clone(), cx);
5037 } else {
5038 clear_linked_edit_ranges = true;
5039 }
5040 }
5041
5042 new_selections.push((selection.map(|_| anchor), 0));
5043 edits.push((selection.start..selection.end, text.clone()));
5044
5045 has_adjacent_edits |= next_is_adjacent;
5046 in_adjacent_group = next_is_adjacent;
5047 }
5048
5049 if all_selections_read_only {
5050 return;
5051 }
5052
5053 drop(regions);
5054 drop(snapshot);
5055
5056 self.transact(window, cx, |this, window, cx| {
5057 if clear_linked_edit_ranges {
5058 this.linked_edit_ranges.clear();
5059 }
5060 let initial_buffer_versions =
5061 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
5062
5063 this.buffer.update(cx, |buffer, cx| {
5064 if has_adjacent_edits {
5065 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
5066 } else {
5067 buffer.edit(edits, this.autoindent_mode.clone(), cx);
5068 }
5069 });
5070 linked_edits.apply(cx);
5071 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
5072 let new_selection_deltas = new_selections.iter().map(|e| e.1);
5073 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
5074 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
5075 new_anchor_selections,
5076 &map,
5077 )
5078 .zip(new_selection_deltas)
5079 .map(|(selection, delta)| Selection {
5080 id: selection.id,
5081 start: selection.start + delta,
5082 end: selection.end + delta,
5083 reversed: selection.reversed,
5084 goal: SelectionGoal::None,
5085 })
5086 .collect::<Vec<_>>();
5087
5088 let mut i = 0;
5089 for (position, delta, selection_id, pair) in new_autoclose_regions {
5090 let position = position.to_offset(map.buffer_snapshot()) + delta;
5091 let start = map.buffer_snapshot().anchor_before(position);
5092 let end = map.buffer_snapshot().anchor_after(position);
5093 while let Some(existing_state) = this.autoclose_regions.get(i) {
5094 match existing_state
5095 .range
5096 .start
5097 .cmp(&start, map.buffer_snapshot())
5098 {
5099 Ordering::Less => i += 1,
5100 Ordering::Greater => break,
5101 Ordering::Equal => {
5102 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
5103 Ordering::Less => i += 1,
5104 Ordering::Equal => break,
5105 Ordering::Greater => break,
5106 }
5107 }
5108 }
5109 }
5110 this.autoclose_regions.insert(
5111 i,
5112 AutocloseRegion {
5113 selection_id,
5114 range: start..end,
5115 pair,
5116 },
5117 );
5118 }
5119
5120 let had_active_edit_prediction = this.has_active_edit_prediction();
5121 this.change_selections(
5122 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
5123 window,
5124 cx,
5125 |s| s.select(new_selections),
5126 );
5127
5128 if !bracket_inserted
5129 && let Some(on_type_format_task) =
5130 this.trigger_on_type_formatting(text.to_string(), window, cx)
5131 {
5132 on_type_format_task.detach_and_log_err(cx);
5133 }
5134
5135 let editor_settings = EditorSettings::get_global(cx);
5136 if bracket_inserted
5137 && (editor_settings.auto_signature_help
5138 || editor_settings.show_signature_help_after_edits)
5139 {
5140 this.show_signature_help(&ShowSignatureHelp, window, cx);
5141 }
5142
5143 let trigger_in_words =
5144 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
5145 if this.hard_wrap.is_some() {
5146 let latest: Range<Point> = this.selections.newest(&map).range();
5147 if latest.is_empty()
5148 && this
5149 .buffer()
5150 .read(cx)
5151 .snapshot(cx)
5152 .line_len(MultiBufferRow(latest.start.row))
5153 == latest.start.column
5154 {
5155 this.rewrap_impl(
5156 RewrapOptions {
5157 override_language_settings: true,
5158 preserve_existing_whitespace: true,
5159 line_length: None,
5160 },
5161 cx,
5162 )
5163 }
5164 }
5165 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
5166 refresh_linked_ranges(this, window, cx);
5167 this.refresh_edit_prediction(true, false, window, cx);
5168 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
5169 });
5170 }
5171
5172 fn find_possible_emoji_shortcode_at_position(
5173 snapshot: &MultiBufferSnapshot,
5174 position: Point,
5175 ) -> Option<String> {
5176 let mut chars = Vec::new();
5177 let mut found_colon = false;
5178 for char in snapshot.reversed_chars_at(position).take(100) {
5179 // Found a possible emoji shortcode in the middle of the buffer
5180 if found_colon {
5181 if char.is_whitespace() {
5182 chars.reverse();
5183 return Some(chars.iter().collect());
5184 }
5185 // If the previous character is not a whitespace, we are in the middle of a word
5186 // and we only want to complete the shortcode if the word is made up of other emojis
5187 let mut containing_word = String::new();
5188 for ch in snapshot
5189 .reversed_chars_at(position)
5190 .skip(chars.len() + 1)
5191 .take(100)
5192 {
5193 if ch.is_whitespace() {
5194 break;
5195 }
5196 containing_word.push(ch);
5197 }
5198 let containing_word = containing_word.chars().rev().collect::<String>();
5199 if util::word_consists_of_emojis(containing_word.as_str()) {
5200 chars.reverse();
5201 return Some(chars.iter().collect());
5202 }
5203 }
5204
5205 if char.is_whitespace() || !char.is_ascii() {
5206 return None;
5207 }
5208 if char == ':' {
5209 found_colon = true;
5210 } else {
5211 chars.push(char);
5212 }
5213 }
5214 // Found a possible emoji shortcode at the beginning of the buffer
5215 chars.reverse();
5216 Some(chars.iter().collect())
5217 }
5218
5219 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
5220 if self.read_only(cx) {
5221 return;
5222 }
5223
5224 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5225 self.transact(window, cx, |this, window, cx| {
5226 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
5227 let selections = this
5228 .selections
5229 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
5230 let multi_buffer = this.buffer.read(cx);
5231 let buffer = multi_buffer.snapshot(cx);
5232 selections
5233 .iter()
5234 .map(|selection| {
5235 let start_point = selection.start.to_point(&buffer);
5236 let mut existing_indent =
5237 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
5238 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
5239 let start = selection.start;
5240 let end = selection.end;
5241 let selection_is_empty = start == end;
5242 let language_scope = buffer.language_scope_at(start);
5243 let (delimiter, newline_config) = if let Some(language) = &language_scope {
5244 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
5245 &buffer,
5246 start..end,
5247 language,
5248 )
5249 || NewlineConfig::insert_extra_newline_tree_sitter(
5250 &buffer,
5251 start..end,
5252 );
5253
5254 let mut newline_config = NewlineConfig::Newline {
5255 additional_indent: IndentSize::spaces(0),
5256 extra_line_additional_indent: if needs_extra_newline {
5257 Some(IndentSize::spaces(0))
5258 } else {
5259 None
5260 },
5261 prevent_auto_indent: false,
5262 };
5263
5264 let comment_delimiter = maybe!({
5265 if !selection_is_empty {
5266 return None;
5267 }
5268
5269 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5270 return None;
5271 }
5272
5273 return comment_delimiter_for_newline(
5274 &start_point,
5275 &buffer,
5276 language,
5277 );
5278 });
5279
5280 let doc_delimiter = maybe!({
5281 if !selection_is_empty {
5282 return None;
5283 }
5284
5285 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
5286 return None;
5287 }
5288
5289 return documentation_delimiter_for_newline(
5290 &start_point,
5291 &buffer,
5292 language,
5293 &mut newline_config,
5294 );
5295 });
5296
5297 let list_delimiter = maybe!({
5298 if !selection_is_empty {
5299 return None;
5300 }
5301
5302 if !multi_buffer.language_settings(cx).extend_list_on_newline {
5303 return None;
5304 }
5305
5306 return list_delimiter_for_newline(
5307 &start_point,
5308 &buffer,
5309 language,
5310 &mut newline_config,
5311 );
5312 });
5313
5314 (
5315 comment_delimiter.or(doc_delimiter).or(list_delimiter),
5316 newline_config,
5317 )
5318 } else {
5319 (
5320 None,
5321 NewlineConfig::Newline {
5322 additional_indent: IndentSize::spaces(0),
5323 extra_line_additional_indent: None,
5324 prevent_auto_indent: false,
5325 },
5326 )
5327 };
5328
5329 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
5330 NewlineConfig::ClearCurrentLine => {
5331 let row_start =
5332 buffer.point_to_offset(Point::new(start_point.row, 0));
5333 (row_start, String::new(), false)
5334 }
5335 NewlineConfig::UnindentCurrentLine { continuation } => {
5336 let row_start =
5337 buffer.point_to_offset(Point::new(start_point.row, 0));
5338 let tab_size = buffer.language_settings_at(start, cx).tab_size;
5339 let tab_size_indent = IndentSize::spaces(tab_size.get());
5340 let reduced_indent =
5341 existing_indent.with_delta(Ordering::Less, tab_size_indent);
5342 let mut new_text = String::new();
5343 new_text.extend(reduced_indent.chars());
5344 new_text.push_str(continuation);
5345 (row_start, new_text, true)
5346 }
5347 NewlineConfig::Newline {
5348 additional_indent,
5349 extra_line_additional_indent,
5350 prevent_auto_indent,
5351 } => {
5352 let auto_indent_mode =
5353 buffer.language_settings_at(start, cx).auto_indent;
5354 let preserve_indent =
5355 auto_indent_mode != language::AutoIndentMode::None;
5356 let apply_syntax_indent =
5357 auto_indent_mode == language::AutoIndentMode::SyntaxAware;
5358 let capacity_for_delimiter =
5359 delimiter.as_deref().map(str::len).unwrap_or_default();
5360 let existing_indent_len = if preserve_indent {
5361 existing_indent.len as usize
5362 } else {
5363 0
5364 };
5365 let extra_line_len = extra_line_additional_indent
5366 .map(|i| 1 + existing_indent_len + i.len as usize)
5367 .unwrap_or(0);
5368 let mut new_text = String::with_capacity(
5369 1 + capacity_for_delimiter
5370 + existing_indent_len
5371 + additional_indent.len as usize
5372 + extra_line_len,
5373 );
5374 new_text.push('\n');
5375 if preserve_indent {
5376 new_text.extend(existing_indent.chars());
5377 }
5378 new_text.extend(additional_indent.chars());
5379 if let Some(delimiter) = &delimiter {
5380 new_text.push_str(delimiter);
5381 }
5382 if let Some(extra_indent) = extra_line_additional_indent {
5383 new_text.push('\n');
5384 if preserve_indent {
5385 new_text.extend(existing_indent.chars());
5386 }
5387 new_text.extend(extra_indent.chars());
5388 }
5389 (
5390 start,
5391 new_text,
5392 *prevent_auto_indent || !apply_syntax_indent,
5393 )
5394 }
5395 };
5396
5397 let anchor = buffer.anchor_after(end);
5398 let new_selection = selection.map(|_| anchor);
5399 (
5400 ((edit_start..end, new_text), prevent_auto_indent),
5401 (newline_config.has_extra_line(), new_selection),
5402 )
5403 })
5404 .unzip()
5405 };
5406
5407 let mut auto_indent_edits = Vec::new();
5408 let mut edits = Vec::new();
5409 for (edit, prevent_auto_indent) in edits_with_flags {
5410 if prevent_auto_indent {
5411 edits.push(edit);
5412 } else {
5413 auto_indent_edits.push(edit);
5414 }
5415 }
5416 if !edits.is_empty() {
5417 this.edit(edits, cx);
5418 }
5419 if !auto_indent_edits.is_empty() {
5420 this.edit_with_autoindent(auto_indent_edits, cx);
5421 }
5422
5423 let buffer = this.buffer.read(cx).snapshot(cx);
5424 let new_selections = selection_info
5425 .into_iter()
5426 .map(|(extra_newline_inserted, new_selection)| {
5427 let mut cursor = new_selection.end.to_point(&buffer);
5428 if extra_newline_inserted {
5429 cursor.row -= 1;
5430 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5431 }
5432 new_selection.map(|_| cursor)
5433 })
5434 .collect();
5435
5436 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5437 this.refresh_edit_prediction(true, false, window, cx);
5438 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5439 task.detach_and_log_err(cx);
5440 }
5441 });
5442 }
5443
5444 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5445 if self.read_only(cx) {
5446 return;
5447 }
5448
5449 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5450
5451 let buffer = self.buffer.read(cx);
5452 let snapshot = buffer.snapshot(cx);
5453
5454 let mut edits = Vec::new();
5455 let mut rows = Vec::new();
5456
5457 for (rows_inserted, selection) in self
5458 .selections
5459 .all_adjusted(&self.display_snapshot(cx))
5460 .into_iter()
5461 .enumerate()
5462 {
5463 let cursor = selection.head();
5464 let row = cursor.row;
5465
5466 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5467
5468 let newline = "\n".to_string();
5469 edits.push((start_of_line..start_of_line, newline));
5470
5471 rows.push(row + rows_inserted as u32);
5472 }
5473
5474 self.transact(window, cx, |editor, window, cx| {
5475 editor.edit(edits, cx);
5476
5477 editor.change_selections(Default::default(), window, cx, |s| {
5478 let mut index = 0;
5479 s.move_cursors_with(&mut |map, _, _| {
5480 let row = rows[index];
5481 index += 1;
5482
5483 let point = Point::new(row, 0);
5484 let boundary = map.next_line_boundary(point).1;
5485 let clipped = map.clip_point(boundary, Bias::Left);
5486
5487 (clipped, SelectionGoal::None)
5488 });
5489 });
5490
5491 let mut indent_edits = Vec::new();
5492 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5493 for row in rows {
5494 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5495 for (row, indent) in indents {
5496 if indent.len == 0 {
5497 continue;
5498 }
5499
5500 let text = match indent.kind {
5501 IndentKind::Space => " ".repeat(indent.len as usize),
5502 IndentKind::Tab => "\t".repeat(indent.len as usize),
5503 };
5504 let point = Point::new(row.0, 0);
5505 indent_edits.push((point..point, text));
5506 }
5507 }
5508 editor.edit(indent_edits, cx);
5509 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5510 format.detach_and_log_err(cx);
5511 }
5512 });
5513 }
5514
5515 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5516 if self.read_only(cx) {
5517 return;
5518 }
5519
5520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5521
5522 let mut buffer_edits: HashMap<EntityId, (Entity<Buffer>, Vec<Point>)> = HashMap::default();
5523 let mut rows = Vec::new();
5524 let mut rows_inserted = 0;
5525
5526 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5527 let cursor = selection.head();
5528 let row = cursor.row;
5529
5530 let point = Point::new(row, 0);
5531 let Some((buffer_handle, buffer_point, _)) =
5532 self.buffer.read(cx).point_to_buffer_point(point, cx)
5533 else {
5534 continue;
5535 };
5536
5537 buffer_edits
5538 .entry(buffer_handle.entity_id())
5539 .or_insert_with(|| (buffer_handle, Vec::new()))
5540 .1
5541 .push(buffer_point);
5542
5543 rows_inserted += 1;
5544 rows.push(row + rows_inserted);
5545 }
5546
5547 self.transact(window, cx, |editor, window, cx| {
5548 for (_, (buffer_handle, points)) in &buffer_edits {
5549 buffer_handle.update(cx, |buffer, cx| {
5550 let edits: Vec<_> = points
5551 .iter()
5552 .map(|point| {
5553 let target = Point::new(point.row + 1, 0);
5554 let start_of_line = buffer.point_to_offset(target).min(buffer.len());
5555 (start_of_line..start_of_line, "\n")
5556 })
5557 .collect();
5558 buffer.edit(edits, None, cx);
5559 });
5560 }
5561
5562 editor.change_selections(Default::default(), window, cx, |s| {
5563 let mut index = 0;
5564 s.move_cursors_with(&mut |map, _, _| {
5565 let row = rows[index];
5566 index += 1;
5567
5568 let point = Point::new(row, 0);
5569 let boundary = map.next_line_boundary(point).1;
5570 let clipped = map.clip_point(boundary, Bias::Left);
5571
5572 (clipped, SelectionGoal::None)
5573 });
5574 });
5575
5576 let mut indent_edits = Vec::new();
5577 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5578 for row in rows {
5579 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5580 for (row, indent) in indents {
5581 if indent.len == 0 {
5582 continue;
5583 }
5584
5585 let text = match indent.kind {
5586 IndentKind::Space => " ".repeat(indent.len as usize),
5587 IndentKind::Tab => "\t".repeat(indent.len as usize),
5588 };
5589 let point = Point::new(row.0, 0);
5590 indent_edits.push((point..point, text));
5591 }
5592 }
5593 editor.edit(indent_edits, cx);
5594 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5595 format.detach_and_log_err(cx);
5596 }
5597 });
5598 }
5599
5600 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5601 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5602 original_indent_columns: Vec::new(),
5603 });
5604 self.replace_selections(text, autoindent, window, cx, false);
5605 }
5606
5607 /// Replaces the editor's selections with the provided `text`, applying the
5608 /// given `autoindent_mode` (`None` will skip autoindentation).
5609 ///
5610 /// Early returns if the editor is in read-only mode, without applying any
5611 /// edits.
5612 fn replace_selections(
5613 &mut self,
5614 text: &str,
5615 autoindent_mode: Option<AutoindentMode>,
5616 window: &mut Window,
5617 cx: &mut Context<Self>,
5618 apply_linked_edits: bool,
5619 ) {
5620 if self.read_only(cx) {
5621 return;
5622 }
5623
5624 let text: Arc<str> = text.into();
5625 self.transact(window, cx, |this, window, cx| {
5626 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5627 let linked_edits = if apply_linked_edits {
5628 this.linked_edits_for_selections(text.clone(), cx)
5629 } else {
5630 LinkedEdits::new()
5631 };
5632
5633 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5634 let anchors = {
5635 let snapshot = buffer.read(cx);
5636 old_selections
5637 .iter()
5638 .map(|s| {
5639 let anchor = snapshot.anchor_after(s.head());
5640 s.map(|_| anchor)
5641 })
5642 .collect::<Vec<_>>()
5643 };
5644 buffer.edit(
5645 old_selections
5646 .iter()
5647 .map(|s| (s.start..s.end, text.clone())),
5648 autoindent_mode,
5649 cx,
5650 );
5651 anchors
5652 });
5653
5654 linked_edits.apply(cx);
5655
5656 this.change_selections(Default::default(), window, cx, |s| {
5657 s.select_anchors(selection_anchors);
5658 });
5659
5660 if apply_linked_edits {
5661 refresh_linked_ranges(this, window, cx);
5662 }
5663
5664 cx.notify();
5665 });
5666 }
5667
5668 /// Collects linked edits for the current selections, pairing each linked
5669 /// range with `text`.
5670 pub fn linked_edits_for_selections(&self, text: Arc<str>, cx: &App) -> LinkedEdits {
5671 let mut linked_edits = LinkedEdits::new();
5672 if !self.linked_edit_ranges.is_empty() {
5673 for selection in self.selections.disjoint_anchors() {
5674 let start = selection.start.text_anchor;
5675 let end = selection.end.text_anchor;
5676 linked_edits.push(self, start..end, text.clone(), cx);
5677 }
5678 }
5679 linked_edits
5680 }
5681
5682 /// Deletes the content covered by the current selections and applies
5683 /// linked edits.
5684 pub fn delete_selections_with_linked_edits(
5685 &mut self,
5686 window: &mut Window,
5687 cx: &mut Context<Self>,
5688 ) {
5689 self.replace_selections("", None, window, cx, true);
5690 }
5691
5692 #[cfg(any(test, feature = "test-support"))]
5693 pub fn set_linked_edit_ranges_for_testing(
5694 &mut self,
5695 ranges: Vec<(Range<Point>, Vec<Range<Point>>)>,
5696 cx: &mut Context<Self>,
5697 ) -> Option<()> {
5698 let Some((buffer, _)) = self
5699 .buffer
5700 .read(cx)
5701 .text_anchor_for_position(self.selections.newest_anchor().start, cx)
5702 else {
5703 return None;
5704 };
5705 let buffer = buffer.read(cx);
5706 let buffer_id = buffer.remote_id();
5707 let mut linked_ranges = Vec::with_capacity(ranges.len());
5708 for (base_range, linked_ranges_points) in ranges {
5709 let base_anchor =
5710 buffer.anchor_before(base_range.start)..buffer.anchor_after(base_range.end);
5711 let linked_anchors = linked_ranges_points
5712 .into_iter()
5713 .map(|range| buffer.anchor_before(range.start)..buffer.anchor_after(range.end))
5714 .collect();
5715 linked_ranges.push((base_anchor, linked_anchors));
5716 }
5717 let mut map = HashMap::default();
5718 map.insert(buffer_id, linked_ranges);
5719 self.linked_edit_ranges = linked_editing_ranges::LinkedEditingRanges(map);
5720 Some(())
5721 }
5722
5723 fn trigger_completion_on_input(
5724 &mut self,
5725 text: &str,
5726 trigger_in_words: bool,
5727 window: &mut Window,
5728 cx: &mut Context<Self>,
5729 ) {
5730 let completions_source = self
5731 .context_menu
5732 .borrow()
5733 .as_ref()
5734 .and_then(|menu| match menu {
5735 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5736 CodeContextMenu::CodeActions(_) => None,
5737 });
5738
5739 match completions_source {
5740 Some(CompletionsMenuSource::Words { .. }) => {
5741 self.open_or_update_completions_menu(
5742 Some(CompletionsMenuSource::Words {
5743 ignore_threshold: false,
5744 }),
5745 None,
5746 trigger_in_words,
5747 window,
5748 cx,
5749 );
5750 }
5751 _ => self.open_or_update_completions_menu(
5752 None,
5753 Some(text.to_owned()).filter(|x| !x.is_empty()),
5754 true,
5755 window,
5756 cx,
5757 ),
5758 }
5759 }
5760
5761 /// If any empty selections is touching the start of its innermost containing autoclose
5762 /// region, expand it to select the brackets.
5763 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5764 let selections = self
5765 .selections
5766 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5767 let buffer = self.buffer.read(cx).read(cx);
5768 let new_selections = self
5769 .selections_with_autoclose_regions(selections, &buffer)
5770 .map(|(mut selection, region)| {
5771 if !selection.is_empty() {
5772 return selection;
5773 }
5774
5775 if let Some(region) = region {
5776 let mut range = region.range.to_offset(&buffer);
5777 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5778 range.start -= region.pair.start.len();
5779 if buffer.contains_str_at(range.start, ®ion.pair.start)
5780 && buffer.contains_str_at(range.end, ®ion.pair.end)
5781 {
5782 range.end += region.pair.end.len();
5783 selection.start = range.start;
5784 selection.end = range.end;
5785
5786 return selection;
5787 }
5788 }
5789 }
5790
5791 let always_treat_brackets_as_autoclosed = buffer
5792 .language_settings_at(selection.start, cx)
5793 .always_treat_brackets_as_autoclosed;
5794
5795 if !always_treat_brackets_as_autoclosed {
5796 return selection;
5797 }
5798
5799 if let Some(scope) = buffer.language_scope_at(selection.start) {
5800 for (pair, enabled) in scope.brackets() {
5801 if !enabled || !pair.close {
5802 continue;
5803 }
5804
5805 if buffer.contains_str_at(selection.start, &pair.end) {
5806 let pair_start_len = pair.start.len();
5807 if buffer.contains_str_at(
5808 selection.start.saturating_sub_usize(pair_start_len),
5809 &pair.start,
5810 ) {
5811 selection.start -= pair_start_len;
5812 selection.end += pair.end.len();
5813
5814 return selection;
5815 }
5816 }
5817 }
5818 }
5819
5820 selection
5821 })
5822 .collect();
5823
5824 drop(buffer);
5825 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5826 selections.select(new_selections)
5827 });
5828 }
5829
5830 /// Iterate the given selections, and for each one, find the smallest surrounding
5831 /// autoclose region. This uses the ordering of the selections and the autoclose
5832 /// regions to avoid repeated comparisons.
5833 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5834 &'a self,
5835 selections: impl IntoIterator<Item = Selection<D>>,
5836 buffer: &'a MultiBufferSnapshot,
5837 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5838 let mut i = 0;
5839 let mut regions = self.autoclose_regions.as_slice();
5840 selections.into_iter().map(move |selection| {
5841 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5842
5843 let mut enclosing = None;
5844 while let Some(pair_state) = regions.get(i) {
5845 if pair_state.range.end.to_offset(buffer) < range.start {
5846 regions = ®ions[i + 1..];
5847 i = 0;
5848 } else if pair_state.range.start.to_offset(buffer) > range.end {
5849 break;
5850 } else {
5851 if pair_state.selection_id == selection.id {
5852 enclosing = Some(pair_state);
5853 }
5854 i += 1;
5855 }
5856 }
5857
5858 (selection, enclosing)
5859 })
5860 }
5861
5862 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5863 fn invalidate_autoclose_regions(
5864 &mut self,
5865 mut selections: &[Selection<Anchor>],
5866 buffer: &MultiBufferSnapshot,
5867 ) {
5868 self.autoclose_regions.retain(|state| {
5869 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5870 return false;
5871 }
5872
5873 let mut i = 0;
5874 while let Some(selection) = selections.get(i) {
5875 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5876 selections = &selections[1..];
5877 continue;
5878 }
5879 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5880 break;
5881 }
5882 if selection.id == state.selection_id {
5883 return true;
5884 } else {
5885 i += 1;
5886 }
5887 }
5888 false
5889 });
5890 }
5891
5892 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5893 let offset = position.to_offset(buffer);
5894 let (word_range, kind) =
5895 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5896 if offset > word_range.start && kind == Some(CharKind::Word) {
5897 Some(
5898 buffer
5899 .text_for_range(word_range.start..offset)
5900 .collect::<String>(),
5901 )
5902 } else {
5903 None
5904 }
5905 }
5906
5907 pub fn visible_excerpts(
5908 &self,
5909 lsp_related_only: bool,
5910 cx: &mut Context<Editor>,
5911 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5912 let project = self.project().cloned();
5913 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
5914 let multi_buffer = self.buffer().read(cx);
5915 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5916 multi_buffer_snapshot
5917 .range_to_buffer_ranges(
5918 self.multi_buffer_visible_range(&display_snapshot, cx)
5919 .to_inclusive(),
5920 )
5921 .into_iter()
5922 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5923 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5924 if !lsp_related_only {
5925 return Some((
5926 excerpt_id,
5927 (
5928 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5929 buffer.version().clone(),
5930 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5931 ),
5932 ));
5933 }
5934
5935 let project = project.as_ref()?.read(cx);
5936 let buffer_file = project::File::from_dyn(buffer.file())?;
5937 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5938 let worktree_entry = buffer_worktree
5939 .read(cx)
5940 .entry_for_id(buffer_file.project_entry_id()?)?;
5941 if worktree_entry.is_ignored {
5942 None
5943 } else {
5944 Some((
5945 excerpt_id,
5946 (
5947 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5948 buffer.version().clone(),
5949 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5950 ),
5951 ))
5952 }
5953 })
5954 .collect()
5955 }
5956
5957 pub fn text_layout_details(&self, window: &mut Window, cx: &mut App) -> TextLayoutDetails {
5958 TextLayoutDetails {
5959 text_system: window.text_system().clone(),
5960 editor_style: self.style.clone().unwrap(),
5961 rem_size: window.rem_size(),
5962 scroll_anchor: self.scroll_manager.shared_scroll_anchor(cx),
5963 visible_rows: self.visible_line_count(),
5964 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5965 }
5966 }
5967
5968 fn trigger_on_type_formatting(
5969 &self,
5970 input: String,
5971 window: &mut Window,
5972 cx: &mut Context<Self>,
5973 ) -> Option<Task<Result<()>>> {
5974 if input.chars().count() != 1 {
5975 return None;
5976 }
5977
5978 let project = self.project()?;
5979 let position = self.selections.newest_anchor().head();
5980 let (buffer, buffer_position) = self
5981 .buffer
5982 .read(cx)
5983 .text_anchor_for_position(position, cx)?;
5984
5985 let settings = LanguageSettings::for_buffer_at(&buffer.read(cx), buffer_position, cx);
5986 if !settings.use_on_type_format {
5987 return None;
5988 }
5989
5990 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5991 // hence we do LSP request & edit on host side only — add formats to host's history.
5992 let push_to_lsp_host_history = true;
5993 // If this is not the host, append its history with new edits.
5994 let push_to_client_history = project.read(cx).is_via_collab();
5995
5996 let on_type_formatting = project.update(cx, |project, cx| {
5997 project.on_type_format(
5998 buffer.clone(),
5999 buffer_position,
6000 input,
6001 push_to_lsp_host_history,
6002 cx,
6003 )
6004 });
6005 Some(cx.spawn_in(window, async move |editor, cx| {
6006 if let Some(transaction) = on_type_formatting.await? {
6007 if push_to_client_history {
6008 buffer.update(cx, |buffer, _| {
6009 buffer.push_transaction(transaction, Instant::now());
6010 buffer.finalize_last_transaction();
6011 });
6012 }
6013 editor.update(cx, |editor, cx| {
6014 editor.refresh_document_highlights(cx);
6015 })?;
6016 }
6017 Ok(())
6018 }))
6019 }
6020
6021 pub fn show_word_completions(
6022 &mut self,
6023 _: &ShowWordCompletions,
6024 window: &mut Window,
6025 cx: &mut Context<Self>,
6026 ) {
6027 self.open_or_update_completions_menu(
6028 Some(CompletionsMenuSource::Words {
6029 ignore_threshold: true,
6030 }),
6031 None,
6032 false,
6033 window,
6034 cx,
6035 );
6036 }
6037
6038 pub fn show_completions(
6039 &mut self,
6040 _: &ShowCompletions,
6041 window: &mut Window,
6042 cx: &mut Context<Self>,
6043 ) {
6044 self.open_or_update_completions_menu(None, None, false, window, cx);
6045 }
6046
6047 fn open_or_update_completions_menu(
6048 &mut self,
6049 requested_source: Option<CompletionsMenuSource>,
6050 trigger: Option<String>,
6051 trigger_in_words: bool,
6052 window: &mut Window,
6053 cx: &mut Context<Self>,
6054 ) {
6055 if self.pending_rename.is_some() {
6056 return;
6057 }
6058
6059 let completions_source = self
6060 .context_menu
6061 .borrow()
6062 .as_ref()
6063 .and_then(|menu| match menu {
6064 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
6065 CodeContextMenu::CodeActions(_) => None,
6066 });
6067
6068 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
6069
6070 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
6071 // inserted and selected. To handle that case, the start of the selection is used so that
6072 // the menu starts with all choices.
6073 let position = self
6074 .selections
6075 .newest_anchor()
6076 .start
6077 .bias_right(&multibuffer_snapshot);
6078 if position.diff_base_anchor.is_some() {
6079 return;
6080 }
6081 let buffer_position = multibuffer_snapshot.anchor_before(position);
6082 let Some(buffer) = buffer_position
6083 .text_anchor
6084 .buffer_id
6085 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
6086 else {
6087 return;
6088 };
6089 let buffer_snapshot = buffer.read(cx).snapshot();
6090
6091 let menu_is_open = matches!(
6092 self.context_menu.borrow().as_ref(),
6093 Some(CodeContextMenu::Completions(_))
6094 );
6095
6096 let language = buffer_snapshot
6097 .language_at(buffer_position.text_anchor)
6098 .map(|language| language.name());
6099 let language_settings = multibuffer_snapshot.language_settings_at(buffer_position, cx);
6100 let completion_settings = language_settings.completions.clone();
6101
6102 let show_completions_on_input = self
6103 .show_completions_on_input_override
6104 .unwrap_or(language_settings.show_completions_on_input);
6105 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
6106 return;
6107 }
6108
6109 let query: Option<Arc<String>> =
6110 Self::completion_query(&multibuffer_snapshot, buffer_position)
6111 .map(|query| query.into());
6112
6113 drop(multibuffer_snapshot);
6114
6115 // Hide the current completions menu when query is empty. Without this, cached
6116 // completions from before the trigger char may be reused (#32774).
6117 if query.is_none() && menu_is_open {
6118 self.hide_context_menu(window, cx);
6119 }
6120
6121 let mut ignore_word_threshold = false;
6122 let provider = match requested_source {
6123 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
6124 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
6125 ignore_word_threshold = ignore_threshold;
6126 None
6127 }
6128 Some(CompletionsMenuSource::SnippetChoices)
6129 | Some(CompletionsMenuSource::SnippetsOnly) => {
6130 log::error!("bug: SnippetChoices requested_source is not handled");
6131 None
6132 }
6133 };
6134
6135 let sort_completions = provider
6136 .as_ref()
6137 .is_some_and(|provider| provider.sort_completions());
6138
6139 let filter_completions = provider
6140 .as_ref()
6141 .is_none_or(|provider| provider.filter_completions());
6142
6143 let was_snippets_only = matches!(
6144 completions_source,
6145 Some(CompletionsMenuSource::SnippetsOnly)
6146 );
6147
6148 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
6149 if filter_completions {
6150 menu.filter(
6151 query.clone().unwrap_or_default(),
6152 buffer_position.text_anchor,
6153 &buffer,
6154 provider.clone(),
6155 window,
6156 cx,
6157 );
6158 }
6159 // When `is_incomplete` is false, no need to re-query completions when the current query
6160 // is a suffix of the initial query.
6161 let was_complete = !menu.is_incomplete;
6162 if was_complete && !was_snippets_only {
6163 // If the new query is a suffix of the old query (typing more characters) and
6164 // the previous result was complete, the existing completions can be filtered.
6165 //
6166 // Note that snippet completions are always complete.
6167 let query_matches = match (&menu.initial_query, &query) {
6168 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
6169 (None, _) => true,
6170 _ => false,
6171 };
6172 if query_matches {
6173 let position_matches = if menu.initial_position == position {
6174 true
6175 } else {
6176 let snapshot = self.buffer.read(cx).read(cx);
6177 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
6178 };
6179 if position_matches {
6180 return;
6181 }
6182 }
6183 }
6184 };
6185
6186 let Anchor {
6187 excerpt_id: buffer_excerpt_id,
6188 text_anchor: buffer_position,
6189 ..
6190 } = buffer_position;
6191
6192 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
6193 buffer_snapshot.surrounding_word(buffer_position, None)
6194 {
6195 let word_to_exclude = buffer_snapshot
6196 .text_for_range(word_range.clone())
6197 .collect::<String>();
6198 (
6199 buffer_snapshot.anchor_before(word_range.start)
6200 ..buffer_snapshot.anchor_after(buffer_position),
6201 Some(word_to_exclude),
6202 )
6203 } else {
6204 (buffer_position..buffer_position, None)
6205 };
6206
6207 let show_completion_documentation = buffer_snapshot
6208 .settings_at(buffer_position, cx)
6209 .show_completion_documentation;
6210
6211 // The document can be large, so stay in reasonable bounds when searching for words,
6212 // otherwise completion pop-up might be slow to appear.
6213 const WORD_LOOKUP_ROWS: u32 = 5_000;
6214 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
6215 let min_word_search = buffer_snapshot.clip_point(
6216 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
6217 Bias::Left,
6218 );
6219 let max_word_search = buffer_snapshot.clip_point(
6220 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
6221 Bias::Right,
6222 );
6223 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
6224 ..buffer_snapshot.point_to_offset(max_word_search);
6225
6226 let skip_digits = query
6227 .as_ref()
6228 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
6229
6230 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
6231 trigger.as_ref().is_none_or(|trigger| {
6232 provider.is_completion_trigger(
6233 &buffer,
6234 position.text_anchor,
6235 trigger,
6236 trigger_in_words,
6237 cx,
6238 )
6239 })
6240 });
6241
6242 let provider_responses = if let Some(provider) = &provider
6243 && load_provider_completions
6244 {
6245 let trigger_character =
6246 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
6247 let completion_context = CompletionContext {
6248 trigger_kind: match &trigger_character {
6249 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
6250 None => CompletionTriggerKind::INVOKED,
6251 },
6252 trigger_character,
6253 };
6254
6255 provider.completions(
6256 buffer_excerpt_id,
6257 &buffer,
6258 buffer_position,
6259 completion_context,
6260 window,
6261 cx,
6262 )
6263 } else {
6264 Task::ready(Ok(Vec::new()))
6265 };
6266
6267 let load_word_completions = if !self.word_completions_enabled {
6268 false
6269 } else if requested_source
6270 == Some(CompletionsMenuSource::Words {
6271 ignore_threshold: true,
6272 })
6273 {
6274 true
6275 } else {
6276 load_provider_completions
6277 && completion_settings.words != WordsCompletionMode::Disabled
6278 && (ignore_word_threshold || {
6279 let words_min_length = completion_settings.words_min_length;
6280 // check whether word has at least `words_min_length` characters
6281 let query_chars = query.iter().flat_map(|q| q.chars());
6282 query_chars.take(words_min_length).count() == words_min_length
6283 })
6284 };
6285
6286 let mut words = if load_word_completions {
6287 cx.background_spawn({
6288 let buffer_snapshot = buffer_snapshot.clone();
6289 async move {
6290 buffer_snapshot.words_in_range(WordsQuery {
6291 fuzzy_contents: None,
6292 range: word_search_range,
6293 skip_digits,
6294 })
6295 }
6296 })
6297 } else {
6298 Task::ready(BTreeMap::default())
6299 };
6300
6301 let snippets = if let Some(provider) = &provider
6302 && provider.show_snippets()
6303 && let Some(project) = self.project()
6304 {
6305 let char_classifier = buffer_snapshot
6306 .char_classifier_at(buffer_position)
6307 .scope_context(Some(CharScopeContext::Completion));
6308 project.update(cx, |project, cx| {
6309 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
6310 })
6311 } else {
6312 Task::ready(Ok(CompletionResponse {
6313 completions: Vec::new(),
6314 display_options: Default::default(),
6315 is_incomplete: false,
6316 }))
6317 };
6318
6319 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
6320
6321 let id = post_inc(&mut self.next_completion_id);
6322 let task = cx.spawn_in(window, async move |editor, cx| {
6323 let Ok(()) = editor.update(cx, |this, _| {
6324 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
6325 }) else {
6326 return;
6327 };
6328
6329 // TODO: Ideally completions from different sources would be selectively re-queried, so
6330 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
6331 let mut completions = Vec::new();
6332 let mut is_incomplete = false;
6333 let mut display_options: Option<CompletionDisplayOptions> = None;
6334 if let Some(provider_responses) = provider_responses.await.log_err()
6335 && !provider_responses.is_empty()
6336 {
6337 for response in provider_responses {
6338 completions.extend(response.completions);
6339 is_incomplete = is_incomplete || response.is_incomplete;
6340 match display_options.as_mut() {
6341 None => {
6342 display_options = Some(response.display_options);
6343 }
6344 Some(options) => options.merge(&response.display_options),
6345 }
6346 }
6347 if completion_settings.words == WordsCompletionMode::Fallback {
6348 words = Task::ready(BTreeMap::default());
6349 }
6350 }
6351 let display_options = display_options.unwrap_or_default();
6352
6353 let mut words = words.await;
6354 if let Some(word_to_exclude) = &word_to_exclude {
6355 words.remove(word_to_exclude);
6356 }
6357 for lsp_completion in &completions {
6358 words.remove(&lsp_completion.new_text);
6359 }
6360 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
6361 replace_range: word_replace_range.clone(),
6362 new_text: word.clone(),
6363 label: CodeLabel::plain(word, None),
6364 match_start: None,
6365 snippet_deduplication_key: None,
6366 icon_path: None,
6367 documentation: None,
6368 source: CompletionSource::BufferWord {
6369 word_range,
6370 resolved: false,
6371 },
6372 insert_text_mode: Some(InsertTextMode::AS_IS),
6373 confirm: None,
6374 }));
6375
6376 completions.extend(
6377 snippets
6378 .await
6379 .into_iter()
6380 .flat_map(|response| response.completions),
6381 );
6382
6383 let menu = if completions.is_empty() {
6384 None
6385 } else {
6386 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
6387 let languages = editor
6388 .workspace
6389 .as_ref()
6390 .and_then(|(workspace, _)| workspace.upgrade())
6391 .map(|workspace| workspace.read(cx).app_state().languages.clone());
6392 let menu = CompletionsMenu::new(
6393 id,
6394 requested_source.unwrap_or(if load_provider_completions {
6395 CompletionsMenuSource::Normal
6396 } else {
6397 CompletionsMenuSource::SnippetsOnly
6398 }),
6399 sort_completions,
6400 show_completion_documentation,
6401 position,
6402 query.clone(),
6403 is_incomplete,
6404 buffer.clone(),
6405 completions.into(),
6406 editor
6407 .context_menu()
6408 .borrow_mut()
6409 .as_ref()
6410 .map(|menu| menu.primary_scroll_handle()),
6411 display_options,
6412 snippet_sort_order,
6413 languages,
6414 language,
6415 cx,
6416 );
6417
6418 let query = if filter_completions { query } else { None };
6419 let matches_task = menu.do_async_filtering(
6420 query.unwrap_or_default(),
6421 buffer_position,
6422 &buffer,
6423 cx,
6424 );
6425 (menu, matches_task)
6426 }) else {
6427 return;
6428 };
6429
6430 let matches = matches_task.await;
6431
6432 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
6433 // Newer menu already set, so exit.
6434 if let Some(CodeContextMenu::Completions(prev_menu)) =
6435 editor.context_menu.borrow().as_ref()
6436 && prev_menu.id > id
6437 {
6438 return;
6439 };
6440
6441 // Only valid to take prev_menu because either the new menu is immediately set
6442 // below, or the menu is hidden.
6443 if let Some(CodeContextMenu::Completions(prev_menu)) =
6444 editor.context_menu.borrow_mut().take()
6445 {
6446 let position_matches =
6447 if prev_menu.initial_position == menu.initial_position {
6448 true
6449 } else {
6450 let snapshot = editor.buffer.read(cx).read(cx);
6451 prev_menu.initial_position.to_offset(&snapshot)
6452 == menu.initial_position.to_offset(&snapshot)
6453 };
6454 if position_matches {
6455 // Preserve markdown cache before `set_filter_results` because it will
6456 // try to populate the documentation cache.
6457 menu.preserve_markdown_cache(prev_menu);
6458 }
6459 };
6460
6461 menu.set_filter_results(matches, provider, window, cx);
6462 }) else {
6463 return;
6464 };
6465
6466 menu.visible().then_some(menu)
6467 };
6468
6469 editor
6470 .update_in(cx, |editor, window, cx| {
6471 if editor.focus_handle.is_focused(window)
6472 && let Some(menu) = menu
6473 {
6474 *editor.context_menu.borrow_mut() =
6475 Some(CodeContextMenu::Completions(menu));
6476
6477 crate::hover_popover::hide_hover(editor, cx);
6478 if editor.show_edit_predictions_in_menu() {
6479 editor.update_visible_edit_prediction(window, cx);
6480 } else {
6481 editor
6482 .discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6483 }
6484
6485 cx.notify();
6486 return;
6487 }
6488
6489 if editor.completion_tasks.len() <= 1 {
6490 // If there are no more completion tasks and the last menu was empty, we should hide it.
6491 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6492 // If it was already hidden and we don't show edit predictions in the menu,
6493 // we should also show the edit prediction when available.
6494 if was_hidden && editor.show_edit_predictions_in_menu() {
6495 editor.update_visible_edit_prediction(window, cx);
6496 }
6497 }
6498 })
6499 .ok();
6500 });
6501
6502 self.completion_tasks.push((id, task));
6503 }
6504
6505 #[cfg(any(test, feature = "test-support"))]
6506 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6507 let menu = self.context_menu.borrow();
6508 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6509 let completions = menu.completions.borrow();
6510 Some(completions.to_vec())
6511 } else {
6512 None
6513 }
6514 }
6515
6516 pub fn with_completions_menu_matching_id<R>(
6517 &self,
6518 id: CompletionId,
6519 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6520 ) -> R {
6521 let mut context_menu = self.context_menu.borrow_mut();
6522 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6523 return f(None);
6524 };
6525 if completions_menu.id != id {
6526 return f(None);
6527 }
6528 f(Some(completions_menu))
6529 }
6530
6531 pub fn confirm_completion(
6532 &mut self,
6533 action: &ConfirmCompletion,
6534 window: &mut Window,
6535 cx: &mut Context<Self>,
6536 ) -> Option<Task<Result<()>>> {
6537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6538 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6539 }
6540
6541 pub fn confirm_completion_insert(
6542 &mut self,
6543 _: &ConfirmCompletionInsert,
6544 window: &mut Window,
6545 cx: &mut Context<Self>,
6546 ) -> Option<Task<Result<()>>> {
6547 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6548 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6549 }
6550
6551 pub fn confirm_completion_replace(
6552 &mut self,
6553 _: &ConfirmCompletionReplace,
6554 window: &mut Window,
6555 cx: &mut Context<Self>,
6556 ) -> Option<Task<Result<()>>> {
6557 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6558 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6559 }
6560
6561 pub fn compose_completion(
6562 &mut self,
6563 action: &ComposeCompletion,
6564 window: &mut Window,
6565 cx: &mut Context<Self>,
6566 ) -> Option<Task<Result<()>>> {
6567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6568 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6569 }
6570
6571 fn do_completion(
6572 &mut self,
6573 item_ix: Option<usize>,
6574 intent: CompletionIntent,
6575 window: &mut Window,
6576 cx: &mut Context<Editor>,
6577 ) -> Option<Task<Result<()>>> {
6578 use language::ToOffset as _;
6579
6580 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6581 else {
6582 return None;
6583 };
6584
6585 let candidate_id = {
6586 let entries = completions_menu.entries.borrow();
6587 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6588 if self.show_edit_predictions_in_menu() {
6589 self.discard_edit_prediction(EditPredictionDiscardReason::Rejected, cx);
6590 }
6591 mat.candidate_id
6592 };
6593
6594 let completion = completions_menu
6595 .completions
6596 .borrow()
6597 .get(candidate_id)?
6598 .clone();
6599 cx.stop_propagation();
6600
6601 let buffer_handle = completions_menu.buffer.clone();
6602
6603 let CompletionEdit {
6604 new_text,
6605 snippet,
6606 replace_range,
6607 } = process_completion_for_edit(
6608 &completion,
6609 intent,
6610 &buffer_handle,
6611 &completions_menu.initial_position.text_anchor,
6612 cx,
6613 );
6614
6615 let buffer = buffer_handle.read(cx);
6616 let snapshot = self.buffer.read(cx).snapshot(cx);
6617 let newest_anchor = self.selections.newest_anchor();
6618 let replace_range_multibuffer = {
6619 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6620 excerpt.map_range_from_buffer(replace_range.clone())
6621 };
6622 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6623 return None;
6624 }
6625
6626 let old_text = buffer
6627 .text_for_range(replace_range.clone())
6628 .collect::<String>();
6629 let lookbehind = newest_anchor
6630 .start
6631 .text_anchor
6632 .to_offset(buffer)
6633 .saturating_sub(replace_range.start.0);
6634 let lookahead = replace_range
6635 .end
6636 .0
6637 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6638 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6639 let suffix = &old_text[lookbehind.min(old_text.len())..];
6640
6641 let selections = self
6642 .selections
6643 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6644 let mut ranges = Vec::new();
6645 let mut all_commit_ranges = Vec::new();
6646 let mut linked_edits = LinkedEdits::new();
6647
6648 let text: Arc<str> = new_text.clone().into();
6649 for selection in &selections {
6650 let range = if selection.id == newest_anchor.id {
6651 replace_range_multibuffer.clone()
6652 } else {
6653 let mut range = selection.range();
6654
6655 // if prefix is present, don't duplicate it
6656 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6657 range.start = range.start.saturating_sub_usize(lookbehind);
6658
6659 // if suffix is also present, mimic the newest cursor and replace it
6660 if selection.id != newest_anchor.id
6661 && snapshot.contains_str_at(range.end, suffix)
6662 {
6663 range.end += lookahead;
6664 }
6665 }
6666 range
6667 };
6668
6669 ranges.push(range.clone());
6670
6671 let start_anchor = snapshot.anchor_before(range.start);
6672 let end_anchor = snapshot.anchor_after(range.end);
6673 let anchor_range = start_anchor.text_anchor..end_anchor.text_anchor;
6674 all_commit_ranges.push(anchor_range.clone());
6675
6676 if !self.linked_edit_ranges.is_empty() {
6677 linked_edits.push(&self, anchor_range, text.clone(), cx);
6678 }
6679 }
6680
6681 let common_prefix_len = old_text
6682 .chars()
6683 .zip(new_text.chars())
6684 .take_while(|(a, b)| a == b)
6685 .map(|(a, _)| a.len_utf8())
6686 .sum::<usize>();
6687
6688 cx.emit(EditorEvent::InputHandled {
6689 utf16_range_to_replace: None,
6690 text: new_text[common_prefix_len..].into(),
6691 });
6692
6693 self.transact(window, cx, |editor, window, cx| {
6694 if let Some(mut snippet) = snippet {
6695 snippet.text = new_text.to_string();
6696 editor
6697 .insert_snippet(&ranges, snippet, window, cx)
6698 .log_err();
6699 } else {
6700 editor.buffer.update(cx, |multi_buffer, cx| {
6701 let auto_indent = match completion.insert_text_mode {
6702 Some(InsertTextMode::AS_IS) => None,
6703 _ => editor.autoindent_mode.clone(),
6704 };
6705 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6706 multi_buffer.edit(edits, auto_indent, cx);
6707 });
6708 }
6709 linked_edits.apply(cx);
6710 editor.refresh_edit_prediction(true, false, window, cx);
6711 });
6712 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6713
6714 let show_new_completions_on_confirm = completion
6715 .confirm
6716 .as_ref()
6717 .is_some_and(|confirm| confirm(intent, window, cx));
6718 if show_new_completions_on_confirm {
6719 self.open_or_update_completions_menu(None, None, false, window, cx);
6720 }
6721
6722 let provider = self.completion_provider.as_ref()?;
6723
6724 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6725 let command = lsp_store.as_ref().and_then(|lsp_store| {
6726 let CompletionSource::Lsp {
6727 lsp_completion,
6728 server_id,
6729 ..
6730 } = &completion.source
6731 else {
6732 return None;
6733 };
6734 let lsp_command = lsp_completion.command.as_ref()?;
6735 let available_commands = lsp_store
6736 .read(cx)
6737 .lsp_server_capabilities
6738 .get(server_id)
6739 .and_then(|server_capabilities| {
6740 server_capabilities
6741 .execute_command_provider
6742 .as_ref()
6743 .map(|options| options.commands.as_slice())
6744 })?;
6745 if available_commands.contains(&lsp_command.command) {
6746 Some(CodeAction {
6747 server_id: *server_id,
6748 range: language::Anchor::MIN..language::Anchor::MIN,
6749 lsp_action: LspAction::Command(lsp_command.clone()),
6750 resolved: false,
6751 })
6752 } else {
6753 None
6754 }
6755 });
6756
6757 drop(completion);
6758 let apply_edits = provider.apply_additional_edits_for_completion(
6759 buffer_handle.clone(),
6760 completions_menu.completions.clone(),
6761 candidate_id,
6762 true,
6763 all_commit_ranges,
6764 cx,
6765 );
6766
6767 let editor_settings = EditorSettings::get_global(cx);
6768 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6769 // After the code completion is finished, users often want to know what signatures are needed.
6770 // so we should automatically call signature_help
6771 self.show_signature_help(&ShowSignatureHelp, window, cx);
6772 }
6773
6774 Some(cx.spawn_in(window, async move |editor, cx| {
6775 apply_edits.await?;
6776
6777 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6778 let title = command.lsp_action.title().to_owned();
6779 let project_transaction = lsp_store
6780 .update(cx, |lsp_store, cx| {
6781 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6782 })
6783 .await
6784 .context("applying post-completion command")?;
6785 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6786 Self::open_project_transaction(
6787 &editor,
6788 workspace.downgrade(),
6789 project_transaction,
6790 title,
6791 cx,
6792 )
6793 .await?;
6794 }
6795 }
6796
6797 Ok(())
6798 }))
6799 }
6800
6801 pub fn toggle_code_actions(
6802 &mut self,
6803 action: &ToggleCodeActions,
6804 window: &mut Window,
6805 cx: &mut Context<Self>,
6806 ) {
6807 let quick_launch = action.quick_launch;
6808 let mut context_menu = self.context_menu.borrow_mut();
6809 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6810 if code_actions.deployed_from == action.deployed_from {
6811 // Toggle if we're selecting the same one
6812 *context_menu = None;
6813 cx.notify();
6814 return;
6815 } else {
6816 // Otherwise, clear it and start a new one
6817 *context_menu = None;
6818 cx.notify();
6819 }
6820 }
6821 drop(context_menu);
6822 let snapshot = self.snapshot(window, cx);
6823 let deployed_from = action.deployed_from.clone();
6824 let action = action.clone();
6825 self.completion_tasks.clear();
6826 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
6827
6828 let multibuffer_point = match &action.deployed_from {
6829 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6830 DisplayPoint::new(*row, 0).to_point(&snapshot)
6831 }
6832 _ => self
6833 .selections
6834 .newest::<Point>(&snapshot.display_snapshot)
6835 .head(),
6836 };
6837 let Some((buffer, buffer_row)) = snapshot
6838 .buffer_snapshot()
6839 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6840 .and_then(|(buffer_snapshot, range)| {
6841 self.buffer()
6842 .read(cx)
6843 .buffer(buffer_snapshot.remote_id())
6844 .map(|buffer| (buffer, range.start.row))
6845 })
6846 else {
6847 return;
6848 };
6849 let buffer_id = buffer.read(cx).remote_id();
6850 let tasks = self
6851 .runnables
6852 .runnables((buffer_id, buffer_row))
6853 .map(|t| Arc::new(t.to_owned()));
6854
6855 if !self.focus_handle.is_focused(window) {
6856 return;
6857 }
6858 let project = self.project.clone();
6859
6860 let code_actions_task = match deployed_from {
6861 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6862 _ => self.code_actions(buffer_row, window, cx),
6863 };
6864
6865 let runnable_task = match deployed_from {
6866 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6867 _ => {
6868 let mut task_context_task = Task::ready(None);
6869 if let Some(tasks) = &tasks
6870 && let Some(project) = project
6871 {
6872 task_context_task =
6873 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6874 }
6875
6876 cx.spawn_in(window, {
6877 let buffer = buffer.clone();
6878 async move |editor, cx| {
6879 let task_context = task_context_task.await;
6880
6881 let resolved_tasks =
6882 tasks
6883 .zip(task_context.clone())
6884 .map(|(tasks, task_context)| ResolvedTasks {
6885 templates: tasks.resolve(&task_context).collect(),
6886 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6887 multibuffer_point.row,
6888 tasks.column,
6889 )),
6890 });
6891 let debug_scenarios = editor
6892 .update(cx, |editor, cx| {
6893 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6894 })?
6895 .await;
6896 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6897 }
6898 })
6899 }
6900 };
6901
6902 cx.spawn_in(window, async move |editor, cx| {
6903 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6904 let code_actions = code_actions_task.await;
6905 let spawn_straight_away = quick_launch
6906 && resolved_tasks
6907 .as_ref()
6908 .is_some_and(|tasks| tasks.templates.len() == 1)
6909 && code_actions
6910 .as_ref()
6911 .is_none_or(|actions| actions.is_empty())
6912 && debug_scenarios.is_empty();
6913
6914 editor.update_in(cx, |editor, window, cx| {
6915 crate::hover_popover::hide_hover(editor, cx);
6916 let actions = CodeActionContents::new(
6917 resolved_tasks,
6918 code_actions,
6919 debug_scenarios,
6920 task_context.unwrap_or_default(),
6921 );
6922
6923 // Don't show the menu if there are no actions available
6924 if actions.is_empty() {
6925 cx.notify();
6926 return Task::ready(Ok(()));
6927 }
6928
6929 *editor.context_menu.borrow_mut() =
6930 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6931 buffer,
6932 actions,
6933 selected_item: Default::default(),
6934 scroll_handle: UniformListScrollHandle::default(),
6935 deployed_from,
6936 }));
6937 cx.notify();
6938 if spawn_straight_away
6939 && let Some(task) = editor.confirm_code_action(
6940 &ConfirmCodeAction { item_ix: Some(0) },
6941 window,
6942 cx,
6943 )
6944 {
6945 return task;
6946 }
6947
6948 Task::ready(Ok(()))
6949 })
6950 })
6951 .detach_and_log_err(cx);
6952 }
6953
6954 fn debug_scenarios(
6955 &mut self,
6956 resolved_tasks: &Option<ResolvedTasks>,
6957 buffer: &Entity<Buffer>,
6958 cx: &mut App,
6959 ) -> Task<Vec<task::DebugScenario>> {
6960 maybe!({
6961 let project = self.project()?;
6962 let dap_store = project.read(cx).dap_store();
6963 let mut scenarios = vec![];
6964 let resolved_tasks = resolved_tasks.as_ref()?;
6965 let buffer = buffer.read(cx);
6966 let language = buffer.language()?;
6967 let debug_adapter = LanguageSettings::for_buffer(&buffer, cx)
6968 .debuggers
6969 .first()
6970 .map(SharedString::from)
6971 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6972
6973 dap_store.update(cx, |dap_store, cx| {
6974 for (_, task) in &resolved_tasks.templates {
6975 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6976 task.original_task().clone(),
6977 debug_adapter.clone().into(),
6978 task.display_label().to_owned().into(),
6979 cx,
6980 );
6981 scenarios.push(maybe_scenario);
6982 }
6983 });
6984 Some(cx.background_spawn(async move {
6985 futures::future::join_all(scenarios)
6986 .await
6987 .into_iter()
6988 .flatten()
6989 .collect::<Vec<_>>()
6990 }))
6991 })
6992 .unwrap_or_else(|| Task::ready(vec![]))
6993 }
6994
6995 fn code_actions(
6996 &mut self,
6997 buffer_row: u32,
6998 window: &mut Window,
6999 cx: &mut Context<Self>,
7000 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
7001 let mut task = self.code_actions_task.take();
7002 cx.spawn_in(window, async move |editor, cx| {
7003 while let Some(prev_task) = task {
7004 prev_task.await.log_err();
7005 task = editor
7006 .update(cx, |this, _| this.code_actions_task.take())
7007 .ok()?;
7008 }
7009
7010 editor
7011 .update(cx, |editor, cx| {
7012 editor
7013 .available_code_actions
7014 .clone()
7015 .and_then(|(location, code_actions)| {
7016 let snapshot = location.buffer.read(cx).snapshot();
7017 let point_range = location.range.to_point(&snapshot);
7018 let point_range = point_range.start.row..=point_range.end.row;
7019 if point_range.contains(&buffer_row) {
7020 Some(code_actions)
7021 } else {
7022 None
7023 }
7024 })
7025 })
7026 .ok()
7027 .flatten()
7028 })
7029 }
7030
7031 pub fn confirm_code_action(
7032 &mut self,
7033 action: &ConfirmCodeAction,
7034 window: &mut Window,
7035 cx: &mut Context<Self>,
7036 ) -> Option<Task<Result<()>>> {
7037 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
7038
7039 let actions_menu =
7040 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
7041 menu
7042 } else {
7043 return None;
7044 };
7045
7046 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
7047 let action = actions_menu.actions.get(action_ix)?;
7048 let title = action.label();
7049 let buffer = actions_menu.buffer;
7050 let workspace = self.workspace()?;
7051
7052 match action {
7053 CodeActionsItem::Task(task_source_kind, resolved_task) => {
7054 workspace.update(cx, |workspace, cx| {
7055 workspace.schedule_resolved_task(
7056 task_source_kind,
7057 resolved_task,
7058 false,
7059 window,
7060 cx,
7061 );
7062
7063 Some(Task::ready(Ok(())))
7064 })
7065 }
7066 CodeActionsItem::CodeAction {
7067 excerpt_id,
7068 action,
7069 provider,
7070 } => {
7071 let apply_code_action =
7072 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
7073 let workspace = workspace.downgrade();
7074 Some(cx.spawn_in(window, async move |editor, cx| {
7075 let project_transaction = apply_code_action.await?;
7076 Self::open_project_transaction(
7077 &editor,
7078 workspace,
7079 project_transaction,
7080 title,
7081 cx,
7082 )
7083 .await
7084 }))
7085 }
7086 CodeActionsItem::DebugScenario(scenario) => {
7087 let context = actions_menu.actions.context.into();
7088
7089 workspace.update(cx, |workspace, cx| {
7090 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
7091 workspace.start_debug_session(
7092 scenario,
7093 context,
7094 Some(buffer),
7095 None,
7096 window,
7097 cx,
7098 );
7099 });
7100 Some(Task::ready(Ok(())))
7101 }
7102 }
7103 }
7104
7105 fn open_transaction_for_hidden_buffers(
7106 workspace: Entity<Workspace>,
7107 transaction: ProjectTransaction,
7108 title: String,
7109 window: &mut Window,
7110 cx: &mut Context<Self>,
7111 ) {
7112 if transaction.0.is_empty() {
7113 return;
7114 }
7115
7116 let edited_buffers_already_open = {
7117 let other_editors: Vec<Entity<Editor>> = workspace
7118 .read(cx)
7119 .panes()
7120 .iter()
7121 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
7122 .filter(|editor| editor.entity_id() != cx.entity_id())
7123 .collect();
7124
7125 transaction.0.keys().all(|buffer| {
7126 other_editors.iter().any(|editor| {
7127 let multi_buffer = editor.read(cx).buffer();
7128 multi_buffer.read(cx).is_singleton()
7129 && multi_buffer
7130 .read(cx)
7131 .as_singleton()
7132 .map_or(false, |singleton| {
7133 singleton.entity_id() == buffer.entity_id()
7134 })
7135 })
7136 })
7137 };
7138 if !edited_buffers_already_open {
7139 let workspace = workspace.downgrade();
7140 cx.defer_in(window, move |_, window, cx| {
7141 cx.spawn_in(window, async move |editor, cx| {
7142 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
7143 .await
7144 .ok()
7145 })
7146 .detach();
7147 });
7148 }
7149 }
7150
7151 pub async fn open_project_transaction(
7152 editor: &WeakEntity<Editor>,
7153 workspace: WeakEntity<Workspace>,
7154 transaction: ProjectTransaction,
7155 title: String,
7156 cx: &mut AsyncWindowContext,
7157 ) -> Result<()> {
7158 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
7159 cx.update(|_, cx| {
7160 entries.sort_unstable_by_key(|(buffer, _)| {
7161 buffer.read(cx).file().map(|f| f.path().clone())
7162 });
7163 })?;
7164 if entries.is_empty() {
7165 return Ok(());
7166 }
7167
7168 // If the project transaction's edits are all contained within this editor, then
7169 // avoid opening a new editor to display them.
7170
7171 if let [(buffer, transaction)] = &*entries {
7172 let excerpt = editor.update(cx, |editor, cx| {
7173 editor
7174 .buffer()
7175 .read(cx)
7176 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
7177 })?;
7178 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
7179 && excerpted_buffer == *buffer
7180 {
7181 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
7182 let excerpt_range = excerpt_range.to_offset(buffer);
7183 buffer
7184 .edited_ranges_for_transaction::<usize>(transaction)
7185 .all(|range| {
7186 excerpt_range.start <= range.start && excerpt_range.end >= range.end
7187 })
7188 });
7189
7190 if all_edits_within_excerpt {
7191 return Ok(());
7192 }
7193 }
7194 }
7195
7196 let mut ranges_to_highlight = Vec::new();
7197 let excerpt_buffer = cx.new(|cx| {
7198 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
7199 for (buffer_handle, transaction) in &entries {
7200 let edited_ranges = buffer_handle
7201 .read(cx)
7202 .edited_ranges_for_transaction::<Point>(transaction)
7203 .collect::<Vec<_>>();
7204 let (ranges, _) = multibuffer.set_excerpts_for_path(
7205 PathKey::for_buffer(buffer_handle, cx),
7206 buffer_handle.clone(),
7207 edited_ranges,
7208 multibuffer_context_lines(cx),
7209 cx,
7210 );
7211
7212 ranges_to_highlight.extend(ranges);
7213 }
7214 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
7215 multibuffer
7216 });
7217
7218 workspace.update_in(cx, |workspace, window, cx| {
7219 let project = workspace.project().clone();
7220 let editor =
7221 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
7222 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
7223 editor.update(cx, |editor, cx| {
7224 editor.highlight_background(
7225 HighlightKey::Editor,
7226 &ranges_to_highlight,
7227 |_, theme| theme.colors().editor_highlighted_line_background,
7228 cx,
7229 );
7230 });
7231 })?;
7232
7233 Ok(())
7234 }
7235
7236 pub fn clear_code_action_providers(&mut self) {
7237 self.code_action_providers.clear();
7238 self.available_code_actions.take();
7239 }
7240
7241 pub fn add_code_action_provider(
7242 &mut self,
7243 provider: Rc<dyn CodeActionProvider>,
7244 window: &mut Window,
7245 cx: &mut Context<Self>,
7246 ) {
7247 if self
7248 .code_action_providers
7249 .iter()
7250 .any(|existing_provider| existing_provider.id() == provider.id())
7251 {
7252 return;
7253 }
7254
7255 self.code_action_providers.push(provider);
7256 self.refresh_code_actions(window, cx);
7257 }
7258
7259 pub fn remove_code_action_provider(
7260 &mut self,
7261 id: Arc<str>,
7262 window: &mut Window,
7263 cx: &mut Context<Self>,
7264 ) {
7265 self.code_action_providers
7266 .retain(|provider| provider.id() != id);
7267 self.refresh_code_actions(window, cx);
7268 }
7269
7270 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
7271 !self.code_action_providers.is_empty()
7272 && EditorSettings::get_global(cx).toolbar.code_actions
7273 }
7274
7275 pub fn has_available_code_actions(&self) -> bool {
7276 self.available_code_actions
7277 .as_ref()
7278 .is_some_and(|(_, actions)| !actions.is_empty())
7279 }
7280
7281 fn render_inline_code_actions(
7282 &self,
7283 icon_size: ui::IconSize,
7284 display_row: DisplayRow,
7285 is_active: bool,
7286 cx: &mut Context<Self>,
7287 ) -> AnyElement {
7288 let show_tooltip = !self.context_menu_visible();
7289 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
7290 .icon_size(icon_size)
7291 .shape(ui::IconButtonShape::Square)
7292 .icon_color(ui::Color::Hidden)
7293 .toggle_state(is_active)
7294 .when(show_tooltip, |this| {
7295 this.tooltip({
7296 let focus_handle = self.focus_handle.clone();
7297 move |_window, cx| {
7298 Tooltip::for_action_in(
7299 "Toggle Code Actions",
7300 &ToggleCodeActions {
7301 deployed_from: None,
7302 quick_launch: false,
7303 },
7304 &focus_handle,
7305 cx,
7306 )
7307 }
7308 })
7309 })
7310 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
7311 window.focus(&editor.focus_handle(cx), cx);
7312 editor.toggle_code_actions(
7313 &crate::actions::ToggleCodeActions {
7314 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
7315 display_row,
7316 )),
7317 quick_launch: false,
7318 },
7319 window,
7320 cx,
7321 );
7322 }))
7323 .into_any_element()
7324 }
7325
7326 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
7327 &self.context_menu
7328 }
7329
7330 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7331 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
7332 cx.background_executor()
7333 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
7334 .await;
7335
7336 let (start_buffer, start, _, end, newest_selection) = this
7337 .update(cx, |this, cx| {
7338 let newest_selection = this.selections.newest_anchor().clone();
7339 if newest_selection.head().diff_base_anchor.is_some() {
7340 return None;
7341 }
7342 let display_snapshot = this.display_snapshot(cx);
7343 let newest_selection_adjusted =
7344 this.selections.newest_adjusted(&display_snapshot);
7345 let buffer = this.buffer.read(cx);
7346
7347 let (start_buffer, start) =
7348 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
7349 let (end_buffer, end) =
7350 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
7351
7352 Some((start_buffer, start, end_buffer, end, newest_selection))
7353 })?
7354 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
7355 .context(
7356 "Expected selection to lie in a single buffer when refreshing code actions",
7357 )?;
7358 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
7359 let providers = this.code_action_providers.clone();
7360 let tasks = this
7361 .code_action_providers
7362 .iter()
7363 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
7364 .collect::<Vec<_>>();
7365 (providers, tasks)
7366 })?;
7367
7368 let mut actions = Vec::new();
7369 for (provider, provider_actions) in
7370 providers.into_iter().zip(future::join_all(tasks).await)
7371 {
7372 if let Some(provider_actions) = provider_actions.log_err() {
7373 actions.extend(provider_actions.into_iter().map(|action| {
7374 AvailableCodeAction {
7375 excerpt_id: newest_selection.start.excerpt_id,
7376 action,
7377 provider: provider.clone(),
7378 }
7379 }));
7380 }
7381 }
7382
7383 this.update(cx, |this, cx| {
7384 this.available_code_actions = if actions.is_empty() {
7385 None
7386 } else {
7387 Some((
7388 Location {
7389 buffer: start_buffer,
7390 range: start..end,
7391 },
7392 actions.into(),
7393 ))
7394 };
7395 cx.notify();
7396 })
7397 }));
7398 }
7399
7400 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7401 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
7402 self.show_git_blame_inline = false;
7403
7404 self.show_git_blame_inline_delay_task =
7405 Some(cx.spawn_in(window, async move |this, cx| {
7406 cx.background_executor().timer(delay).await;
7407
7408 this.update(cx, |this, cx| {
7409 this.show_git_blame_inline = true;
7410 cx.notify();
7411 })
7412 .log_err();
7413 }));
7414 }
7415 }
7416
7417 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
7418 let snapshot = self.snapshot(window, cx);
7419 let cursor = self
7420 .selections
7421 .newest::<Point>(&snapshot.display_snapshot)
7422 .head();
7423 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
7424 else {
7425 return;
7426 };
7427
7428 if self.blame.is_none() {
7429 self.start_git_blame(true, window, cx);
7430 }
7431 let Some(blame) = self.blame.as_ref() else {
7432 return;
7433 };
7434
7435 let row_info = RowInfo {
7436 buffer_id: Some(buffer.remote_id()),
7437 buffer_row: Some(point.row),
7438 ..Default::default()
7439 };
7440 let Some((buffer, blame_entry)) = blame
7441 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
7442 .flatten()
7443 else {
7444 return;
7445 };
7446
7447 let anchor = self.selections.newest_anchor().head();
7448 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7449 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7450 self.show_blame_popover(
7451 buffer,
7452 &blame_entry,
7453 position + last_bounds.origin,
7454 true,
7455 cx,
7456 );
7457 };
7458 }
7459
7460 fn show_blame_popover(
7461 &mut self,
7462 buffer: BufferId,
7463 blame_entry: &BlameEntry,
7464 position: gpui::Point<Pixels>,
7465 ignore_timeout: bool,
7466 cx: &mut Context<Self>,
7467 ) {
7468 if let Some(state) = &mut self.inline_blame_popover {
7469 state.hide_task.take();
7470 } else {
7471 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7472 let blame_entry = blame_entry.clone();
7473 let show_task = cx.spawn(async move |editor, cx| {
7474 if !ignore_timeout {
7475 cx.background_executor()
7476 .timer(std::time::Duration::from_millis(blame_popover_delay))
7477 .await;
7478 }
7479 editor
7480 .update(cx, |editor, cx| {
7481 editor.inline_blame_popover_show_task.take();
7482 let Some(blame) = editor.blame.as_ref() else {
7483 return;
7484 };
7485 let blame = blame.read(cx);
7486 let details = blame.details_for_entry(buffer, &blame_entry);
7487 let markdown = cx.new(|cx| {
7488 Markdown::new(
7489 details
7490 .as_ref()
7491 .map(|message| message.message.clone())
7492 .unwrap_or_default(),
7493 None,
7494 None,
7495 cx,
7496 )
7497 });
7498 editor.inline_blame_popover = Some(InlineBlamePopover {
7499 position,
7500 hide_task: None,
7501 popover_bounds: None,
7502 popover_state: InlineBlamePopoverState {
7503 scroll_handle: ScrollHandle::new(),
7504 commit_message: details,
7505 markdown,
7506 },
7507 keyboard_grace: ignore_timeout,
7508 });
7509 cx.notify();
7510 })
7511 .ok();
7512 });
7513 self.inline_blame_popover_show_task = Some(show_task);
7514 }
7515 }
7516
7517 pub fn has_mouse_context_menu(&self) -> bool {
7518 self.mouse_context_menu.is_some()
7519 }
7520
7521 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7522 self.inline_blame_popover_show_task.take();
7523 if let Some(state) = &mut self.inline_blame_popover {
7524 let hide_task = cx.spawn(async move |editor, cx| {
7525 if !ignore_timeout {
7526 cx.background_executor()
7527 .timer(std::time::Duration::from_millis(100))
7528 .await;
7529 }
7530 editor
7531 .update(cx, |editor, cx| {
7532 editor.inline_blame_popover.take();
7533 cx.notify();
7534 })
7535 .ok();
7536 });
7537 state.hide_task = Some(hide_task);
7538 true
7539 } else {
7540 false
7541 }
7542 }
7543
7544 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7545 if self.pending_rename.is_some() {
7546 return None;
7547 }
7548
7549 let provider = self.semantics_provider.clone()?;
7550 let buffer = self.buffer.read(cx);
7551 let newest_selection = self.selections.newest_anchor().clone();
7552 let cursor_position = newest_selection.head();
7553 let (cursor_buffer, cursor_buffer_position) =
7554 buffer.text_anchor_for_position(cursor_position, cx)?;
7555 let (tail_buffer, tail_buffer_position) =
7556 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7557 if cursor_buffer != tail_buffer {
7558 return None;
7559 }
7560
7561 let snapshot = cursor_buffer.read(cx).snapshot();
7562 let word_ranges = cx.background_spawn(async move {
7563 // this might look odd to put on the background thread, but
7564 // `surrounding_word` can be quite expensive as it calls into
7565 // tree-sitter language scopes
7566 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7567 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7568 (start_word_range, end_word_range)
7569 });
7570
7571 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7572 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7573 let (start_word_range, end_word_range) = word_ranges.await;
7574 if start_word_range != end_word_range {
7575 this.update(cx, |this, cx| {
7576 this.document_highlights_task.take();
7577 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
7578 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
7579 })
7580 .ok();
7581 return;
7582 }
7583 cx.background_executor()
7584 .timer(Duration::from_millis(debounce))
7585 .await;
7586
7587 let highlights = if let Some(highlights) = cx.update(|cx| {
7588 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7589 }) {
7590 highlights.await.log_err()
7591 } else {
7592 None
7593 };
7594
7595 if let Some(highlights) = highlights {
7596 this.update(cx, |this, cx| {
7597 if this.pending_rename.is_some() {
7598 return;
7599 }
7600
7601 let buffer = this.buffer.read(cx);
7602 if buffer
7603 .text_anchor_for_position(cursor_position, cx)
7604 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7605 {
7606 return;
7607 }
7608
7609 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7610 let mut write_ranges = Vec::new();
7611 let mut read_ranges = Vec::new();
7612 for highlight in highlights {
7613 let buffer_id = cursor_buffer.read(cx).remote_id();
7614 for (excerpt_id, _, excerpt_range) in
7615 buffer.excerpts_for_buffer(buffer_id, cx)
7616 {
7617 let start = highlight
7618 .range
7619 .start
7620 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7621 let end = highlight
7622 .range
7623 .end
7624 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7625 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7626 continue;
7627 }
7628
7629 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7630 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7631 write_ranges.push(range);
7632 } else {
7633 read_ranges.push(range);
7634 }
7635 }
7636 }
7637
7638 this.highlight_background(
7639 HighlightKey::DocumentHighlightRead,
7640 &read_ranges,
7641 |_, theme| theme.colors().editor_document_highlight_read_background,
7642 cx,
7643 );
7644 this.highlight_background(
7645 HighlightKey::DocumentHighlightWrite,
7646 &write_ranges,
7647 |_, theme| theme.colors().editor_document_highlight_write_background,
7648 cx,
7649 );
7650 cx.notify();
7651 })
7652 .log_err();
7653 }
7654 }));
7655 None
7656 }
7657
7658 fn prepare_highlight_query_from_selection(
7659 &mut self,
7660 snapshot: &DisplaySnapshot,
7661 cx: &mut Context<Editor>,
7662 ) -> Option<(String, Range<Anchor>)> {
7663 if matches!(self.mode, EditorMode::SingleLine) {
7664 return None;
7665 }
7666 if !EditorSettings::get_global(cx).selection_highlight {
7667 return None;
7668 }
7669 if self.selections.count() != 1 || self.selections.line_mode() {
7670 return None;
7671 }
7672 let selection = self.selections.newest::<Point>(&snapshot);
7673 // If the selection spans multiple rows OR it is empty
7674 if selection.start.row != selection.end.row
7675 || selection.start.column == selection.end.column
7676 {
7677 return None;
7678 }
7679 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7680 let query = snapshot
7681 .buffer_snapshot()
7682 .text_for_range(selection_anchor_range.clone())
7683 .collect::<String>();
7684 if query.trim().is_empty() {
7685 return None;
7686 }
7687 Some((query, selection_anchor_range))
7688 }
7689
7690 #[ztracing::instrument(skip_all)]
7691 fn update_selection_occurrence_highlights(
7692 &mut self,
7693 multi_buffer_snapshot: MultiBufferSnapshot,
7694 query_text: String,
7695 query_range: Range<Anchor>,
7696 multi_buffer_range_to_query: Range<Point>,
7697 use_debounce: bool,
7698 window: &mut Window,
7699 cx: &mut Context<Editor>,
7700 ) -> Task<()> {
7701 cx.spawn_in(window, async move |editor, cx| {
7702 if use_debounce {
7703 cx.background_executor()
7704 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7705 .await;
7706 }
7707 let match_task = cx.background_spawn(async move {
7708 let buffer_ranges = multi_buffer_snapshot
7709 .range_to_buffer_ranges(
7710 multi_buffer_range_to_query.start..=multi_buffer_range_to_query.end,
7711 )
7712 .into_iter()
7713 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7714 let mut match_ranges = Vec::new();
7715 let Ok(regex) = project::search::SearchQuery::text(
7716 query_text,
7717 false,
7718 false,
7719 false,
7720 Default::default(),
7721 Default::default(),
7722 false,
7723 None,
7724 ) else {
7725 return Vec::default();
7726 };
7727 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7728 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7729 match_ranges.extend(
7730 regex
7731 .search(
7732 buffer_snapshot,
7733 Some(search_range.start.0..search_range.end.0),
7734 )
7735 .await
7736 .into_iter()
7737 .filter_map(|match_range| {
7738 let match_start = buffer_snapshot
7739 .anchor_after(search_range.start + match_range.start);
7740 let match_end = buffer_snapshot
7741 .anchor_before(search_range.start + match_range.end);
7742 let match_anchor_range =
7743 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7744 (match_anchor_range != query_range).then_some(match_anchor_range)
7745 }),
7746 );
7747 }
7748 match_ranges
7749 });
7750 let match_ranges = match_task.await;
7751 editor
7752 .update_in(cx, |editor, _, cx| {
7753 if use_debounce {
7754 editor.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7755 editor.debounced_selection_highlight_complete = true;
7756 } else if editor.debounced_selection_highlight_complete {
7757 return;
7758 }
7759 if !match_ranges.is_empty() {
7760 editor.highlight_background(
7761 HighlightKey::SelectedTextHighlight,
7762 &match_ranges,
7763 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7764 cx,
7765 )
7766 }
7767 })
7768 .log_err();
7769 })
7770 }
7771
7772 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7773 struct NewlineFold;
7774 let type_id = std::any::TypeId::of::<NewlineFold>();
7775 if !self.mode.is_single_line() {
7776 return;
7777 }
7778 let snapshot = self.snapshot(window, cx);
7779 if snapshot.buffer_snapshot().max_point().row == 0 {
7780 return;
7781 }
7782 let task = cx.background_spawn(async move {
7783 let new_newlines = snapshot
7784 .buffer_chars_at(MultiBufferOffset(0))
7785 .filter_map(|(c, i)| {
7786 if c == '\n' {
7787 Some(
7788 snapshot.buffer_snapshot().anchor_after(i)
7789 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7790 )
7791 } else {
7792 None
7793 }
7794 })
7795 .collect::<Vec<_>>();
7796 let existing_newlines = snapshot
7797 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7798 .filter_map(|fold| {
7799 if fold.placeholder.type_tag == Some(type_id) {
7800 Some(fold.range.start..fold.range.end)
7801 } else {
7802 None
7803 }
7804 })
7805 .collect::<Vec<_>>();
7806
7807 (new_newlines, existing_newlines)
7808 });
7809 self.folding_newlines = cx.spawn(async move |this, cx| {
7810 let (new_newlines, existing_newlines) = task.await;
7811 if new_newlines == existing_newlines {
7812 return;
7813 }
7814 let placeholder = FoldPlaceholder {
7815 render: Arc::new(move |_, _, cx| {
7816 div()
7817 .bg(cx.theme().status().hint_background)
7818 .border_b_1()
7819 .size_full()
7820 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7821 .border_color(cx.theme().status().hint)
7822 .child("\\n")
7823 .into_any()
7824 }),
7825 constrain_width: false,
7826 merge_adjacent: false,
7827 type_tag: Some(type_id),
7828 collapsed_text: None,
7829 };
7830 let creases = new_newlines
7831 .into_iter()
7832 .map(|range| Crease::simple(range, placeholder.clone()))
7833 .collect();
7834 this.update(cx, |this, cx| {
7835 this.display_map.update(cx, |display_map, cx| {
7836 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7837 display_map.fold(creases, cx);
7838 });
7839 })
7840 .ok();
7841 });
7842 }
7843
7844 #[ztracing::instrument(skip_all)]
7845 fn refresh_outline_symbols_at_cursor(&mut self, cx: &mut Context<Editor>) {
7846 if !self.lsp_data_enabled() {
7847 return;
7848 }
7849 let cursor = self.selections.newest_anchor().head();
7850 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7851
7852 if self.uses_lsp_document_symbols(cursor, &multi_buffer_snapshot, cx) {
7853 self.outline_symbols_at_cursor =
7854 self.lsp_symbols_at_cursor(cursor, &multi_buffer_snapshot, cx);
7855 cx.emit(EditorEvent::OutlineSymbolsChanged);
7856 cx.notify();
7857 } else {
7858 let syntax = cx.theme().syntax().clone();
7859 let background_task = cx.background_spawn(async move {
7860 multi_buffer_snapshot.symbols_containing(cursor, Some(&syntax))
7861 });
7862 self.refresh_outline_symbols_at_cursor_at_cursor_task =
7863 cx.spawn(async move |this, cx| {
7864 let symbols = background_task.await;
7865 this.update(cx, |this, cx| {
7866 this.outline_symbols_at_cursor = symbols;
7867 cx.emit(EditorEvent::OutlineSymbolsChanged);
7868 cx.notify();
7869 })
7870 .ok();
7871 });
7872 }
7873 }
7874
7875 #[ztracing::instrument(skip_all)]
7876 fn refresh_selected_text_highlights(
7877 &mut self,
7878 snapshot: &DisplaySnapshot,
7879 on_buffer_edit: bool,
7880 window: &mut Window,
7881 cx: &mut Context<Editor>,
7882 ) {
7883 let Some((query_text, query_range)) =
7884 self.prepare_highlight_query_from_selection(snapshot, cx)
7885 else {
7886 self.clear_background_highlights(HighlightKey::SelectedTextHighlight, cx);
7887 self.quick_selection_highlight_task.take();
7888 self.debounced_selection_highlight_task.take();
7889 self.debounced_selection_highlight_complete = false;
7890 return;
7891 };
7892 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
7893 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7894 let query_changed = self
7895 .quick_selection_highlight_task
7896 .as_ref()
7897 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range);
7898 if query_changed {
7899 self.debounced_selection_highlight_complete = false;
7900 }
7901 if on_buffer_edit || query_changed {
7902 self.quick_selection_highlight_task = Some((
7903 query_range.clone(),
7904 self.update_selection_occurrence_highlights(
7905 snapshot.buffer.clone(),
7906 query_text.clone(),
7907 query_range.clone(),
7908 self.multi_buffer_visible_range(&display_snapshot, cx),
7909 false,
7910 window,
7911 cx,
7912 ),
7913 ));
7914 }
7915 if on_buffer_edit
7916 || self
7917 .debounced_selection_highlight_task
7918 .as_ref()
7919 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7920 {
7921 let multi_buffer_start = multi_buffer_snapshot
7922 .anchor_before(MultiBufferOffset(0))
7923 .to_point(&multi_buffer_snapshot);
7924 let multi_buffer_end = multi_buffer_snapshot
7925 .anchor_after(multi_buffer_snapshot.len())
7926 .to_point(&multi_buffer_snapshot);
7927 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7928 self.debounced_selection_highlight_task = Some((
7929 query_range.clone(),
7930 self.update_selection_occurrence_highlights(
7931 snapshot.buffer.clone(),
7932 query_text,
7933 query_range,
7934 multi_buffer_full_range,
7935 true,
7936 window,
7937 cx,
7938 ),
7939 ));
7940 }
7941 }
7942
7943 pub fn multi_buffer_visible_range(
7944 &self,
7945 display_snapshot: &DisplaySnapshot,
7946 cx: &App,
7947 ) -> Range<Point> {
7948 let visible_start = self
7949 .scroll_manager
7950 .native_anchor(display_snapshot, cx)
7951 .anchor
7952 .to_point(display_snapshot.buffer_snapshot())
7953 .to_display_point(display_snapshot);
7954
7955 let mut target_end = visible_start;
7956 *target_end.row_mut() += self.visible_line_count().unwrap_or(0.).ceil() as u32;
7957
7958 visible_start.to_point(display_snapshot)
7959 ..display_snapshot
7960 .clip_point(target_end, Bias::Right)
7961 .to_point(display_snapshot)
7962 }
7963
7964 pub fn refresh_edit_prediction(
7965 &mut self,
7966 debounce: bool,
7967 user_requested: bool,
7968 window: &mut Window,
7969 cx: &mut Context<Self>,
7970 ) -> Option<()> {
7971 if self.leader_id.is_some() {
7972 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7973 return None;
7974 }
7975
7976 let cursor = self.selections.newest_anchor().head();
7977 let (buffer, cursor_buffer_position) =
7978 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7979
7980 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
7981 return None;
7982 }
7983
7984 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7985 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7986 return None;
7987 }
7988
7989 self.update_visible_edit_prediction(window, cx);
7990
7991 if !user_requested
7992 && (!self.should_show_edit_predictions()
7993 || !self.is_focused(window)
7994 || buffer.read(cx).is_empty())
7995 {
7996 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
7997 return None;
7998 }
7999
8000 self.edit_prediction_provider()?
8001 .refresh(buffer, cursor_buffer_position, debounce, cx);
8002 Some(())
8003 }
8004
8005 fn show_edit_predictions_in_menu(&self) -> bool {
8006 match self.edit_prediction_settings {
8007 EditPredictionSettings::Disabled => false,
8008 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
8009 }
8010 }
8011
8012 pub fn edit_predictions_enabled(&self) -> bool {
8013 match self.edit_prediction_settings {
8014 EditPredictionSettings::Disabled => false,
8015 EditPredictionSettings::Enabled { .. } => true,
8016 }
8017 }
8018
8019 fn edit_prediction_requires_modifier(&self) -> bool {
8020 match self.edit_prediction_settings {
8021 EditPredictionSettings::Disabled => false,
8022 EditPredictionSettings::Enabled {
8023 preview_requires_modifier,
8024 ..
8025 } => preview_requires_modifier,
8026 }
8027 }
8028
8029 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
8030 if self.edit_prediction_provider.is_none() {
8031 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8032 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8033 return;
8034 }
8035
8036 let selection = self.selections.newest_anchor();
8037 let cursor = selection.head();
8038
8039 if let Some((buffer, cursor_buffer_position)) =
8040 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8041 {
8042 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8043 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8044 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8045 return;
8046 }
8047 self.edit_prediction_settings =
8048 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8049 }
8050 }
8051
8052 fn edit_prediction_settings_at_position(
8053 &self,
8054 buffer: &Entity<Buffer>,
8055 buffer_position: language::Anchor,
8056 cx: &App,
8057 ) -> EditPredictionSettings {
8058 if !self.mode.is_full()
8059 || !self.show_edit_predictions_override.unwrap_or(true)
8060 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
8061 {
8062 return EditPredictionSettings::Disabled;
8063 }
8064
8065 if !LanguageSettings::for_buffer(&buffer.read(cx), cx).show_edit_predictions {
8066 return EditPredictionSettings::Disabled;
8067 };
8068
8069 let by_provider = matches!(
8070 self.menu_edit_predictions_policy,
8071 MenuEditPredictionsPolicy::ByProvider
8072 );
8073
8074 let show_in_menu = by_provider
8075 && self
8076 .edit_prediction_provider
8077 .as_ref()
8078 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
8079
8080 let file = buffer.read(cx).file();
8081 let preview_requires_modifier =
8082 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
8083
8084 EditPredictionSettings::Enabled {
8085 show_in_menu,
8086 preview_requires_modifier,
8087 }
8088 }
8089
8090 fn should_show_edit_predictions(&self) -> bool {
8091 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
8092 }
8093
8094 pub fn edit_prediction_preview_is_active(&self) -> bool {
8095 matches!(
8096 self.edit_prediction_preview,
8097 EditPredictionPreview::Active { .. }
8098 )
8099 }
8100
8101 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
8102 let cursor = self.selections.newest_anchor().head();
8103 if let Some((buffer, cursor_position)) =
8104 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
8105 {
8106 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
8107 } else {
8108 false
8109 }
8110 }
8111
8112 pub fn supports_minimap(&self, cx: &App) -> bool {
8113 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
8114 }
8115
8116 fn edit_predictions_enabled_in_buffer(
8117 &self,
8118 buffer: &Entity<Buffer>,
8119 buffer_position: language::Anchor,
8120 cx: &App,
8121 ) -> bool {
8122 maybe!({
8123 if self.read_only(cx) || self.leader_id.is_some() {
8124 return Some(false);
8125 }
8126 let provider = self.edit_prediction_provider()?;
8127 if !provider.is_enabled(buffer, buffer_position, cx) {
8128 return Some(false);
8129 }
8130 let buffer = buffer.read(cx);
8131 let Some(file) = buffer.file() else {
8132 return Some(true);
8133 };
8134 let settings = all_language_settings(Some(file), cx);
8135 Some(settings.edit_predictions_enabled_for_file(file, cx))
8136 })
8137 .unwrap_or(false)
8138 }
8139
8140 pub fn show_edit_prediction(
8141 &mut self,
8142 _: &ShowEditPrediction,
8143 window: &mut Window,
8144 cx: &mut Context<Self>,
8145 ) {
8146 if !self.has_active_edit_prediction() {
8147 self.refresh_edit_prediction(false, true, window, cx);
8148 return;
8149 }
8150
8151 self.update_visible_edit_prediction(window, cx);
8152 }
8153
8154 pub fn display_cursor_names(
8155 &mut self,
8156 _: &DisplayCursorNames,
8157 window: &mut Window,
8158 cx: &mut Context<Self>,
8159 ) {
8160 self.show_cursor_names(window, cx);
8161 }
8162
8163 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8164 self.show_cursor_names = true;
8165 cx.notify();
8166 cx.spawn_in(window, async move |this, cx| {
8167 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
8168 this.update(cx, |this, cx| {
8169 this.show_cursor_names = false;
8170 cx.notify()
8171 })
8172 .ok()
8173 })
8174 .detach();
8175 }
8176
8177 pub fn accept_partial_edit_prediction(
8178 &mut self,
8179 granularity: EditPredictionGranularity,
8180 window: &mut Window,
8181 cx: &mut Context<Self>,
8182 ) {
8183 if self.show_edit_predictions_in_menu() {
8184 self.hide_context_menu(window, cx);
8185 }
8186
8187 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
8188 return;
8189 };
8190
8191 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
8192 return;
8193 }
8194
8195 match &active_edit_prediction.completion {
8196 EditPrediction::MoveWithin { target, .. } => {
8197 let target = *target;
8198
8199 if matches!(granularity, EditPredictionGranularity::Full) {
8200 if let Some(position_map) = &self.last_position_map {
8201 let target_row = target.to_display_point(&position_map.snapshot).row();
8202 let is_visible = position_map.visible_row_range.contains(&target_row);
8203
8204 if is_visible || !self.edit_prediction_requires_modifier() {
8205 self.unfold_ranges(&[target..target], true, false, cx);
8206 self.change_selections(
8207 SelectionEffects::scroll(Autoscroll::newest()),
8208 window,
8209 cx,
8210 |selections| {
8211 selections.select_anchor_ranges([target..target]);
8212 },
8213 );
8214 self.clear_row_highlights::<EditPredictionPreview>();
8215 self.edit_prediction_preview
8216 .set_previous_scroll_position(None);
8217 } else {
8218 // Highlight and request scroll
8219 self.edit_prediction_preview
8220 .set_previous_scroll_position(Some(
8221 position_map.snapshot.scroll_anchor,
8222 ));
8223 self.highlight_rows::<EditPredictionPreview>(
8224 target..target,
8225 cx.theme().colors().editor_highlighted_line_background,
8226 RowHighlightOptions {
8227 autoscroll: true,
8228 ..Default::default()
8229 },
8230 cx,
8231 );
8232 self.request_autoscroll(Autoscroll::fit(), cx);
8233 }
8234 }
8235 } else {
8236 self.change_selections(
8237 SelectionEffects::scroll(Autoscroll::newest()),
8238 window,
8239 cx,
8240 |selections| {
8241 selections.select_anchor_ranges([target..target]);
8242 },
8243 );
8244 }
8245 }
8246 EditPrediction::MoveOutside { snapshot, target } => {
8247 if let Some(workspace) = self.workspace() {
8248 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
8249 .detach_and_log_err(cx);
8250 }
8251 }
8252 EditPrediction::Edit {
8253 edits,
8254 cursor_position,
8255 ..
8256 } => {
8257 self.report_edit_prediction_event(
8258 active_edit_prediction.completion_id.clone(),
8259 true,
8260 cx,
8261 );
8262
8263 match granularity {
8264 EditPredictionGranularity::Full => {
8265 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
8266
8267 // Compute fallback cursor position BEFORE applying the edit,
8268 // so the anchor tracks through the edit correctly
8269 let fallback_cursor_target = {
8270 let snapshot = self.buffer.read(cx).snapshot(cx);
8271 edits.last().unwrap().0.end.bias_right(&snapshot)
8272 };
8273
8274 self.buffer.update(cx, |buffer, cx| {
8275 buffer.edit(edits.iter().cloned(), None, cx)
8276 });
8277
8278 if let Some(provider) = self.edit_prediction_provider() {
8279 provider.accept(cx);
8280 }
8281
8282 // Resolve cursor position after the edit is applied
8283 let cursor_target = if let Some((anchor, offset)) = cursor_position {
8284 // The anchor tracks through the edit, then we add the offset
8285 let snapshot = self.buffer.read(cx).snapshot(cx);
8286 let base_offset = anchor.to_offset(&snapshot).0;
8287 let target_offset =
8288 MultiBufferOffset((base_offset + offset).min(snapshot.len().0));
8289 snapshot.anchor_after(target_offset)
8290 } else {
8291 fallback_cursor_target
8292 };
8293
8294 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
8295 s.select_anchor_ranges([cursor_target..cursor_target]);
8296 });
8297
8298 let selections = self.selections.disjoint_anchors_arc();
8299 if let Some(transaction_id_now) =
8300 self.buffer.read(cx).last_transaction_id(cx)
8301 {
8302 if transaction_id_prev != Some(transaction_id_now) {
8303 self.selection_history
8304 .insert_transaction(transaction_id_now, selections);
8305 }
8306 }
8307
8308 self.update_visible_edit_prediction(window, cx);
8309 if self.active_edit_prediction.is_none() {
8310 self.refresh_edit_prediction(true, true, window, cx);
8311 }
8312 cx.notify();
8313 }
8314 _ => {
8315 let snapshot = self.buffer.read(cx).snapshot(cx);
8316 let cursor_offset = self
8317 .selections
8318 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8319 .head();
8320
8321 let insertion = edits.iter().find_map(|(range, text)| {
8322 let range = range.to_offset(&snapshot);
8323 if range.is_empty() && range.start == cursor_offset {
8324 Some(text)
8325 } else {
8326 None
8327 }
8328 });
8329
8330 if let Some(text) = insertion {
8331 let text_to_insert = match granularity {
8332 EditPredictionGranularity::Word => {
8333 let mut partial = text
8334 .chars()
8335 .by_ref()
8336 .take_while(|c| c.is_alphabetic())
8337 .collect::<String>();
8338 if partial.is_empty() {
8339 partial = text
8340 .chars()
8341 .by_ref()
8342 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
8343 .collect::<String>();
8344 }
8345 partial
8346 }
8347 EditPredictionGranularity::Line => {
8348 if let Some(line) = text.split_inclusive('\n').next() {
8349 line.to_string()
8350 } else {
8351 text.to_string()
8352 }
8353 }
8354 EditPredictionGranularity::Full => unreachable!(),
8355 };
8356
8357 cx.emit(EditorEvent::InputHandled {
8358 utf16_range_to_replace: None,
8359 text: text_to_insert.clone().into(),
8360 });
8361
8362 self.replace_selections(&text_to_insert, None, window, cx, false);
8363 self.refresh_edit_prediction(true, true, window, cx);
8364 cx.notify();
8365 } else {
8366 self.accept_partial_edit_prediction(
8367 EditPredictionGranularity::Full,
8368 window,
8369 cx,
8370 );
8371 }
8372 }
8373 }
8374 }
8375 }
8376 }
8377
8378 pub fn accept_next_word_edit_prediction(
8379 &mut self,
8380 _: &AcceptNextWordEditPrediction,
8381 window: &mut Window,
8382 cx: &mut Context<Self>,
8383 ) {
8384 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
8385 }
8386
8387 pub fn accept_next_line_edit_prediction(
8388 &mut self,
8389 _: &AcceptNextLineEditPrediction,
8390 window: &mut Window,
8391 cx: &mut Context<Self>,
8392 ) {
8393 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
8394 }
8395
8396 pub fn accept_edit_prediction(
8397 &mut self,
8398 _: &AcceptEditPrediction,
8399 window: &mut Window,
8400 cx: &mut Context<Self>,
8401 ) {
8402 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
8403 }
8404
8405 fn discard_edit_prediction(
8406 &mut self,
8407 reason: EditPredictionDiscardReason,
8408 cx: &mut Context<Self>,
8409 ) -> bool {
8410 if reason == EditPredictionDiscardReason::Rejected {
8411 let completion_id = self
8412 .active_edit_prediction
8413 .as_ref()
8414 .and_then(|active_completion| active_completion.completion_id.clone());
8415
8416 self.report_edit_prediction_event(completion_id, false, cx);
8417 }
8418
8419 if let Some(provider) = self.edit_prediction_provider() {
8420 provider.discard(reason, cx);
8421 }
8422
8423 self.take_active_edit_prediction(reason == EditPredictionDiscardReason::Ignored, cx)
8424 }
8425
8426 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
8427 let Some(provider) = self.edit_prediction_provider() else {
8428 return;
8429 };
8430
8431 let Some((_, buffer, _)) = self
8432 .buffer
8433 .read(cx)
8434 .excerpt_containing(self.selections.newest_anchor().head(), cx)
8435 else {
8436 return;
8437 };
8438
8439 let extension = buffer
8440 .read(cx)
8441 .file()
8442 .and_then(|file| Some(file.path().extension()?.to_string()));
8443
8444 let event_type = match accepted {
8445 true => "Edit Prediction Accepted",
8446 false => "Edit Prediction Discarded",
8447 };
8448 telemetry::event!(
8449 event_type,
8450 provider = provider.name(),
8451 prediction_id = id,
8452 suggestion_accepted = accepted,
8453 file_extension = extension,
8454 );
8455 }
8456
8457 fn open_editor_at_anchor(
8458 snapshot: &language::BufferSnapshot,
8459 target: language::Anchor,
8460 workspace: &Entity<Workspace>,
8461 window: &mut Window,
8462 cx: &mut App,
8463 ) -> Task<Result<()>> {
8464 workspace.update(cx, |workspace, cx| {
8465 let path = snapshot.file().map(|file| file.full_path(cx));
8466 let Some(path) =
8467 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
8468 else {
8469 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
8470 };
8471 let target = text::ToPoint::to_point(&target, snapshot);
8472 let item = workspace.open_path(path, None, true, window, cx);
8473 window.spawn(cx, async move |cx| {
8474 let Some(editor) = item.await?.downcast::<Editor>() else {
8475 return Ok(());
8476 };
8477 editor
8478 .update_in(cx, |editor, window, cx| {
8479 editor.go_to_singleton_buffer_point(target, window, cx);
8480 })
8481 .ok();
8482 anyhow::Ok(())
8483 })
8484 })
8485 }
8486
8487 pub fn has_active_edit_prediction(&self) -> bool {
8488 self.active_edit_prediction.is_some()
8489 }
8490
8491 fn take_active_edit_prediction(
8492 &mut self,
8493 preserve_stale_in_menu: bool,
8494 cx: &mut Context<Self>,
8495 ) -> bool {
8496 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
8497 if !preserve_stale_in_menu {
8498 self.stale_edit_prediction_in_menu = None;
8499 }
8500 return false;
8501 };
8502
8503 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
8504 self.clear_highlights(HighlightKey::EditPredictionHighlight, cx);
8505 self.stale_edit_prediction_in_menu =
8506 preserve_stale_in_menu.then_some(active_edit_prediction);
8507 true
8508 }
8509
8510 /// Returns true when we're displaying the edit prediction popover below the cursor
8511 /// like we are not previewing and the LSP autocomplete menu is visible
8512 /// or we are in `when_holding_modifier` mode.
8513 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
8514 if self.edit_prediction_preview_is_active()
8515 || !self.show_edit_predictions_in_menu()
8516 || !self.edit_predictions_enabled()
8517 {
8518 return false;
8519 }
8520
8521 if self.has_visible_completions_menu() {
8522 return true;
8523 }
8524
8525 has_completion && self.edit_prediction_requires_modifier()
8526 }
8527
8528 fn handle_modifiers_changed(
8529 &mut self,
8530 modifiers: Modifiers,
8531 position_map: &PositionMap,
8532 window: &mut Window,
8533 cx: &mut Context<Self>,
8534 ) {
8535 self.update_edit_prediction_settings(cx);
8536
8537 // Ensure that the edit prediction preview is updated, even when not
8538 // enabled, if there's an active edit prediction preview.
8539 if self.show_edit_predictions_in_menu()
8540 || self.edit_prediction_requires_modifier()
8541 || matches!(
8542 self.edit_prediction_preview,
8543 EditPredictionPreview::Active { .. }
8544 )
8545 {
8546 self.update_edit_prediction_preview(&modifiers, window, cx);
8547 }
8548
8549 self.update_selection_mode(&modifiers, position_map, window, cx);
8550
8551 let mouse_position = window.mouse_position();
8552 if !position_map.text_hitbox.is_hovered(window) {
8553 return;
8554 }
8555
8556 self.update_hovered_link(
8557 position_map.point_for_position(mouse_position),
8558 Some(mouse_position),
8559 &position_map.snapshot,
8560 modifiers,
8561 window,
8562 cx,
8563 )
8564 }
8565
8566 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8567 match EditorSettings::get_global(cx).multi_cursor_modifier {
8568 MultiCursorModifier::Alt => modifiers.secondary(),
8569 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8570 }
8571 }
8572
8573 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8574 match EditorSettings::get_global(cx).multi_cursor_modifier {
8575 MultiCursorModifier::Alt => modifiers.alt,
8576 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8577 }
8578 }
8579
8580 fn columnar_selection_mode(
8581 modifiers: &Modifiers,
8582 cx: &mut Context<Self>,
8583 ) -> Option<ColumnarMode> {
8584 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8585 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8586 Some(ColumnarMode::FromMouse)
8587 } else if Self::is_alt_pressed(modifiers, cx) {
8588 Some(ColumnarMode::FromSelection)
8589 } else {
8590 None
8591 }
8592 } else {
8593 None
8594 }
8595 }
8596
8597 fn update_selection_mode(
8598 &mut self,
8599 modifiers: &Modifiers,
8600 position_map: &PositionMap,
8601 window: &mut Window,
8602 cx: &mut Context<Self>,
8603 ) {
8604 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8605 return;
8606 };
8607 if self.selections.pending_anchor().is_none() {
8608 return;
8609 }
8610
8611 let mouse_position = window.mouse_position();
8612 let point_for_position = position_map.point_for_position(mouse_position);
8613 let position = point_for_position.previous_valid;
8614
8615 self.select(
8616 SelectPhase::BeginColumnar {
8617 position,
8618 reset: false,
8619 mode,
8620 goal_column: point_for_position.exact_unclipped.column(),
8621 },
8622 window,
8623 cx,
8624 );
8625 }
8626
8627 fn update_edit_prediction_preview(
8628 &mut self,
8629 modifiers: &Modifiers,
8630 window: &mut Window,
8631 cx: &mut Context<Self>,
8632 ) {
8633 let modifiers_held = self.edit_prediction_preview_modifiers_held(modifiers, window, cx);
8634
8635 if modifiers_held {
8636 if matches!(
8637 self.edit_prediction_preview,
8638 EditPredictionPreview::Inactive { .. }
8639 ) {
8640 self.edit_prediction_preview = EditPredictionPreview::Active {
8641 previous_scroll_position: None,
8642 since: Instant::now(),
8643 };
8644
8645 self.update_visible_edit_prediction(window, cx);
8646 cx.notify();
8647 }
8648 } else if let EditPredictionPreview::Active {
8649 previous_scroll_position,
8650 since,
8651 } = self.edit_prediction_preview
8652 {
8653 if let (Some(previous_scroll_position), Some(position_map)) =
8654 (previous_scroll_position, self.last_position_map.as_ref())
8655 {
8656 self.set_scroll_position(
8657 previous_scroll_position
8658 .scroll_position(&position_map.snapshot.display_snapshot),
8659 window,
8660 cx,
8661 );
8662 }
8663
8664 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8665 released_too_fast: since.elapsed() < Duration::from_millis(200),
8666 };
8667 self.clear_row_highlights::<EditPredictionPreview>();
8668 self.update_visible_edit_prediction(window, cx);
8669 cx.notify();
8670 }
8671 }
8672
8673 fn update_visible_edit_prediction(
8674 &mut self,
8675 _window: &mut Window,
8676 cx: &mut Context<Self>,
8677 ) -> Option<()> {
8678 if self.ime_transaction.is_some() {
8679 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8680 return None;
8681 }
8682
8683 let selection = self.selections.newest_anchor();
8684 let cursor = selection.head();
8685 let multibuffer = self.buffer.read(cx).snapshot(cx);
8686
8687 // Check project-level disable_ai setting for the current buffer
8688 if let Some((buffer, _)) = self.buffer.read(cx).text_anchor_for_position(cursor, cx) {
8689 if DisableAiSettings::is_ai_disabled_for_buffer(Some(&buffer), cx) {
8690 return None;
8691 }
8692 }
8693 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8694 let excerpt_id = cursor.excerpt_id;
8695
8696 let show_in_menu = self.show_edit_predictions_in_menu();
8697 let completions_menu_has_precedence = !show_in_menu
8698 && (self.context_menu.borrow().is_some()
8699 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8700
8701 if completions_menu_has_precedence
8702 || !offset_selection.is_empty()
8703 || self
8704 .active_edit_prediction
8705 .as_ref()
8706 .is_some_and(|completion| {
8707 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8708 return false;
8709 };
8710 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8711 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8712 !invalidation_range.contains(&offset_selection.head())
8713 })
8714 {
8715 self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx);
8716 return None;
8717 }
8718
8719 self.take_active_edit_prediction(true, cx);
8720 let Some(provider) = self.edit_prediction_provider() else {
8721 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8722 return None;
8723 };
8724
8725 let (buffer, cursor_buffer_position) =
8726 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8727
8728 self.edit_prediction_settings =
8729 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8730
8731 self.in_leading_whitespace = multibuffer.is_line_whitespace_upto(cursor);
8732
8733 if self.in_leading_whitespace {
8734 let cursor_point = cursor.to_point(&multibuffer);
8735 let mut suggested_indent = None;
8736 multibuffer.suggested_indents_callback(
8737 cursor_point.row..cursor_point.row + 1,
8738 &mut |_, indent| {
8739 suggested_indent = Some(indent);
8740 ControlFlow::Break(())
8741 },
8742 cx,
8743 );
8744
8745 if let Some(indent) = suggested_indent
8746 && indent.len == cursor_point.column
8747 {
8748 self.in_leading_whitespace = false;
8749 }
8750 }
8751
8752 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8753
8754 let (completion_id, edits, predicted_cursor_position, edit_preview) = match edit_prediction
8755 {
8756 edit_prediction_types::EditPrediction::Local {
8757 id,
8758 edits,
8759 cursor_position,
8760 edit_preview,
8761 } => (id, edits, cursor_position, edit_preview),
8762 edit_prediction_types::EditPrediction::Jump {
8763 id,
8764 snapshot,
8765 target,
8766 } => {
8767 if let Some(provider) = &self.edit_prediction_provider {
8768 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8769 }
8770 self.stale_edit_prediction_in_menu = None;
8771 self.active_edit_prediction = Some(EditPredictionState {
8772 inlay_ids: vec![],
8773 completion: EditPrediction::MoveOutside { snapshot, target },
8774 completion_id: id,
8775 invalidation_range: None,
8776 });
8777 cx.notify();
8778 return Some(());
8779 }
8780 };
8781
8782 let edits = edits
8783 .into_iter()
8784 .flat_map(|(range, new_text)| {
8785 Some((
8786 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8787 new_text,
8788 ))
8789 })
8790 .collect::<Vec<_>>();
8791 if edits.is_empty() {
8792 return None;
8793 }
8794
8795 let cursor_position = predicted_cursor_position.and_then(|predicted| {
8796 let anchor = multibuffer.anchor_in_excerpt(excerpt_id, predicted.anchor)?;
8797 Some((anchor, predicted.offset))
8798 });
8799
8800 let first_edit_start = edits.first().unwrap().0.start;
8801 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8802 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8803
8804 let last_edit_end = edits.last().unwrap().0.end;
8805 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8806 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8807
8808 let cursor_row = cursor.to_point(&multibuffer).row;
8809
8810 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8811
8812 let mut inlay_ids = Vec::new();
8813 let invalidation_row_range;
8814 let move_invalidation_row_range = if cursor_row < edit_start_row {
8815 Some(cursor_row..edit_end_row)
8816 } else if cursor_row > edit_end_row {
8817 Some(edit_start_row..cursor_row)
8818 } else {
8819 None
8820 };
8821 let supports_jump = self
8822 .edit_prediction_provider
8823 .as_ref()
8824 .map(|provider| provider.provider.supports_jump_to_edit())
8825 .unwrap_or(true);
8826
8827 let is_move = supports_jump
8828 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8829 let completion = if is_move {
8830 if let Some(provider) = &self.edit_prediction_provider {
8831 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8832 }
8833 invalidation_row_range =
8834 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8835 let target = first_edit_start;
8836 EditPrediction::MoveWithin { target, snapshot }
8837 } else {
8838 let show_completions_in_menu = self.has_visible_completions_menu();
8839 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8840 && !self.edit_predictions_hidden_for_vim_mode;
8841
8842 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8843 if provider.show_tab_accept_marker() {
8844 EditDisplayMode::TabAccept
8845 } else {
8846 EditDisplayMode::Inline
8847 }
8848 } else {
8849 EditDisplayMode::DiffPopover
8850 };
8851
8852 let report_shown = match display_mode {
8853 EditDisplayMode::DiffPopover | EditDisplayMode::Inline => {
8854 show_completions_in_buffer || show_completions_in_menu
8855 }
8856 EditDisplayMode::TabAccept => {
8857 show_completions_in_menu || self.edit_prediction_preview_is_active()
8858 }
8859 };
8860
8861 if report_shown && let Some(provider) = &self.edit_prediction_provider {
8862 let suggestion_display_type = match display_mode {
8863 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8864 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8865 SuggestionDisplayType::GhostText
8866 }
8867 };
8868 provider.provider.did_show(suggestion_display_type, cx);
8869 }
8870
8871 if show_completions_in_buffer {
8872 if edits
8873 .iter()
8874 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8875 {
8876 let mut inlays = Vec::new();
8877 for (range, new_text) in &edits {
8878 let inlay = Inlay::edit_prediction(
8879 post_inc(&mut self.next_inlay_id),
8880 range.start,
8881 new_text.as_ref(),
8882 );
8883 inlay_ids.push(inlay.id);
8884 inlays.push(inlay);
8885 }
8886
8887 self.splice_inlays(&[], inlays, cx);
8888 } else {
8889 let background_color = cx.theme().status().deleted_background;
8890 self.highlight_text(
8891 HighlightKey::EditPredictionHighlight,
8892 edits.iter().map(|(range, _)| range.clone()).collect(),
8893 HighlightStyle {
8894 background_color: Some(background_color),
8895 ..Default::default()
8896 },
8897 cx,
8898 );
8899 }
8900 }
8901
8902 invalidation_row_range = edit_start_row..edit_end_row;
8903
8904 EditPrediction::Edit {
8905 edits,
8906 cursor_position,
8907 edit_preview,
8908 display_mode,
8909 snapshot,
8910 }
8911 };
8912
8913 let invalidation_range = multibuffer
8914 .anchor_before(Point::new(invalidation_row_range.start, 0))
8915 ..multibuffer.anchor_after(Point::new(
8916 invalidation_row_range.end,
8917 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8918 ));
8919
8920 self.stale_edit_prediction_in_menu = None;
8921 self.active_edit_prediction = Some(EditPredictionState {
8922 inlay_ids,
8923 completion,
8924 completion_id,
8925 invalidation_range: Some(invalidation_range),
8926 });
8927
8928 cx.notify();
8929
8930 Some(())
8931 }
8932
8933 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8934 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8935 }
8936
8937 /// Get all display points of breakpoints that will be rendered within editor
8938 ///
8939 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8940 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8941 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8942 fn active_breakpoints(
8943 &self,
8944 range: Range<DisplayRow>,
8945 window: &mut Window,
8946 cx: &mut Context<Self>,
8947 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8948 let mut breakpoint_display_points = HashMap::default();
8949
8950 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8951 return breakpoint_display_points;
8952 };
8953
8954 let snapshot = self.snapshot(window, cx);
8955
8956 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8957 let Some(project) = self.project() else {
8958 return breakpoint_display_points;
8959 };
8960
8961 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8962 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8963
8964 for (buffer_snapshot, range, excerpt_id) in
8965 multi_buffer_snapshot.range_to_buffer_ranges(range.start..=range.end)
8966 {
8967 let Some(buffer) = project
8968 .read(cx)
8969 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8970 else {
8971 continue;
8972 };
8973 let breakpoints = breakpoint_store.read(cx).breakpoints(
8974 &buffer,
8975 Some(
8976 buffer_snapshot.anchor_before(range.start)
8977 ..buffer_snapshot.anchor_after(range.end),
8978 ),
8979 buffer_snapshot,
8980 cx,
8981 );
8982 for (breakpoint, state) in breakpoints {
8983 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8984 let position = multi_buffer_anchor
8985 .to_point(&multi_buffer_snapshot)
8986 .to_display_point(&snapshot);
8987
8988 breakpoint_display_points.insert(
8989 position.row(),
8990 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8991 );
8992 }
8993 }
8994
8995 breakpoint_display_points
8996 }
8997
8998 fn breakpoint_context_menu(
8999 &self,
9000 anchor: Anchor,
9001 window: &mut Window,
9002 cx: &mut Context<Self>,
9003 ) -> Entity<ui::ContextMenu> {
9004 let weak_editor = cx.weak_entity();
9005 let focus_handle = self.focus_handle(cx);
9006
9007 let row = self
9008 .buffer
9009 .read(cx)
9010 .snapshot(cx)
9011 .summary_for_anchor::<Point>(&anchor)
9012 .row;
9013
9014 let breakpoint = self
9015 .breakpoint_at_row(row, window, cx)
9016 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
9017
9018 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
9019 "Edit Log Breakpoint"
9020 } else {
9021 "Set Log Breakpoint"
9022 };
9023
9024 let condition_breakpoint_msg = if breakpoint
9025 .as_ref()
9026 .is_some_and(|bp| bp.1.condition.is_some())
9027 {
9028 "Edit Condition Breakpoint"
9029 } else {
9030 "Set Condition Breakpoint"
9031 };
9032
9033 let hit_condition_breakpoint_msg = if breakpoint
9034 .as_ref()
9035 .is_some_and(|bp| bp.1.hit_condition.is_some())
9036 {
9037 "Edit Hit Condition Breakpoint"
9038 } else {
9039 "Set Hit Condition Breakpoint"
9040 };
9041
9042 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
9043 "Unset Breakpoint"
9044 } else {
9045 "Set Breakpoint"
9046 };
9047
9048 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
9049
9050 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
9051 BreakpointState::Enabled => Some("Disable"),
9052 BreakpointState::Disabled => Some("Enable"),
9053 });
9054
9055 let (anchor, breakpoint) =
9056 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
9057
9058 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
9059 menu.on_blur_subscription(Subscription::new(|| {}))
9060 .context(focus_handle)
9061 .when(run_to_cursor, |this| {
9062 let weak_editor = weak_editor.clone();
9063 this.entry("Run to Cursor", None, move |window, cx| {
9064 weak_editor
9065 .update(cx, |editor, cx| {
9066 editor.change_selections(
9067 SelectionEffects::no_scroll(),
9068 window,
9069 cx,
9070 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
9071 );
9072 })
9073 .ok();
9074
9075 window.dispatch_action(Box::new(RunToCursor), cx);
9076 })
9077 .separator()
9078 })
9079 .when_some(toggle_state_msg, |this, msg| {
9080 this.entry(msg, None, {
9081 let weak_editor = weak_editor.clone();
9082 let breakpoint = breakpoint.clone();
9083 move |_window, cx| {
9084 weak_editor
9085 .update(cx, |this, cx| {
9086 this.edit_breakpoint_at_anchor(
9087 anchor,
9088 breakpoint.as_ref().clone(),
9089 BreakpointEditAction::InvertState,
9090 cx,
9091 );
9092 })
9093 .log_err();
9094 }
9095 })
9096 })
9097 .entry(set_breakpoint_msg, None, {
9098 let weak_editor = weak_editor.clone();
9099 let breakpoint = breakpoint.clone();
9100 move |_window, cx| {
9101 weak_editor
9102 .update(cx, |this, cx| {
9103 this.edit_breakpoint_at_anchor(
9104 anchor,
9105 breakpoint.as_ref().clone(),
9106 BreakpointEditAction::Toggle,
9107 cx,
9108 );
9109 })
9110 .log_err();
9111 }
9112 })
9113 .entry(log_breakpoint_msg, None, {
9114 let breakpoint = breakpoint.clone();
9115 let weak_editor = weak_editor.clone();
9116 move |window, cx| {
9117 weak_editor
9118 .update(cx, |this, cx| {
9119 this.add_edit_breakpoint_block(
9120 anchor,
9121 breakpoint.as_ref(),
9122 BreakpointPromptEditAction::Log,
9123 window,
9124 cx,
9125 );
9126 })
9127 .log_err();
9128 }
9129 })
9130 .entry(condition_breakpoint_msg, None, {
9131 let breakpoint = breakpoint.clone();
9132 let weak_editor = weak_editor.clone();
9133 move |window, cx| {
9134 weak_editor
9135 .update(cx, |this, cx| {
9136 this.add_edit_breakpoint_block(
9137 anchor,
9138 breakpoint.as_ref(),
9139 BreakpointPromptEditAction::Condition,
9140 window,
9141 cx,
9142 );
9143 })
9144 .log_err();
9145 }
9146 })
9147 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
9148 weak_editor
9149 .update(cx, |this, cx| {
9150 this.add_edit_breakpoint_block(
9151 anchor,
9152 breakpoint.as_ref(),
9153 BreakpointPromptEditAction::HitCondition,
9154 window,
9155 cx,
9156 );
9157 })
9158 .log_err();
9159 })
9160 })
9161 }
9162
9163 fn render_breakpoint(
9164 &self,
9165 position: Anchor,
9166 row: DisplayRow,
9167 breakpoint: &Breakpoint,
9168 state: Option<BreakpointSessionState>,
9169 cx: &mut Context<Self>,
9170 ) -> IconButton {
9171 let is_rejected = state.is_some_and(|s| !s.verified);
9172 // Is it a breakpoint that shows up when hovering over gutter?
9173 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
9174 (false, false),
9175 |PhantomBreakpointIndicator {
9176 is_active,
9177 display_row,
9178 collides_with_existing_breakpoint,
9179 }| {
9180 (
9181 is_active && display_row == row,
9182 collides_with_existing_breakpoint,
9183 )
9184 },
9185 );
9186
9187 let (color, icon) = {
9188 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
9189 (false, false) => ui::IconName::DebugBreakpoint,
9190 (true, false) => ui::IconName::DebugLogBreakpoint,
9191 (false, true) => ui::IconName::DebugDisabledBreakpoint,
9192 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
9193 };
9194
9195 let theme_colors = cx.theme().colors();
9196
9197 let color = if is_phantom {
9198 if collides_with_existing {
9199 Color::Custom(
9200 theme_colors
9201 .debugger_accent
9202 .blend(theme_colors.text.opacity(0.6)),
9203 )
9204 } else {
9205 Color::Hint
9206 }
9207 } else if is_rejected {
9208 Color::Disabled
9209 } else {
9210 Color::Debugger
9211 };
9212
9213 (color, icon)
9214 };
9215
9216 let breakpoint = Arc::from(breakpoint.clone());
9217
9218 let alt_as_text = gpui::Keystroke {
9219 modifiers: Modifiers::secondary_key(),
9220 ..Default::default()
9221 };
9222 let primary_action_text = if breakpoint.is_disabled() {
9223 "Enable breakpoint"
9224 } else if is_phantom && !collides_with_existing {
9225 "Set breakpoint"
9226 } else {
9227 "Unset breakpoint"
9228 };
9229 let focus_handle = self.focus_handle.clone();
9230
9231 let meta = if is_rejected {
9232 SharedString::from("No executable code is associated with this line.")
9233 } else if collides_with_existing && !breakpoint.is_disabled() {
9234 SharedString::from(format!(
9235 "{alt_as_text}-click to disable,\nright-click for more options."
9236 ))
9237 } else {
9238 SharedString::from("Right-click for more options.")
9239 };
9240 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
9241 .icon_size(IconSize::XSmall)
9242 .size(ui::ButtonSize::None)
9243 .when(is_rejected, |this| {
9244 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
9245 })
9246 .icon_color(color)
9247 .style(ButtonStyle::Transparent)
9248 .on_click(cx.listener({
9249 move |editor, event: &ClickEvent, window, cx| {
9250 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
9251 BreakpointEditAction::InvertState
9252 } else {
9253 BreakpointEditAction::Toggle
9254 };
9255
9256 window.focus(&editor.focus_handle(cx), cx);
9257 editor.update_breakpoint_collision_on_toggle(row, &edit_action);
9258 editor.edit_breakpoint_at_anchor(
9259 position,
9260 breakpoint.as_ref().clone(),
9261 edit_action,
9262 cx,
9263 );
9264 }
9265 }))
9266 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
9267 editor.set_breakpoint_context_menu(
9268 row,
9269 Some(position),
9270 event.position(),
9271 window,
9272 cx,
9273 );
9274 }))
9275 .tooltip(move |_window, cx| {
9276 Tooltip::with_meta_in(
9277 primary_action_text,
9278 Some(&ToggleBreakpoint),
9279 meta.clone(),
9280 &focus_handle,
9281 cx,
9282 )
9283 })
9284 }
9285
9286 fn build_tasks_context(
9287 project: &Entity<Project>,
9288 buffer: &Entity<Buffer>,
9289 buffer_row: u32,
9290 tasks: &Arc<RunnableTasks>,
9291 cx: &mut Context<Self>,
9292 ) -> Task<Option<task::TaskContext>> {
9293 let position = Point::new(buffer_row, tasks.column);
9294 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
9295 let location = Location {
9296 buffer: buffer.clone(),
9297 range: range_start..range_start,
9298 };
9299 // Fill in the environmental variables from the tree-sitter captures
9300 let mut captured_task_variables = TaskVariables::default();
9301 for (capture_name, value) in tasks.extra_variables.clone() {
9302 captured_task_variables.insert(
9303 task::VariableName::Custom(capture_name.into()),
9304 value.clone(),
9305 );
9306 }
9307 project.update(cx, |project, cx| {
9308 project.task_store().update(cx, |task_store, cx| {
9309 task_store.task_context_for_location(captured_task_variables, location, cx)
9310 })
9311 })
9312 }
9313
9314 pub fn context_menu_visible(&self) -> bool {
9315 !self.edit_prediction_preview_is_active()
9316 && self
9317 .context_menu
9318 .borrow()
9319 .as_ref()
9320 .is_some_and(|menu| menu.visible())
9321 }
9322
9323 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
9324 self.context_menu
9325 .borrow()
9326 .as_ref()
9327 .map(|menu| menu.origin())
9328 }
9329
9330 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
9331 self.context_menu_options = Some(options);
9332 }
9333
9334 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
9335 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
9336
9337 fn render_edit_prediction_popover(
9338 &mut self,
9339 text_bounds: &Bounds<Pixels>,
9340 content_origin: gpui::Point<Pixels>,
9341 right_margin: Pixels,
9342 editor_snapshot: &EditorSnapshot,
9343 visible_row_range: Range<DisplayRow>,
9344 scroll_top: ScrollOffset,
9345 scroll_bottom: ScrollOffset,
9346 line_layouts: &[LineWithInvisibles],
9347 line_height: Pixels,
9348 scroll_position: gpui::Point<ScrollOffset>,
9349 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9350 newest_selection_head: Option<DisplayPoint>,
9351 editor_width: Pixels,
9352 style: &EditorStyle,
9353 window: &mut Window,
9354 cx: &mut App,
9355 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9356 if self.mode().is_minimap() {
9357 return None;
9358 }
9359 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
9360
9361 if self.edit_prediction_visible_in_cursor_popover(true) {
9362 return None;
9363 }
9364
9365 match &active_edit_prediction.completion {
9366 EditPrediction::MoveWithin { target, .. } => {
9367 let target_display_point = target.to_display_point(editor_snapshot);
9368
9369 if self.edit_prediction_requires_modifier() {
9370 if !self.edit_prediction_preview_is_active() {
9371 return None;
9372 }
9373
9374 self.render_edit_prediction_modifier_jump_popover(
9375 text_bounds,
9376 content_origin,
9377 visible_row_range,
9378 line_layouts,
9379 line_height,
9380 scroll_pixel_position,
9381 newest_selection_head,
9382 target_display_point,
9383 window,
9384 cx,
9385 )
9386 } else {
9387 self.render_edit_prediction_eager_jump_popover(
9388 text_bounds,
9389 content_origin,
9390 editor_snapshot,
9391 visible_row_range,
9392 scroll_top,
9393 scroll_bottom,
9394 line_height,
9395 scroll_pixel_position,
9396 target_display_point,
9397 editor_width,
9398 window,
9399 cx,
9400 )
9401 }
9402 }
9403 EditPrediction::Edit {
9404 display_mode: EditDisplayMode::Inline,
9405 ..
9406 } => None,
9407 EditPrediction::Edit {
9408 display_mode: EditDisplayMode::TabAccept,
9409 edits,
9410 ..
9411 } => {
9412 let range = &edits.first()?.0;
9413 let target_display_point = range.end.to_display_point(editor_snapshot);
9414
9415 self.render_edit_prediction_end_of_line_popover(
9416 "Accept",
9417 editor_snapshot,
9418 visible_row_range,
9419 target_display_point,
9420 line_height,
9421 scroll_pixel_position,
9422 content_origin,
9423 editor_width,
9424 window,
9425 cx,
9426 )
9427 }
9428 EditPrediction::Edit {
9429 edits,
9430 edit_preview,
9431 display_mode: EditDisplayMode::DiffPopover,
9432 snapshot,
9433 ..
9434 } => self.render_edit_prediction_diff_popover(
9435 text_bounds,
9436 content_origin,
9437 right_margin,
9438 editor_snapshot,
9439 visible_row_range,
9440 line_layouts,
9441 line_height,
9442 scroll_position,
9443 scroll_pixel_position,
9444 newest_selection_head,
9445 editor_width,
9446 style,
9447 edits,
9448 edit_preview,
9449 snapshot,
9450 window,
9451 cx,
9452 ),
9453 EditPrediction::MoveOutside { snapshot, .. } => {
9454 let mut element = self
9455 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9456 .into_any();
9457
9458 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9459 let origin_x = text_bounds.size.width - size.width - px(30.);
9460 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9461 element.prepaint_at(origin, window, cx);
9462
9463 Some((element, origin))
9464 }
9465 }
9466 }
9467
9468 fn render_edit_prediction_modifier_jump_popover(
9469 &mut self,
9470 text_bounds: &Bounds<Pixels>,
9471 content_origin: gpui::Point<Pixels>,
9472 visible_row_range: Range<DisplayRow>,
9473 line_layouts: &[LineWithInvisibles],
9474 line_height: Pixels,
9475 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9476 newest_selection_head: Option<DisplayPoint>,
9477 target_display_point: DisplayPoint,
9478 window: &mut Window,
9479 cx: &mut App,
9480 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9481 let scrolled_content_origin =
9482 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9483
9484 const SCROLL_PADDING_Y: Pixels = px(12.);
9485
9486 if target_display_point.row() < visible_row_range.start {
9487 return self.render_edit_prediction_scroll_popover(
9488 &|_| SCROLL_PADDING_Y,
9489 IconName::ArrowUp,
9490 visible_row_range,
9491 line_layouts,
9492 newest_selection_head,
9493 scrolled_content_origin,
9494 window,
9495 cx,
9496 );
9497 } else if target_display_point.row() >= visible_row_range.end {
9498 return self.render_edit_prediction_scroll_popover(
9499 &|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9500 IconName::ArrowDown,
9501 visible_row_range,
9502 line_layouts,
9503 newest_selection_head,
9504 scrolled_content_origin,
9505 window,
9506 cx,
9507 );
9508 }
9509
9510 const POLE_WIDTH: Pixels = px(2.);
9511
9512 let line_layout =
9513 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9514 let target_column = target_display_point.column() as usize;
9515
9516 let target_x = line_layout.x_for_index(target_column);
9517 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9518 - scroll_pixel_position.y;
9519
9520 let flag_on_right = target_x < text_bounds.size.width / 2.;
9521
9522 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9523 border_color.l += 0.001;
9524
9525 let mut element = v_flex()
9526 .items_end()
9527 .when(flag_on_right, |el| el.items_start())
9528 .child(if flag_on_right {
9529 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9530 .rounded_bl(px(0.))
9531 .rounded_tl(px(0.))
9532 .border_l_2()
9533 .border_color(border_color)
9534 } else {
9535 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9536 .rounded_br(px(0.))
9537 .rounded_tr(px(0.))
9538 .border_r_2()
9539 .border_color(border_color)
9540 })
9541 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9542 .into_any();
9543
9544 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9545
9546 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9547 - point(
9548 if flag_on_right {
9549 POLE_WIDTH
9550 } else {
9551 size.width - POLE_WIDTH
9552 },
9553 size.height - line_height,
9554 );
9555
9556 origin.x = origin.x.max(content_origin.x);
9557
9558 element.prepaint_at(origin, window, cx);
9559
9560 Some((element, origin))
9561 }
9562
9563 fn render_edit_prediction_scroll_popover(
9564 &mut self,
9565 to_y: &dyn Fn(Size<Pixels>) -> Pixels,
9566 scroll_icon: IconName,
9567 visible_row_range: Range<DisplayRow>,
9568 line_layouts: &[LineWithInvisibles],
9569 newest_selection_head: Option<DisplayPoint>,
9570 scrolled_content_origin: gpui::Point<Pixels>,
9571 window: &mut Window,
9572 cx: &mut App,
9573 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9574 let mut element = self
9575 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9576 .into_any();
9577
9578 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9579
9580 let cursor = newest_selection_head?;
9581 let cursor_row_layout =
9582 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9583 let cursor_column = cursor.column() as usize;
9584
9585 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9586
9587 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9588
9589 element.prepaint_at(origin, window, cx);
9590 Some((element, origin))
9591 }
9592
9593 fn render_edit_prediction_eager_jump_popover(
9594 &mut self,
9595 text_bounds: &Bounds<Pixels>,
9596 content_origin: gpui::Point<Pixels>,
9597 editor_snapshot: &EditorSnapshot,
9598 visible_row_range: Range<DisplayRow>,
9599 scroll_top: ScrollOffset,
9600 scroll_bottom: ScrollOffset,
9601 line_height: Pixels,
9602 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9603 target_display_point: DisplayPoint,
9604 editor_width: Pixels,
9605 window: &mut Window,
9606 cx: &mut App,
9607 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9608 if target_display_point.row().as_f64() < scroll_top {
9609 let mut element = self
9610 .render_edit_prediction_line_popover(
9611 "Jump to Edit",
9612 Some(IconName::ArrowUp),
9613 window,
9614 cx,
9615 )
9616 .into_any();
9617
9618 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9619 let offset = point(
9620 (text_bounds.size.width - size.width) / 2.,
9621 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9622 );
9623
9624 let origin = text_bounds.origin + offset;
9625 element.prepaint_at(origin, window, cx);
9626 Some((element, origin))
9627 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9628 let mut element = self
9629 .render_edit_prediction_line_popover(
9630 "Jump to Edit",
9631 Some(IconName::ArrowDown),
9632 window,
9633 cx,
9634 )
9635 .into_any();
9636
9637 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9638 let offset = point(
9639 (text_bounds.size.width - size.width) / 2.,
9640 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9641 );
9642
9643 let origin = text_bounds.origin + offset;
9644 element.prepaint_at(origin, window, cx);
9645 Some((element, origin))
9646 } else {
9647 self.render_edit_prediction_end_of_line_popover(
9648 "Jump to Edit",
9649 editor_snapshot,
9650 visible_row_range,
9651 target_display_point,
9652 line_height,
9653 scroll_pixel_position,
9654 content_origin,
9655 editor_width,
9656 window,
9657 cx,
9658 )
9659 }
9660 }
9661
9662 fn render_edit_prediction_end_of_line_popover(
9663 self: &mut Editor,
9664 label: &'static str,
9665 editor_snapshot: &EditorSnapshot,
9666 visible_row_range: Range<DisplayRow>,
9667 target_display_point: DisplayPoint,
9668 line_height: Pixels,
9669 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9670 content_origin: gpui::Point<Pixels>,
9671 editor_width: Pixels,
9672 window: &mut Window,
9673 cx: &mut App,
9674 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9675 let target_line_end = DisplayPoint::new(
9676 target_display_point.row(),
9677 editor_snapshot.line_len(target_display_point.row()),
9678 );
9679
9680 let mut element = self
9681 .render_edit_prediction_line_popover(label, None, window, cx)
9682 .into_any();
9683
9684 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9685
9686 let line_origin =
9687 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9688
9689 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9690 let mut origin = start_point
9691 + line_origin
9692 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9693 origin.x = origin.x.max(content_origin.x);
9694
9695 let max_x = content_origin.x + editor_width - size.width;
9696
9697 if origin.x > max_x {
9698 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9699
9700 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9701 origin.y += offset;
9702 IconName::ArrowUp
9703 } else {
9704 origin.y -= offset;
9705 IconName::ArrowDown
9706 };
9707
9708 element = self
9709 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9710 .into_any();
9711
9712 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9713
9714 origin.x = content_origin.x + editor_width - size.width - px(2.);
9715 }
9716
9717 element.prepaint_at(origin, window, cx);
9718 Some((element, origin))
9719 }
9720
9721 fn render_edit_prediction_diff_popover(
9722 self: &Editor,
9723 text_bounds: &Bounds<Pixels>,
9724 content_origin: gpui::Point<Pixels>,
9725 right_margin: Pixels,
9726 editor_snapshot: &EditorSnapshot,
9727 visible_row_range: Range<DisplayRow>,
9728 line_layouts: &[LineWithInvisibles],
9729 line_height: Pixels,
9730 scroll_position: gpui::Point<ScrollOffset>,
9731 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9732 newest_selection_head: Option<DisplayPoint>,
9733 editor_width: Pixels,
9734 style: &EditorStyle,
9735 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9736 edit_preview: &Option<language::EditPreview>,
9737 snapshot: &language::BufferSnapshot,
9738 window: &mut Window,
9739 cx: &mut App,
9740 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9741 let edit_start = edits
9742 .first()
9743 .unwrap()
9744 .0
9745 .start
9746 .to_display_point(editor_snapshot);
9747 let edit_end = edits
9748 .last()
9749 .unwrap()
9750 .0
9751 .end
9752 .to_display_point(editor_snapshot);
9753
9754 let is_visible = visible_row_range.contains(&edit_start.row())
9755 || visible_row_range.contains(&edit_end.row());
9756 if !is_visible {
9757 return None;
9758 }
9759
9760 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9761 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9762 } else {
9763 // Fallback for providers without edit_preview
9764 crate::edit_prediction_fallback_text(edits, cx)
9765 };
9766
9767 let styled_text = highlighted_edits.to_styled_text(&style.text);
9768 let line_count = highlighted_edits.text.lines().count();
9769
9770 const BORDER_WIDTH: Pixels = px(1.);
9771
9772 let keybind = self.render_edit_prediction_keybind(window, cx);
9773 let has_keybind = keybind.is_some();
9774
9775 let mut element = h_flex()
9776 .items_start()
9777 .child(
9778 h_flex()
9779 .bg(cx.theme().colors().editor_background)
9780 .border(BORDER_WIDTH)
9781 .shadow_xs()
9782 .border_color(cx.theme().colors().border)
9783 .rounded_l_lg()
9784 .when(line_count > 1, |el| el.rounded_br_lg())
9785 .pr_1()
9786 .child(styled_text),
9787 )
9788 .child(
9789 h_flex()
9790 .h(line_height + BORDER_WIDTH * 2.)
9791 .px_1p5()
9792 .gap_1()
9793 // Workaround: For some reason, there's a gap if we don't do this
9794 .ml(-BORDER_WIDTH)
9795 .shadow(vec![gpui::BoxShadow {
9796 color: gpui::black().opacity(0.05),
9797 offset: point(px(1.), px(1.)),
9798 blur_radius: px(2.),
9799 spread_radius: px(0.),
9800 }])
9801 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9802 .border(BORDER_WIDTH)
9803 .border_color(cx.theme().colors().border)
9804 .rounded_r_lg()
9805 .id("edit_prediction_diff_popover_keybind")
9806 .when(!has_keybind, |el| {
9807 let status_colors = cx.theme().status();
9808
9809 el.bg(status_colors.error_background)
9810 .border_color(status_colors.error.opacity(0.6))
9811 .child(Icon::new(IconName::Info).color(Color::Error))
9812 .cursor_default()
9813 .hoverable_tooltip(move |_window, cx| {
9814 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9815 })
9816 })
9817 .children(keybind),
9818 )
9819 .into_any();
9820
9821 let longest_row =
9822 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9823 let longest_line_width = if visible_row_range.contains(&longest_row) {
9824 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9825 } else {
9826 layout_line(
9827 longest_row,
9828 editor_snapshot,
9829 style,
9830 editor_width,
9831 |_| false,
9832 window,
9833 cx,
9834 )
9835 .width
9836 };
9837
9838 let viewport_bounds =
9839 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9840 right: -right_margin,
9841 ..Default::default()
9842 });
9843
9844 let x_after_longest = Pixels::from(
9845 ScrollPixelOffset::from(
9846 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9847 ) - scroll_pixel_position.x,
9848 );
9849
9850 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9851
9852 // Fully visible if it can be displayed within the window (allow overlapping other
9853 // panes). However, this is only allowed if the popover starts within text_bounds.
9854 let can_position_to_the_right = x_after_longest < text_bounds.right()
9855 && x_after_longest + element_bounds.width < viewport_bounds.right();
9856
9857 let mut origin = if can_position_to_the_right {
9858 point(
9859 x_after_longest,
9860 text_bounds.origin.y
9861 + Pixels::from(
9862 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9863 - scroll_pixel_position.y,
9864 ),
9865 )
9866 } else {
9867 let cursor_row = newest_selection_head.map(|head| head.row());
9868 let above_edit = edit_start
9869 .row()
9870 .0
9871 .checked_sub(line_count as u32)
9872 .map(DisplayRow);
9873 let below_edit = Some(edit_end.row() + 1);
9874 let above_cursor =
9875 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9876 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9877
9878 // Place the edit popover adjacent to the edit if there is a location
9879 // available that is onscreen and does not obscure the cursor. Otherwise,
9880 // place it adjacent to the cursor.
9881 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9882 .into_iter()
9883 .flatten()
9884 .find(|&start_row| {
9885 let end_row = start_row + line_count as u32;
9886 visible_row_range.contains(&start_row)
9887 && visible_row_range.contains(&end_row)
9888 && cursor_row
9889 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9890 })?;
9891
9892 content_origin
9893 + point(
9894 Pixels::from(-scroll_pixel_position.x),
9895 Pixels::from(
9896 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9897 ),
9898 )
9899 };
9900
9901 origin.x -= BORDER_WIDTH;
9902
9903 window.with_content_mask(
9904 Some(gpui::ContentMask {
9905 bounds: *text_bounds,
9906 }),
9907 |window| {
9908 window.defer_draw(element, origin, 1, Some(window.content_mask()));
9909 },
9910 );
9911
9912 // Do not return an element, since it will already be drawn due to defer_draw.
9913 None
9914 }
9915
9916 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9917 px(30.)
9918 }
9919
9920 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9921 if self.read_only(cx) {
9922 cx.theme().players().read_only()
9923 } else {
9924 self.style.as_ref().unwrap().local_player
9925 }
9926 }
9927
9928 fn render_edit_prediction_inline_keystroke(
9929 &self,
9930 keystroke: &gpui::KeybindingKeystroke,
9931 modifiers_color: Color,
9932 cx: &App,
9933 ) -> AnyElement {
9934 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9935
9936 h_flex()
9937 .px_0p5()
9938 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9939 .font(
9940 theme_settings::ThemeSettings::get_global(cx)
9941 .buffer_font
9942 .clone(),
9943 )
9944 .text_size(TextSize::XSmall.rems(cx))
9945 .child(h_flex().children(ui::render_modifiers(
9946 keystroke.modifiers(),
9947 PlatformStyle::platform(),
9948 Some(modifiers_color),
9949 Some(IconSize::XSmall.rems().into()),
9950 true,
9951 )))
9952 .when(is_platform_style_mac, |parent| {
9953 parent.child(keystroke.key().to_string())
9954 })
9955 .when(!is_platform_style_mac, |parent| {
9956 parent.child(
9957 Key::new(ui::utils::capitalize(keystroke.key()), Some(Color::Default))
9958 .size(Some(IconSize::XSmall.rems().into())),
9959 )
9960 })
9961 .into_any()
9962 }
9963
9964 fn render_edit_prediction_popover_keystroke(
9965 &self,
9966 keystroke: &gpui::KeybindingKeystroke,
9967 color: Color,
9968 cx: &App,
9969 ) -> AnyElement {
9970 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9971
9972 if keystroke.modifiers().modified() {
9973 h_flex()
9974 .font(
9975 theme_settings::ThemeSettings::get_global(cx)
9976 .buffer_font
9977 .clone(),
9978 )
9979 .when(is_platform_style_mac, |parent| parent.gap_1())
9980 .child(h_flex().children(ui::render_modifiers(
9981 keystroke.modifiers(),
9982 PlatformStyle::platform(),
9983 Some(color),
9984 None,
9985 false,
9986 )))
9987 .into_any()
9988 } else {
9989 Key::new(ui::utils::capitalize(keystroke.key()), Some(color))
9990 .size(Some(IconSize::XSmall.rems().into()))
9991 .into_any_element()
9992 }
9993 }
9994
9995 fn render_edit_prediction_keybind(
9996 &self,
9997 window: &mut Window,
9998 cx: &mut App,
9999 ) -> Option<AnyElement> {
10000 let keybind_display =
10001 self.edit_prediction_keybind_display(EditPredictionKeybindSurface::Inline, window, cx);
10002 let keystroke = keybind_display.displayed_keystroke.as_ref()?;
10003
10004 let modifiers_color = if *keystroke.modifiers() == window.modifiers() {
10005 Color::Accent
10006 } else {
10007 Color::Muted
10008 };
10009
10010 Some(self.render_edit_prediction_inline_keystroke(keystroke, modifiers_color, cx))
10011 }
10012
10013 fn render_edit_prediction_line_popover(
10014 &self,
10015 label: impl Into<SharedString>,
10016 icon: Option<IconName>,
10017 window: &mut Window,
10018 cx: &mut App,
10019 ) -> Stateful<Div> {
10020 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
10021
10022 let keybind = self.render_edit_prediction_keybind(window, cx);
10023 let has_keybind = keybind.is_some();
10024 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10025
10026 h_flex()
10027 .id("ep-line-popover")
10028 .py_0p5()
10029 .pl_1()
10030 .pr(padding_right)
10031 .gap_1()
10032 .rounded_md()
10033 .border_1()
10034 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10035 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10036 .shadow_xs()
10037 .when(!has_keybind, |el| {
10038 let status_colors = cx.theme().status();
10039
10040 el.bg(status_colors.error_background)
10041 .border_color(status_colors.error.opacity(0.6))
10042 .pl_2()
10043 .child(Icon::new(icons.error).color(Color::Error))
10044 .cursor_default()
10045 .hoverable_tooltip(move |_window, cx| {
10046 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10047 })
10048 })
10049 .children(keybind)
10050 .child(
10051 Label::new(label)
10052 .size(LabelSize::Small)
10053 .when(!has_keybind, |el| {
10054 el.color(cx.theme().status().error.into()).strikethrough()
10055 }),
10056 )
10057 .when(!has_keybind, |el| {
10058 el.child(
10059 h_flex().ml_1().child(
10060 Icon::new(IconName::Info)
10061 .size(IconSize::Small)
10062 .color(cx.theme().status().error.into()),
10063 ),
10064 )
10065 })
10066 .when_some(icon, |element, icon| {
10067 element.child(
10068 div()
10069 .mt(px(1.5))
10070 .child(Icon::new(icon).size(IconSize::Small)),
10071 )
10072 })
10073 }
10074
10075 fn render_edit_prediction_jump_outside_popover(
10076 &self,
10077 snapshot: &BufferSnapshot,
10078 window: &mut Window,
10079 cx: &mut App,
10080 ) -> Stateful<Div> {
10081 let keybind = self.render_edit_prediction_keybind(window, cx);
10082 let has_keybind = keybind.is_some();
10083 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10084
10085 let file_name = snapshot
10086 .file()
10087 .map(|file| SharedString::new(file.file_name(cx)))
10088 .unwrap_or(SharedString::new_static("untitled"));
10089
10090 h_flex()
10091 .id("ep-jump-outside-popover")
10092 .py_1()
10093 .px_2()
10094 .gap_1()
10095 .rounded_md()
10096 .border_1()
10097 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10098 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
10099 .shadow_xs()
10100 .when(!has_keybind, |el| {
10101 let status_colors = cx.theme().status();
10102
10103 el.bg(status_colors.error_background)
10104 .border_color(status_colors.error.opacity(0.6))
10105 .pl_2()
10106 .child(Icon::new(icons.error).color(Color::Error))
10107 .cursor_default()
10108 .hoverable_tooltip(move |_window, cx| {
10109 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
10110 })
10111 })
10112 .children(keybind)
10113 .child(
10114 Label::new(file_name)
10115 .size(LabelSize::Small)
10116 .buffer_font(cx)
10117 .when(!has_keybind, |el| {
10118 el.color(cx.theme().status().error.into()).strikethrough()
10119 }),
10120 )
10121 .when(!has_keybind, |el| {
10122 el.child(
10123 h_flex().ml_1().child(
10124 Icon::new(IconName::Info)
10125 .size(IconSize::Small)
10126 .color(cx.theme().status().error.into()),
10127 ),
10128 )
10129 })
10130 .child(
10131 div()
10132 .mt(px(1.5))
10133 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
10134 )
10135 }
10136
10137 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
10138 let accent_color = cx.theme().colors().text_accent;
10139 let editor_bg_color = cx.theme().colors().editor_background;
10140 editor_bg_color.blend(accent_color.opacity(0.1))
10141 }
10142
10143 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
10144 let accent_color = cx.theme().colors().text_accent;
10145 let editor_bg_color = cx.theme().colors().editor_background;
10146 editor_bg_color.blend(accent_color.opacity(0.6))
10147 }
10148 fn get_prediction_provider_icons(
10149 provider: &Option<RegisteredEditPredictionDelegate>,
10150 cx: &App,
10151 ) -> edit_prediction_types::EditPredictionIconSet {
10152 match provider {
10153 Some(provider) => provider.provider.icons(cx),
10154 None => edit_prediction_types::EditPredictionIconSet::new(IconName::ZedPredict),
10155 }
10156 }
10157
10158 fn render_edit_prediction_cursor_popover(
10159 &self,
10160 min_width: Pixels,
10161 max_width: Pixels,
10162 cursor_point: Point,
10163 style: &EditorStyle,
10164 window: &mut Window,
10165 cx: &mut Context<Editor>,
10166 ) -> Option<AnyElement> {
10167 let provider = self.edit_prediction_provider.as_ref()?;
10168 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10169
10170 let is_refreshing = provider.provider.is_refreshing(cx);
10171
10172 fn pending_completion_container(icon: IconName) -> Div {
10173 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
10174 }
10175
10176 let completion = match &self.active_edit_prediction {
10177 Some(prediction) => {
10178 if !self.has_visible_completions_menu() {
10179 const RADIUS: Pixels = px(6.);
10180 const BORDER_WIDTH: Pixels = px(1.);
10181 let keybind_display = self.edit_prediction_keybind_display(
10182 EditPredictionKeybindSurface::CursorPopoverCompact,
10183 window,
10184 cx,
10185 );
10186
10187 return Some(
10188 h_flex()
10189 .elevation_2(cx)
10190 .border(BORDER_WIDTH)
10191 .border_color(cx.theme().colors().border)
10192 .when(keybind_display.missing_accept_keystroke, |el| {
10193 el.border_color(cx.theme().status().error)
10194 })
10195 .rounded(RADIUS)
10196 .rounded_tl(px(0.))
10197 .overflow_hidden()
10198 .child(div().px_1p5().child(match &prediction.completion {
10199 EditPrediction::MoveWithin { target, snapshot } => {
10200 use text::ToPoint as _;
10201 if target.text_anchor.to_point(snapshot).row > cursor_point.row
10202 {
10203 Icon::new(icons.down)
10204 } else {
10205 Icon::new(icons.up)
10206 }
10207 }
10208 EditPrediction::MoveOutside { .. } => {
10209 // TODO [zeta2] custom icon for external jump?
10210 Icon::new(icons.base)
10211 }
10212 EditPrediction::Edit { .. } => Icon::new(icons.base),
10213 }))
10214 .child(
10215 h_flex()
10216 .gap_1()
10217 .py_1()
10218 .px_2()
10219 .rounded_r(RADIUS - BORDER_WIDTH)
10220 .border_l_1()
10221 .border_color(cx.theme().colors().border)
10222 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10223 .when(keybind_display.show_hold_label, |el| {
10224 el.child(
10225 Label::new("Hold")
10226 .size(LabelSize::Small)
10227 .when(
10228 keybind_display.missing_accept_keystroke,
10229 |el| el.strikethrough(),
10230 )
10231 .line_height_style(LineHeightStyle::UiLabel),
10232 )
10233 })
10234 .id("edit_prediction_cursor_popover_keybind")
10235 .when(keybind_display.missing_accept_keystroke, |el| {
10236 let status_colors = cx.theme().status();
10237
10238 el.bg(status_colors.error_background)
10239 .border_color(status_colors.error.opacity(0.6))
10240 .child(Icon::new(IconName::Info).color(Color::Error))
10241 .cursor_default()
10242 .hoverable_tooltip(move |_window, cx| {
10243 cx.new(|_| MissingEditPredictionKeybindingTooltip)
10244 .into()
10245 })
10246 })
10247 .when_some(
10248 keybind_display.displayed_keystroke.as_ref(),
10249 |el, compact_keystroke| {
10250 el.child(self.render_edit_prediction_popover_keystroke(
10251 compact_keystroke,
10252 Color::Default,
10253 cx,
10254 ))
10255 },
10256 ),
10257 )
10258 .into_any(),
10259 );
10260 }
10261
10262 self.render_edit_prediction_cursor_popover_preview(
10263 prediction,
10264 cursor_point,
10265 style,
10266 cx,
10267 )?
10268 }
10269
10270 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
10271 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
10272 stale_completion,
10273 cursor_point,
10274 style,
10275 cx,
10276 )?,
10277
10278 None => pending_completion_container(icons.base)
10279 .child(Label::new("...").size(LabelSize::Small)),
10280 },
10281
10282 None => pending_completion_container(icons.base)
10283 .child(Label::new("...").size(LabelSize::Small)),
10284 };
10285
10286 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
10287 completion
10288 .with_animation(
10289 "loading-completion",
10290 Animation::new(Duration::from_secs(2))
10291 .repeat()
10292 .with_easing(pulsating_between(0.4, 0.8)),
10293 |label, delta| label.opacity(delta),
10294 )
10295 .into_any_element()
10296 } else {
10297 completion.into_any_element()
10298 };
10299
10300 let has_completion = self.active_edit_prediction.is_some();
10301 let keybind_display = self.edit_prediction_keybind_display(
10302 EditPredictionKeybindSurface::CursorPopoverExpanded,
10303 window,
10304 cx,
10305 );
10306
10307 Some(
10308 h_flex()
10309 .min_w(min_width)
10310 .max_w(max_width)
10311 .flex_1()
10312 .elevation_2(cx)
10313 .border_color(cx.theme().colors().border)
10314 .child(
10315 div()
10316 .flex_1()
10317 .py_1()
10318 .px_2()
10319 .overflow_hidden()
10320 .child(completion),
10321 )
10322 .when_some(
10323 keybind_display.displayed_keystroke.as_ref(),
10324 |el, keystroke| {
10325 let key_color = if !has_completion {
10326 Color::Muted
10327 } else {
10328 Color::Default
10329 };
10330
10331 if keybind_display.action == EditPredictionKeybindAction::Preview {
10332 el.child(
10333 h_flex()
10334 .h_full()
10335 .border_l_1()
10336 .rounded_r_lg()
10337 .border_color(cx.theme().colors().border)
10338 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10339 .gap_1()
10340 .py_1()
10341 .px_2()
10342 .child(self.render_edit_prediction_popover_keystroke(
10343 keystroke, key_color, cx,
10344 ))
10345 .child(Label::new("Preview").into_any_element())
10346 .opacity(if has_completion { 1.0 } else { 0.4 }),
10347 )
10348 } else {
10349 el.child(
10350 h_flex()
10351 .h_full()
10352 .border_l_1()
10353 .rounded_r_lg()
10354 .border_color(cx.theme().colors().border)
10355 .bg(Self::edit_prediction_line_popover_bg_color(cx))
10356 .gap_1()
10357 .py_1()
10358 .px_2()
10359 .child(self.render_edit_prediction_popover_keystroke(
10360 keystroke, key_color, cx,
10361 ))
10362 .opacity(if has_completion { 1.0 } else { 0.4 }),
10363 )
10364 }
10365 },
10366 )
10367 .into_any(),
10368 )
10369 }
10370
10371 fn render_edit_prediction_cursor_popover_preview(
10372 &self,
10373 completion: &EditPredictionState,
10374 cursor_point: Point,
10375 style: &EditorStyle,
10376 cx: &mut Context<Editor>,
10377 ) -> Option<Div> {
10378 use text::ToPoint as _;
10379
10380 fn render_relative_row_jump(
10381 prefix: impl Into<String>,
10382 current_row: u32,
10383 target_row: u32,
10384 ) -> Div {
10385 let (row_diff, arrow) = if target_row < current_row {
10386 (current_row - target_row, IconName::ArrowUp)
10387 } else {
10388 (target_row - current_row, IconName::ArrowDown)
10389 };
10390
10391 h_flex()
10392 .child(
10393 Label::new(format!("{}{}", prefix.into(), row_diff))
10394 .color(Color::Muted)
10395 .size(LabelSize::Small),
10396 )
10397 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
10398 }
10399
10400 let supports_jump = self
10401 .edit_prediction_provider
10402 .as_ref()
10403 .map(|provider| provider.provider.supports_jump_to_edit())
10404 .unwrap_or(true);
10405
10406 let icons = Self::get_prediction_provider_icons(&self.edit_prediction_provider, cx);
10407
10408 match &completion.completion {
10409 EditPrediction::MoveWithin {
10410 target, snapshot, ..
10411 } => {
10412 if !supports_jump {
10413 return None;
10414 }
10415
10416 Some(
10417 h_flex()
10418 .px_2()
10419 .gap_2()
10420 .flex_1()
10421 .child(
10422 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
10423 Icon::new(icons.down)
10424 } else {
10425 Icon::new(icons.up)
10426 },
10427 )
10428 .child(Label::new("Jump to Edit")),
10429 )
10430 }
10431 EditPrediction::MoveOutside { snapshot, .. } => {
10432 let file_name = snapshot
10433 .file()
10434 .map(|file| file.file_name(cx))
10435 .unwrap_or("untitled");
10436 Some(
10437 h_flex()
10438 .px_2()
10439 .gap_2()
10440 .flex_1()
10441 .child(Icon::new(icons.base))
10442 .child(Label::new(format!("Jump to {file_name}"))),
10443 )
10444 }
10445 EditPrediction::Edit {
10446 edits,
10447 edit_preview,
10448 snapshot,
10449 ..
10450 } => {
10451 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10452
10453 let (highlighted_edits, has_more_lines) =
10454 if let Some(edit_preview) = edit_preview.as_ref() {
10455 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10456 .first_line_preview()
10457 } else {
10458 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10459 };
10460
10461 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10462 .with_default_highlights(&style.text, highlighted_edits.highlights);
10463
10464 let preview = h_flex()
10465 .gap_1()
10466 .min_w_16()
10467 .child(styled_text)
10468 .when(has_more_lines, |parent| parent.child("…"));
10469
10470 let left = if supports_jump && first_edit_row != cursor_point.row {
10471 render_relative_row_jump("", cursor_point.row, first_edit_row)
10472 .into_any_element()
10473 } else {
10474 Icon::new(icons.base).into_any_element()
10475 };
10476
10477 Some(
10478 h_flex()
10479 .h_full()
10480 .flex_1()
10481 .gap_2()
10482 .pr_1()
10483 .overflow_x_hidden()
10484 .font(
10485 theme_settings::ThemeSettings::get_global(cx)
10486 .buffer_font
10487 .clone(),
10488 )
10489 .child(left)
10490 .child(preview),
10491 )
10492 }
10493 }
10494 }
10495
10496 pub fn render_context_menu(
10497 &mut self,
10498 max_height_in_lines: u32,
10499 window: &mut Window,
10500 cx: &mut Context<Editor>,
10501 ) -> Option<AnyElement> {
10502 let menu = self.context_menu.borrow();
10503 let menu = menu.as_ref()?;
10504 if !menu.visible() {
10505 return None;
10506 };
10507 self.style
10508 .as_ref()
10509 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10510 }
10511
10512 fn render_context_menu_aside(
10513 &mut self,
10514 max_size: Size<Pixels>,
10515 window: &mut Window,
10516 cx: &mut Context<Editor>,
10517 ) -> Option<AnyElement> {
10518 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10519 if menu.visible() {
10520 menu.render_aside(max_size, window, cx)
10521 } else {
10522 None
10523 }
10524 })
10525 }
10526
10527 fn hide_context_menu(
10528 &mut self,
10529 window: &mut Window,
10530 cx: &mut Context<Self>,
10531 ) -> Option<CodeContextMenu> {
10532 cx.notify();
10533 self.completion_tasks.clear();
10534 let context_menu = self.context_menu.borrow_mut().take();
10535 self.stale_edit_prediction_in_menu.take();
10536 self.update_visible_edit_prediction(window, cx);
10537 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10538 && let Some(completion_provider) = &self.completion_provider
10539 {
10540 completion_provider.selection_changed(None, window, cx);
10541 }
10542 context_menu
10543 }
10544
10545 fn show_snippet_choices(
10546 &mut self,
10547 choices: &Vec<String>,
10548 selection: Range<Anchor>,
10549 cx: &mut Context<Self>,
10550 ) {
10551 let Some((_, buffer, _)) = self
10552 .buffer()
10553 .read(cx)
10554 .excerpt_containing(selection.start, cx)
10555 else {
10556 return;
10557 };
10558 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10559 else {
10560 return;
10561 };
10562 if buffer != end_buffer {
10563 log::error!("expected anchor range to have matching buffer IDs");
10564 return;
10565 }
10566
10567 let id = post_inc(&mut self.next_completion_id);
10568 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10569 let mut context_menu = self.context_menu.borrow_mut();
10570 let old_menu = context_menu.take();
10571 *context_menu = Some(CodeContextMenu::Completions(
10572 CompletionsMenu::new_snippet_choices(
10573 id,
10574 true,
10575 choices,
10576 selection,
10577 buffer,
10578 old_menu.map(|menu| menu.primary_scroll_handle()),
10579 snippet_sort_order,
10580 ),
10581 ));
10582 }
10583
10584 pub fn insert_snippet(
10585 &mut self,
10586 insertion_ranges: &[Range<MultiBufferOffset>],
10587 snippet: Snippet,
10588 window: &mut Window,
10589 cx: &mut Context<Self>,
10590 ) -> Result<()> {
10591 struct Tabstop<T> {
10592 is_end_tabstop: bool,
10593 ranges: Vec<Range<T>>,
10594 choices: Option<Vec<String>>,
10595 }
10596
10597 let tabstops = self.buffer.update(cx, |buffer, cx| {
10598 let snippet_text: Arc<str> = snippet.text.clone().into();
10599 let edits = insertion_ranges
10600 .iter()
10601 .cloned()
10602 .map(|range| (range, snippet_text.clone()));
10603 let autoindent_mode = AutoindentMode::Block {
10604 original_indent_columns: Vec::new(),
10605 };
10606 buffer.edit(edits, Some(autoindent_mode), cx);
10607
10608 let snapshot = &*buffer.read(cx);
10609 let snippet = &snippet;
10610 snippet
10611 .tabstops
10612 .iter()
10613 .map(|tabstop| {
10614 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10615 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10616 });
10617 let mut tabstop_ranges = tabstop
10618 .ranges
10619 .iter()
10620 .flat_map(|tabstop_range| {
10621 let mut delta = 0_isize;
10622 insertion_ranges.iter().map(move |insertion_range| {
10623 let insertion_start = insertion_range.start + delta;
10624 delta += snippet.text.len() as isize
10625 - (insertion_range.end - insertion_range.start) as isize;
10626
10627 let start =
10628 (insertion_start + tabstop_range.start).min(snapshot.len());
10629 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10630 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10631 })
10632 })
10633 .collect::<Vec<_>>();
10634 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10635
10636 Tabstop {
10637 is_end_tabstop,
10638 ranges: tabstop_ranges,
10639 choices: tabstop.choices.clone(),
10640 }
10641 })
10642 .collect::<Vec<_>>()
10643 });
10644 if let Some(tabstop) = tabstops.first() {
10645 self.change_selections(Default::default(), window, cx, |s| {
10646 // Reverse order so that the first range is the newest created selection.
10647 // Completions will use it and autoscroll will prioritize it.
10648 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10649 });
10650
10651 if let Some(choices) = &tabstop.choices
10652 && let Some(selection) = tabstop.ranges.first()
10653 {
10654 self.show_snippet_choices(choices, selection.clone(), cx)
10655 }
10656
10657 // If we're already at the last tabstop and it's at the end of the snippet,
10658 // we're done, we don't need to keep the state around.
10659 if !tabstop.is_end_tabstop {
10660 let choices = tabstops
10661 .iter()
10662 .map(|tabstop| tabstop.choices.clone())
10663 .collect();
10664
10665 let ranges = tabstops
10666 .into_iter()
10667 .map(|tabstop| tabstop.ranges)
10668 .collect::<Vec<_>>();
10669
10670 self.snippet_stack.push(SnippetState {
10671 active_index: 0,
10672 ranges,
10673 choices,
10674 });
10675 }
10676
10677 // Check whether the just-entered snippet ends with an auto-closable bracket.
10678 if self.autoclose_regions.is_empty() {
10679 let snapshot = self.buffer.read(cx).snapshot(cx);
10680 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10681 let selection_head = selection.head();
10682 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10683 continue;
10684 };
10685
10686 let mut bracket_pair = None;
10687 let max_lookup_length = scope
10688 .brackets()
10689 .map(|(pair, _)| {
10690 pair.start
10691 .as_str()
10692 .chars()
10693 .count()
10694 .max(pair.end.as_str().chars().count())
10695 })
10696 .max();
10697 if let Some(max_lookup_length) = max_lookup_length {
10698 let next_text = snapshot
10699 .chars_at(selection_head)
10700 .take(max_lookup_length)
10701 .collect::<String>();
10702 let prev_text = snapshot
10703 .reversed_chars_at(selection_head)
10704 .take(max_lookup_length)
10705 .collect::<String>();
10706
10707 for (pair, enabled) in scope.brackets() {
10708 if enabled
10709 && pair.close
10710 && prev_text.starts_with(pair.start.as_str())
10711 && next_text.starts_with(pair.end.as_str())
10712 {
10713 bracket_pair = Some(pair.clone());
10714 break;
10715 }
10716 }
10717 }
10718
10719 if let Some(pair) = bracket_pair {
10720 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10721 let autoclose_enabled =
10722 self.use_autoclose && snapshot_settings.use_autoclose;
10723 if autoclose_enabled {
10724 let start = snapshot.anchor_after(selection_head);
10725 let end = snapshot.anchor_after(selection_head);
10726 self.autoclose_regions.push(AutocloseRegion {
10727 selection_id: selection.id,
10728 range: start..end,
10729 pair,
10730 });
10731 }
10732 }
10733 }
10734 }
10735 }
10736 Ok(())
10737 }
10738
10739 pub fn move_to_next_snippet_tabstop(
10740 &mut self,
10741 window: &mut Window,
10742 cx: &mut Context<Self>,
10743 ) -> bool {
10744 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10745 }
10746
10747 pub fn move_to_prev_snippet_tabstop(
10748 &mut self,
10749 window: &mut Window,
10750 cx: &mut Context<Self>,
10751 ) -> bool {
10752 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10753 }
10754
10755 pub fn move_to_snippet_tabstop(
10756 &mut self,
10757 bias: Bias,
10758 window: &mut Window,
10759 cx: &mut Context<Self>,
10760 ) -> bool {
10761 if let Some(mut snippet) = self.snippet_stack.pop() {
10762 match bias {
10763 Bias::Left => {
10764 if snippet.active_index > 0 {
10765 snippet.active_index -= 1;
10766 } else {
10767 self.snippet_stack.push(snippet);
10768 return false;
10769 }
10770 }
10771 Bias::Right => {
10772 if snippet.active_index + 1 < snippet.ranges.len() {
10773 snippet.active_index += 1;
10774 } else {
10775 self.snippet_stack.push(snippet);
10776 return false;
10777 }
10778 }
10779 }
10780 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10781 self.change_selections(Default::default(), window, cx, |s| {
10782 // Reverse order so that the first range is the newest created selection.
10783 // Completions will use it and autoscroll will prioritize it.
10784 s.select_ranges(current_ranges.iter().rev().cloned())
10785 });
10786
10787 if let Some(choices) = &snippet.choices[snippet.active_index]
10788 && let Some(selection) = current_ranges.first()
10789 {
10790 self.show_snippet_choices(choices, selection.clone(), cx);
10791 }
10792
10793 // If snippet state is not at the last tabstop, push it back on the stack
10794 if snippet.active_index + 1 < snippet.ranges.len() {
10795 self.snippet_stack.push(snippet);
10796 }
10797 return true;
10798 }
10799 }
10800
10801 false
10802 }
10803
10804 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10805 self.transact(window, cx, |this, window, cx| {
10806 this.select_all(&SelectAll, window, cx);
10807 this.insert("", window, cx);
10808 });
10809 }
10810
10811 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10812 if self.read_only(cx) {
10813 return;
10814 }
10815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10816 self.transact(window, cx, |this, window, cx| {
10817 this.select_autoclose_pair(window, cx);
10818
10819 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10820
10821 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10822 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10823 for selection in &mut selections {
10824 if selection.is_empty() {
10825 let old_head = selection.head();
10826 let mut new_head =
10827 movement::left(&display_map, old_head.to_display_point(&display_map))
10828 .to_point(&display_map);
10829 if let Some((buffer, line_buffer_range)) = display_map
10830 .buffer_snapshot()
10831 .buffer_line_for_row(MultiBufferRow(old_head.row))
10832 {
10833 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10834 let indent_len = match indent_size.kind {
10835 IndentKind::Space => {
10836 buffer.settings_at(line_buffer_range.start, cx).tab_size
10837 }
10838 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10839 };
10840 if old_head.column <= indent_size.len && old_head.column > 0 {
10841 let indent_len = indent_len.get();
10842 new_head = cmp::min(
10843 new_head,
10844 MultiBufferPoint::new(
10845 old_head.row,
10846 ((old_head.column - 1) / indent_len) * indent_len,
10847 ),
10848 );
10849 }
10850 }
10851
10852 selection.set_head(new_head, SelectionGoal::None);
10853 }
10854 }
10855
10856 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10857 this.insert("", window, cx);
10858 linked_edits.apply_with_left_expansion(cx);
10859 this.refresh_edit_prediction(true, false, window, cx);
10860 refresh_linked_ranges(this, window, cx);
10861 });
10862 }
10863
10864 pub fn delete(&mut self, _: &Delete, 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.change_selections(Default::default(), window, cx, |s| {
10871 s.move_with(&mut |map, selection| {
10872 if selection.is_empty() {
10873 let cursor = movement::right(map, selection.head());
10874 selection.end = cursor;
10875 selection.reversed = true;
10876 selection.goal = SelectionGoal::None;
10877 }
10878 })
10879 });
10880 let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx);
10881 this.insert("", window, cx);
10882 linked_edits.apply(cx);
10883 this.refresh_edit_prediction(true, false, window, cx);
10884 refresh_linked_ranges(this, window, cx);
10885 });
10886 }
10887
10888 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10889 if self.mode.is_single_line() {
10890 cx.propagate();
10891 return;
10892 }
10893
10894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10895 if self.move_to_prev_snippet_tabstop(window, cx) {
10896 return;
10897 }
10898 self.outdent(&Outdent, window, cx);
10899 }
10900
10901 pub fn next_snippet_tabstop(
10902 &mut self,
10903 _: &NextSnippetTabstop,
10904 window: &mut Window,
10905 cx: &mut Context<Self>,
10906 ) {
10907 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10908 cx.propagate();
10909 return;
10910 }
10911
10912 if self.move_to_next_snippet_tabstop(window, cx) {
10913 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10914 return;
10915 }
10916 cx.propagate();
10917 }
10918
10919 pub fn previous_snippet_tabstop(
10920 &mut self,
10921 _: &PreviousSnippetTabstop,
10922 window: &mut Window,
10923 cx: &mut Context<Self>,
10924 ) {
10925 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10926 cx.propagate();
10927 return;
10928 }
10929
10930 if self.move_to_prev_snippet_tabstop(window, cx) {
10931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10932 return;
10933 }
10934 cx.propagate();
10935 }
10936
10937 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10938 if self.mode.is_single_line() {
10939 cx.propagate();
10940 return;
10941 }
10942
10943 if self.move_to_next_snippet_tabstop(window, cx) {
10944 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10945 return;
10946 }
10947 if self.read_only(cx) {
10948 return;
10949 }
10950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10951 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10952 let buffer = self.buffer.read(cx);
10953 let snapshot = buffer.snapshot(cx);
10954 let rows_iter = selections.iter().map(|s| s.head().row);
10955 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10956
10957 let has_some_cursor_in_whitespace = selections
10958 .iter()
10959 .filter(|selection| selection.is_empty())
10960 .any(|selection| {
10961 let cursor = selection.head();
10962 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10963 cursor.column < current_indent.len
10964 });
10965
10966 let mut edits = Vec::new();
10967 let mut prev_edited_row = 0;
10968 let mut row_delta = 0;
10969 for selection in &mut selections {
10970 if selection.start.row != prev_edited_row {
10971 row_delta = 0;
10972 }
10973 prev_edited_row = selection.end.row;
10974
10975 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10976 if selection.is_empty() {
10977 let cursor = selection.head();
10978 let settings = buffer.language_settings_at(cursor, cx);
10979 if settings.indent_list_on_tab {
10980 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10981 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10982 row_delta = Self::indent_selection(
10983 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10984 );
10985 continue;
10986 }
10987 }
10988 }
10989 }
10990
10991 // If the selection is non-empty, then increase the indentation of the selected lines.
10992 if !selection.is_empty() {
10993 row_delta =
10994 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10995 continue;
10996 }
10997
10998 let cursor = selection.head();
10999 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
11000 if let Some(suggested_indent) =
11001 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
11002 {
11003 // Don't do anything if already at suggested indent
11004 // and there is any other cursor which is not
11005 if has_some_cursor_in_whitespace
11006 && cursor.column == current_indent.len
11007 && current_indent.len == suggested_indent.len
11008 {
11009 continue;
11010 }
11011
11012 // Adjust line and move cursor to suggested indent
11013 // if cursor is not at suggested indent
11014 if cursor.column < suggested_indent.len
11015 && cursor.column <= current_indent.len
11016 && current_indent.len <= suggested_indent.len
11017 {
11018 selection.start = Point::new(cursor.row, suggested_indent.len);
11019 selection.end = selection.start;
11020 if row_delta == 0 {
11021 edits.extend(Buffer::edit_for_indent_size_adjustment(
11022 cursor.row,
11023 current_indent,
11024 suggested_indent,
11025 ));
11026 row_delta = suggested_indent.len - current_indent.len;
11027 }
11028 continue;
11029 }
11030
11031 // If current indent is more than suggested indent
11032 // only move cursor to current indent and skip indent
11033 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
11034 selection.start = Point::new(cursor.row, current_indent.len);
11035 selection.end = selection.start;
11036 continue;
11037 }
11038 }
11039
11040 // Otherwise, insert a hard or soft tab.
11041 let settings = buffer.language_settings_at(cursor, cx);
11042 let tab_size = if settings.hard_tabs {
11043 IndentSize::tab()
11044 } else {
11045 let tab_size = settings.tab_size.get();
11046 let indent_remainder = snapshot
11047 .text_for_range(Point::new(cursor.row, 0)..cursor)
11048 .flat_map(str::chars)
11049 .fold(row_delta % tab_size, |counter: u32, c| {
11050 if c == '\t' {
11051 0
11052 } else {
11053 (counter + 1) % tab_size
11054 }
11055 });
11056
11057 let chars_to_next_tab_stop = tab_size - indent_remainder;
11058 IndentSize::spaces(chars_to_next_tab_stop)
11059 };
11060 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
11061 selection.end = selection.start;
11062 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
11063 row_delta += tab_size.len;
11064 }
11065
11066 self.transact(window, cx, |this, window, cx| {
11067 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11068 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11069 this.refresh_edit_prediction(true, false, window, cx);
11070 });
11071 }
11072
11073 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
11074 if self.read_only(cx) {
11075 return;
11076 }
11077 if self.mode.is_single_line() {
11078 cx.propagate();
11079 return;
11080 }
11081
11082 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11083 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11084 let mut prev_edited_row = 0;
11085 let mut row_delta = 0;
11086 let mut edits = Vec::new();
11087 let buffer = self.buffer.read(cx);
11088 let snapshot = buffer.snapshot(cx);
11089 for selection in &mut selections {
11090 if selection.start.row != prev_edited_row {
11091 row_delta = 0;
11092 }
11093 prev_edited_row = selection.end.row;
11094
11095 row_delta =
11096 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
11097 }
11098
11099 self.transact(window, cx, |this, window, cx| {
11100 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
11101 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11102 });
11103 }
11104
11105 fn indent_selection(
11106 buffer: &MultiBuffer,
11107 snapshot: &MultiBufferSnapshot,
11108 selection: &mut Selection<Point>,
11109 edits: &mut Vec<(Range<Point>, String)>,
11110 delta_for_start_row: u32,
11111 cx: &App,
11112 ) -> u32 {
11113 let settings = buffer.language_settings_at(selection.start, cx);
11114 let tab_size = settings.tab_size.get();
11115 let indent_kind = if settings.hard_tabs {
11116 IndentKind::Tab
11117 } else {
11118 IndentKind::Space
11119 };
11120 let mut start_row = selection.start.row;
11121 let mut end_row = selection.end.row + 1;
11122
11123 // If a selection ends at the beginning of a line, don't indent
11124 // that last line.
11125 if selection.end.column == 0 && selection.end.row > selection.start.row {
11126 end_row -= 1;
11127 }
11128
11129 // Avoid re-indenting a row that has already been indented by a
11130 // previous selection, but still update this selection's column
11131 // to reflect that indentation.
11132 if delta_for_start_row > 0 {
11133 start_row += 1;
11134 selection.start.column += delta_for_start_row;
11135 if selection.end.row == selection.start.row {
11136 selection.end.column += delta_for_start_row;
11137 }
11138 }
11139
11140 let mut delta_for_end_row = 0;
11141 let has_multiple_rows = start_row + 1 != end_row;
11142 for row in start_row..end_row {
11143 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
11144 let indent_delta = match (current_indent.kind, indent_kind) {
11145 (IndentKind::Space, IndentKind::Space) => {
11146 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
11147 IndentSize::spaces(columns_to_next_tab_stop)
11148 }
11149 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
11150 (_, IndentKind::Tab) => IndentSize::tab(),
11151 };
11152
11153 let start = if has_multiple_rows || current_indent.len < selection.start.column {
11154 0
11155 } else {
11156 selection.start.column
11157 };
11158 let row_start = Point::new(row, start);
11159 edits.push((
11160 row_start..row_start,
11161 indent_delta.chars().collect::<String>(),
11162 ));
11163
11164 // Update this selection's endpoints to reflect the indentation.
11165 if row == selection.start.row {
11166 selection.start.column += indent_delta.len;
11167 }
11168 if row == selection.end.row {
11169 selection.end.column += indent_delta.len;
11170 delta_for_end_row = indent_delta.len;
11171 }
11172 }
11173
11174 if selection.start.row == selection.end.row {
11175 delta_for_start_row + delta_for_end_row
11176 } else {
11177 delta_for_end_row
11178 }
11179 }
11180
11181 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
11182 if self.read_only(cx) {
11183 return;
11184 }
11185 if self.mode.is_single_line() {
11186 cx.propagate();
11187 return;
11188 }
11189
11190 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11191 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11192 let selections = self.selections.all::<Point>(&display_map);
11193 let mut deletion_ranges = Vec::new();
11194 let mut last_outdent = None;
11195 {
11196 let buffer = self.buffer.read(cx);
11197 let snapshot = buffer.snapshot(cx);
11198 for selection in &selections {
11199 let settings = buffer.language_settings_at(selection.start, cx);
11200 let tab_size = settings.tab_size.get();
11201 let mut rows = selection.spanned_rows(false, &display_map);
11202
11203 // Avoid re-outdenting a row that has already been outdented by a
11204 // previous selection.
11205 if let Some(last_row) = last_outdent
11206 && last_row == rows.start
11207 {
11208 rows.start = rows.start.next_row();
11209 }
11210 let has_multiple_rows = rows.len() > 1;
11211 for row in rows.iter_rows() {
11212 let indent_size = snapshot.indent_size_for_line(row);
11213 if indent_size.len > 0 {
11214 let deletion_len = match indent_size.kind {
11215 IndentKind::Space => {
11216 let columns_to_prev_tab_stop = indent_size.len % tab_size;
11217 if columns_to_prev_tab_stop == 0 {
11218 tab_size
11219 } else {
11220 columns_to_prev_tab_stop
11221 }
11222 }
11223 IndentKind::Tab => 1,
11224 };
11225 let start = if has_multiple_rows
11226 || deletion_len > selection.start.column
11227 || indent_size.len < selection.start.column
11228 {
11229 0
11230 } else {
11231 selection.start.column - deletion_len
11232 };
11233 deletion_ranges.push(
11234 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
11235 );
11236 last_outdent = Some(row);
11237 }
11238 }
11239 }
11240 }
11241
11242 self.transact(window, cx, |this, window, cx| {
11243 this.buffer.update(cx, |buffer, cx| {
11244 let empty_str: Arc<str> = Arc::default();
11245 buffer.edit(
11246 deletion_ranges
11247 .into_iter()
11248 .map(|range| (range, empty_str.clone())),
11249 None,
11250 cx,
11251 );
11252 });
11253 let selections = this
11254 .selections
11255 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11256 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11257 });
11258 }
11259
11260 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
11261 if self.read_only(cx) {
11262 return;
11263 }
11264 if self.mode.is_single_line() {
11265 cx.propagate();
11266 return;
11267 }
11268
11269 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11270 let selections = self
11271 .selections
11272 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11273 .into_iter()
11274 .map(|s| s.range());
11275
11276 self.transact(window, cx, |this, window, cx| {
11277 this.buffer.update(cx, |buffer, cx| {
11278 buffer.autoindent_ranges(selections, cx);
11279 });
11280 let selections = this
11281 .selections
11282 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
11283 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
11284 });
11285 }
11286
11287 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
11288 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11289 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11290 let selections = self.selections.all::<Point>(&display_map);
11291
11292 let mut new_cursors = Vec::new();
11293 let mut edit_ranges = Vec::new();
11294 let mut selections = selections.iter().peekable();
11295 while let Some(selection) = selections.next() {
11296 let mut rows = selection.spanned_rows(false, &display_map);
11297
11298 // Accumulate contiguous regions of rows that we want to delete.
11299 while let Some(next_selection) = selections.peek() {
11300 let next_rows = next_selection.spanned_rows(false, &display_map);
11301 if next_rows.start <= rows.end {
11302 rows.end = next_rows.end;
11303 selections.next().unwrap();
11304 } else {
11305 break;
11306 }
11307 }
11308
11309 let buffer = display_map.buffer_snapshot();
11310 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
11311 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
11312 // If there's a line after the range, delete the \n from the end of the row range
11313 (
11314 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
11315 rows.end,
11316 )
11317 } else {
11318 // If there isn't a line after the range, delete the \n from the line before the
11319 // start of the row range
11320 edit_start = edit_start.saturating_sub_usize(1);
11321 (buffer.len(), rows.start.previous_row())
11322 };
11323
11324 let text_layout_details = self.text_layout_details(window, cx);
11325 let x = display_map.x_for_display_point(
11326 selection.head().to_display_point(&display_map),
11327 &text_layout_details,
11328 );
11329 let row = Point::new(target_row.0, 0)
11330 .to_display_point(&display_map)
11331 .row();
11332 let column = display_map.display_column_for_x(row, x, &text_layout_details);
11333
11334 new_cursors.push((
11335 selection.id,
11336 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
11337 SelectionGoal::None,
11338 ));
11339 edit_ranges.push(edit_start..edit_end);
11340 }
11341
11342 self.transact(window, cx, |this, window, cx| {
11343 let buffer = this.buffer.update(cx, |buffer, cx| {
11344 let empty_str: Arc<str> = Arc::default();
11345 buffer.edit(
11346 edit_ranges
11347 .into_iter()
11348 .map(|range| (range, empty_str.clone())),
11349 None,
11350 cx,
11351 );
11352 buffer.snapshot(cx)
11353 });
11354 let new_selections = new_cursors
11355 .into_iter()
11356 .map(|(id, cursor, goal)| {
11357 let cursor = cursor.to_point(&buffer);
11358 Selection {
11359 id,
11360 start: cursor,
11361 end: cursor,
11362 reversed: false,
11363 goal,
11364 }
11365 })
11366 .collect();
11367
11368 this.change_selections(Default::default(), window, cx, |s| {
11369 s.select(new_selections);
11370 });
11371 });
11372 }
11373
11374 pub fn join_lines_impl(
11375 &mut self,
11376 insert_whitespace: bool,
11377 window: &mut Window,
11378 cx: &mut Context<Self>,
11379 ) {
11380 if self.read_only(cx) {
11381 return;
11382 }
11383 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
11384 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
11385 let start = MultiBufferRow(selection.start.row);
11386 // Treat single line selections as if they include the next line. Otherwise this action
11387 // would do nothing for single line selections individual cursors.
11388 let end = if selection.start.row == selection.end.row {
11389 MultiBufferRow(selection.start.row + 1)
11390 } else if selection.end.column == 0 {
11391 // If the selection ends at the start of a line, it's logically at the end of the
11392 // previous line (plus its newline).
11393 // Don't include the end line unless there's only one line selected.
11394 if selection.start.row + 1 == selection.end.row {
11395 MultiBufferRow(selection.end.row)
11396 } else {
11397 MultiBufferRow(selection.end.row - 1)
11398 }
11399 } else {
11400 MultiBufferRow(selection.end.row)
11401 };
11402
11403 if let Some(last_row_range) = row_ranges.last_mut()
11404 && start <= last_row_range.end
11405 {
11406 last_row_range.end = end;
11407 continue;
11408 }
11409 row_ranges.push(start..end);
11410 }
11411
11412 let snapshot = self.buffer.read(cx).snapshot(cx);
11413 let mut cursor_positions = Vec::new();
11414 for row_range in &row_ranges {
11415 let anchor = snapshot.anchor_before(Point::new(
11416 row_range.end.previous_row().0,
11417 snapshot.line_len(row_range.end.previous_row()),
11418 ));
11419 cursor_positions.push(anchor..anchor);
11420 }
11421
11422 self.transact(window, cx, |this, window, cx| {
11423 for row_range in row_ranges.into_iter().rev() {
11424 for row in row_range.iter_rows().rev() {
11425 let end_of_line = Point::new(row.0, snapshot.line_len(row));
11426 let next_line_row = row.next_row();
11427 let indent = snapshot.indent_size_for_line(next_line_row);
11428 let mut join_start_column = indent.len;
11429
11430 if let Some(language_scope) =
11431 snapshot.language_scope_at(Point::new(next_line_row.0, indent.len))
11432 {
11433 let line_end =
11434 Point::new(next_line_row.0, snapshot.line_len(next_line_row));
11435 let line_text_after_indent = snapshot
11436 .text_for_range(Point::new(next_line_row.0, indent.len)..line_end)
11437 .collect::<String>();
11438
11439 if !line_text_after_indent.is_empty() {
11440 let block_prefix = language_scope
11441 .block_comment()
11442 .map(|c| c.prefix.as_ref())
11443 .filter(|p| !p.is_empty());
11444 let doc_prefix = language_scope
11445 .documentation_comment()
11446 .map(|c| c.prefix.as_ref())
11447 .filter(|p| !p.is_empty());
11448 let all_prefixes = language_scope
11449 .line_comment_prefixes()
11450 .iter()
11451 .map(|p| p.as_ref())
11452 .chain(block_prefix)
11453 .chain(doc_prefix)
11454 .chain(language_scope.unordered_list().iter().map(|p| p.as_ref()));
11455
11456 let mut longest_prefix_len = None;
11457 for prefix in all_prefixes {
11458 let trimmed = prefix.trim_end();
11459 if line_text_after_indent.starts_with(trimmed) {
11460 let candidate_len =
11461 if line_text_after_indent.starts_with(prefix) {
11462 prefix.len()
11463 } else {
11464 trimmed.len()
11465 };
11466 if longest_prefix_len.map_or(true, |len| candidate_len > len) {
11467 longest_prefix_len = Some(candidate_len);
11468 }
11469 }
11470 }
11471
11472 if let Some(prefix_len) = longest_prefix_len {
11473 join_start_column =
11474 join_start_column.saturating_add(prefix_len as u32);
11475 }
11476 }
11477 }
11478
11479 let start_of_next_line = Point::new(next_line_row.0, join_start_column);
11480
11481 let replace = if snapshot.line_len(next_line_row) > join_start_column
11482 && insert_whitespace
11483 {
11484 " "
11485 } else {
11486 ""
11487 };
11488
11489 this.buffer.update(cx, |buffer, cx| {
11490 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11491 });
11492 }
11493 }
11494
11495 this.change_selections(Default::default(), window, cx, |s| {
11496 s.select_anchor_ranges(cursor_positions)
11497 });
11498 });
11499 }
11500
11501 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11502 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11503 self.join_lines_impl(true, window, cx);
11504 }
11505
11506 pub fn sort_lines_case_sensitive(
11507 &mut self,
11508 _: &SortLinesCaseSensitive,
11509 window: &mut Window,
11510 cx: &mut Context<Self>,
11511 ) {
11512 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11513 }
11514
11515 pub fn sort_lines_by_length(
11516 &mut self,
11517 _: &SortLinesByLength,
11518 window: &mut Window,
11519 cx: &mut Context<Self>,
11520 ) {
11521 self.manipulate_immutable_lines(window, cx, |lines| {
11522 lines.sort_by_key(|&line| line.chars().count())
11523 })
11524 }
11525
11526 pub fn sort_lines_case_insensitive(
11527 &mut self,
11528 _: &SortLinesCaseInsensitive,
11529 window: &mut Window,
11530 cx: &mut Context<Self>,
11531 ) {
11532 self.manipulate_immutable_lines(window, cx, |lines| {
11533 lines.sort_by_key(|line| line.to_lowercase())
11534 })
11535 }
11536
11537 pub fn unique_lines_case_insensitive(
11538 &mut self,
11539 _: &UniqueLinesCaseInsensitive,
11540 window: &mut Window,
11541 cx: &mut Context<Self>,
11542 ) {
11543 self.manipulate_immutable_lines(window, cx, |lines| {
11544 let mut seen = HashSet::default();
11545 lines.retain(|line| seen.insert(line.to_lowercase()));
11546 })
11547 }
11548
11549 pub fn unique_lines_case_sensitive(
11550 &mut self,
11551 _: &UniqueLinesCaseSensitive,
11552 window: &mut Window,
11553 cx: &mut Context<Self>,
11554 ) {
11555 self.manipulate_immutable_lines(window, cx, |lines| {
11556 let mut seen = HashSet::default();
11557 lines.retain(|line| seen.insert(*line));
11558 })
11559 }
11560
11561 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11562 let snapshot = self.buffer.read(cx).snapshot(cx);
11563 for selection in self.selections.disjoint_anchors_arc().iter() {
11564 if snapshot
11565 .language_at(selection.start)
11566 .and_then(|lang| lang.config().wrap_characters.as_ref())
11567 .is_some()
11568 {
11569 return true;
11570 }
11571 }
11572 false
11573 }
11574
11575 fn wrap_selections_in_tag(
11576 &mut self,
11577 _: &WrapSelectionsInTag,
11578 window: &mut Window,
11579 cx: &mut Context<Self>,
11580 ) {
11581 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11582
11583 let snapshot = self.buffer.read(cx).snapshot(cx);
11584
11585 let mut edits = Vec::new();
11586 let mut boundaries = Vec::new();
11587
11588 for selection in self
11589 .selections
11590 .all_adjusted(&self.display_snapshot(cx))
11591 .iter()
11592 {
11593 let Some(wrap_config) = snapshot
11594 .language_at(selection.start)
11595 .and_then(|lang| lang.config().wrap_characters.clone())
11596 else {
11597 continue;
11598 };
11599
11600 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11601 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11602
11603 let start_before = snapshot.anchor_before(selection.start);
11604 let end_after = snapshot.anchor_after(selection.end);
11605
11606 edits.push((start_before..start_before, open_tag));
11607 edits.push((end_after..end_after, close_tag));
11608
11609 boundaries.push((
11610 start_before,
11611 end_after,
11612 wrap_config.start_prefix.len(),
11613 wrap_config.end_suffix.len(),
11614 ));
11615 }
11616
11617 if edits.is_empty() {
11618 return;
11619 }
11620
11621 self.transact(window, cx, |this, window, cx| {
11622 let buffer = this.buffer.update(cx, |buffer, cx| {
11623 buffer.edit(edits, None, cx);
11624 buffer.snapshot(cx)
11625 });
11626
11627 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11628 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11629 boundaries.into_iter()
11630 {
11631 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11632 let close_offset = end_after
11633 .to_offset(&buffer)
11634 .saturating_sub_usize(end_suffix_len);
11635 new_selections.push(open_offset..open_offset);
11636 new_selections.push(close_offset..close_offset);
11637 }
11638
11639 this.change_selections(Default::default(), window, cx, |s| {
11640 s.select_ranges(new_selections);
11641 });
11642
11643 this.request_autoscroll(Autoscroll::fit(), cx);
11644 });
11645 }
11646
11647 pub fn toggle_read_only(
11648 &mut self,
11649 _: &workspace::ToggleReadOnlyFile,
11650 _: &mut Window,
11651 cx: &mut Context<Self>,
11652 ) {
11653 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11654 buffer.update(cx, |buffer, cx| {
11655 buffer.set_capability(
11656 match buffer.capability() {
11657 Capability::ReadWrite => Capability::Read,
11658 Capability::Read => Capability::ReadWrite,
11659 Capability::ReadOnly => Capability::ReadOnly,
11660 },
11661 cx,
11662 );
11663 })
11664 }
11665 }
11666
11667 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11668 let Some(project) = self.project.clone() else {
11669 return;
11670 };
11671 let task = self.reload(project, window, cx);
11672 self.detach_and_notify_err(task, window, cx);
11673 }
11674
11675 pub fn restore_file(
11676 &mut self,
11677 _: &::git::RestoreFile,
11678 window: &mut Window,
11679 cx: &mut Context<Self>,
11680 ) {
11681 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11682 let mut buffer_ids = HashSet::default();
11683 let snapshot = self.buffer().read(cx).snapshot(cx);
11684 for selection in self
11685 .selections
11686 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11687 {
11688 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11689 }
11690
11691 let buffer = self.buffer().read(cx);
11692 let ranges = buffer_ids
11693 .into_iter()
11694 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11695 .collect::<Vec<_>>();
11696
11697 self.restore_hunks_in_ranges(ranges, window, cx);
11698 }
11699
11700 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11701 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11702 let selections = self
11703 .selections
11704 .all(&self.display_snapshot(cx))
11705 .into_iter()
11706 .map(|s| s.range())
11707 .collect();
11708 self.restore_hunks_in_ranges(selections, window, cx);
11709 }
11710
11711 /// Restores the diff hunks in the editor's selections and moves the cursor
11712 /// to the next diff hunk. Wraps around to the beginning of the buffer if
11713 /// not all diff hunks are expanded.
11714 pub fn restore_and_next(
11715 &mut self,
11716 _: &::git::RestoreAndNext,
11717 window: &mut Window,
11718 cx: &mut Context<Self>,
11719 ) {
11720 let selections = self
11721 .selections
11722 .all(&self.display_snapshot(cx))
11723 .into_iter()
11724 .map(|selection| selection.range())
11725 .collect();
11726
11727 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11728 self.restore_hunks_in_ranges(selections, window, cx);
11729
11730 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
11731 let wrap_around = !all_diff_hunks_expanded;
11732 let snapshot = self.snapshot(window, cx);
11733 let position = self
11734 .selections
11735 .newest::<Point>(&snapshot.display_snapshot)
11736 .head();
11737
11738 self.go_to_hunk_before_or_after_position(
11739 &snapshot,
11740 position,
11741 Direction::Next,
11742 wrap_around,
11743 window,
11744 cx,
11745 );
11746 }
11747
11748 pub fn restore_hunks_in_ranges(
11749 &mut self,
11750 ranges: Vec<Range<Point>>,
11751 window: &mut Window,
11752 cx: &mut Context<Editor>,
11753 ) {
11754 if self.delegate_stage_and_restore {
11755 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11756 if !hunks.is_empty() {
11757 cx.emit(EditorEvent::RestoreRequested { hunks });
11758 }
11759 return;
11760 }
11761 let hunks = self.snapshot(window, cx).hunks_for_ranges(ranges);
11762 self.transact(window, cx, |editor, window, cx| {
11763 editor.restore_diff_hunks(hunks, cx);
11764 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
11765 selections.refresh()
11766 });
11767 });
11768 }
11769
11770 pub(crate) fn restore_diff_hunks(&self, hunks: Vec<MultiBufferDiffHunk>, cx: &mut App) {
11771 let mut revert_changes = HashMap::default();
11772 let chunk_by = hunks.into_iter().chunk_by(|hunk| hunk.buffer_id);
11773 for (buffer_id, hunks) in &chunk_by {
11774 let hunks = hunks.collect::<Vec<_>>();
11775 for hunk in &hunks {
11776 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11777 }
11778 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11779 }
11780 if !revert_changes.is_empty() {
11781 self.buffer().update(cx, |multi_buffer, cx| {
11782 for (buffer_id, changes) in revert_changes {
11783 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
11784 buffer.update(cx, |buffer, cx| {
11785 buffer.edit(
11786 changes
11787 .into_iter()
11788 .map(|(range, text)| (range, text.to_string())),
11789 None,
11790 cx,
11791 );
11792 });
11793 }
11794 }
11795 });
11796 }
11797 }
11798
11799 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11800 if let Some(status) = self
11801 .addons
11802 .iter()
11803 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11804 {
11805 return Some(status);
11806 }
11807 self.project
11808 .as_ref()?
11809 .read(cx)
11810 .status_for_buffer_id(buffer_id, cx)
11811 }
11812
11813 pub fn open_active_item_in_terminal(
11814 &mut self,
11815 _: &OpenInTerminal,
11816 window: &mut Window,
11817 cx: &mut Context<Self>,
11818 ) {
11819 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11820 let project_path = buffer.read(cx).project_path(cx)?;
11821 let project = self.project()?.read(cx);
11822 let entry = project.entry_for_path(&project_path, cx)?;
11823 let parent = match &entry.canonical_path {
11824 Some(canonical_path) => canonical_path.to_path_buf(),
11825 None => project.absolute_path(&project_path, cx)?,
11826 }
11827 .parent()?
11828 .to_path_buf();
11829 Some(parent)
11830 }) {
11831 window.dispatch_action(
11832 OpenTerminal {
11833 working_directory,
11834 local: false,
11835 }
11836 .boxed_clone(),
11837 cx,
11838 );
11839 }
11840 }
11841
11842 fn set_breakpoint_context_menu(
11843 &mut self,
11844 display_row: DisplayRow,
11845 position: Option<Anchor>,
11846 clicked_point: gpui::Point<Pixels>,
11847 window: &mut Window,
11848 cx: &mut Context<Self>,
11849 ) {
11850 let source = self
11851 .buffer
11852 .read(cx)
11853 .snapshot(cx)
11854 .anchor_before(Point::new(display_row.0, 0u32));
11855
11856 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11857
11858 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11859 self,
11860 source,
11861 clicked_point,
11862 context_menu,
11863 window,
11864 cx,
11865 );
11866 }
11867
11868 fn add_edit_breakpoint_block(
11869 &mut self,
11870 anchor: Anchor,
11871 breakpoint: &Breakpoint,
11872 edit_action: BreakpointPromptEditAction,
11873 window: &mut Window,
11874 cx: &mut Context<Self>,
11875 ) {
11876 let weak_editor = cx.weak_entity();
11877 let bp_prompt = cx.new(|cx| {
11878 BreakpointPromptEditor::new(
11879 weak_editor,
11880 anchor,
11881 breakpoint.clone(),
11882 edit_action,
11883 window,
11884 cx,
11885 )
11886 });
11887
11888 let height = bp_prompt.update(cx, |this, cx| {
11889 this.prompt
11890 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11891 });
11892 let cloned_prompt = bp_prompt.clone();
11893 let blocks = vec![BlockProperties {
11894 style: BlockStyle::Sticky,
11895 placement: BlockPlacement::Above(anchor),
11896 height: Some(height),
11897 render: Arc::new(move |cx| {
11898 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11899 cloned_prompt.clone().into_any_element()
11900 }),
11901 priority: 0,
11902 }];
11903
11904 let focus_handle = bp_prompt.focus_handle(cx);
11905 window.focus(&focus_handle, cx);
11906
11907 let block_ids = self.insert_blocks(blocks, None, cx);
11908 bp_prompt.update(cx, |prompt, _| {
11909 prompt.add_block_ids(block_ids);
11910 });
11911 }
11912
11913 pub(crate) fn breakpoint_at_row(
11914 &self,
11915 row: u32,
11916 window: &mut Window,
11917 cx: &mut Context<Self>,
11918 ) -> Option<(Anchor, Breakpoint)> {
11919 let snapshot = self.snapshot(window, cx);
11920 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11921
11922 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11923 }
11924
11925 pub(crate) fn breakpoint_at_anchor(
11926 &self,
11927 breakpoint_position: Anchor,
11928 snapshot: &EditorSnapshot,
11929 cx: &mut Context<Self>,
11930 ) -> Option<(Anchor, Breakpoint)> {
11931 let buffer = self
11932 .buffer
11933 .read(cx)
11934 .buffer_for_anchor(breakpoint_position, cx)?;
11935
11936 let enclosing_excerpt = breakpoint_position.excerpt_id;
11937 let buffer_snapshot = buffer.read(cx).snapshot();
11938
11939 let row = buffer_snapshot
11940 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11941 .row;
11942
11943 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11944 let anchor_end = snapshot
11945 .buffer_snapshot()
11946 .anchor_after(Point::new(row, line_len));
11947
11948 self.breakpoint_store
11949 .as_ref()?
11950 .read_with(cx, |breakpoint_store, cx| {
11951 breakpoint_store
11952 .breakpoints(
11953 &buffer,
11954 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11955 &buffer_snapshot,
11956 cx,
11957 )
11958 .next()
11959 .and_then(|(bp, _)| {
11960 let breakpoint_row = buffer_snapshot
11961 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11962 .row;
11963
11964 if breakpoint_row == row {
11965 snapshot
11966 .buffer_snapshot()
11967 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11968 .map(|position| (position, bp.bp.clone()))
11969 } else {
11970 None
11971 }
11972 })
11973 })
11974 }
11975
11976 pub fn edit_log_breakpoint(
11977 &mut self,
11978 _: &EditLogBreakpoint,
11979 window: &mut Window,
11980 cx: &mut Context<Self>,
11981 ) {
11982 if self.breakpoint_store.is_none() {
11983 return;
11984 }
11985
11986 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11987 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11988 message: None,
11989 state: BreakpointState::Enabled,
11990 condition: None,
11991 hit_condition: None,
11992 });
11993
11994 self.add_edit_breakpoint_block(
11995 anchor,
11996 &breakpoint,
11997 BreakpointPromptEditAction::Log,
11998 window,
11999 cx,
12000 );
12001 }
12002 }
12003
12004 fn breakpoints_at_cursors(
12005 &self,
12006 window: &mut Window,
12007 cx: &mut Context<Self>,
12008 ) -> Vec<(Anchor, Option<Breakpoint>)> {
12009 let snapshot = self.snapshot(window, cx);
12010 let cursors = self
12011 .selections
12012 .disjoint_anchors_arc()
12013 .iter()
12014 .map(|selection| {
12015 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
12016
12017 let breakpoint_position = self
12018 .breakpoint_at_row(cursor_position.row, window, cx)
12019 .map(|bp| bp.0)
12020 .unwrap_or_else(|| {
12021 snapshot
12022 .display_snapshot
12023 .buffer_snapshot()
12024 .anchor_after(Point::new(cursor_position.row, 0))
12025 });
12026
12027 let breakpoint = self
12028 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
12029 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
12030
12031 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
12032 })
12033 // 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.
12034 .collect::<HashMap<Anchor, _>>();
12035
12036 cursors.into_iter().collect()
12037 }
12038
12039 pub fn enable_breakpoint(
12040 &mut self,
12041 _: &crate::actions::EnableBreakpoint,
12042 window: &mut Window,
12043 cx: &mut Context<Self>,
12044 ) {
12045 if self.breakpoint_store.is_none() {
12046 return;
12047 }
12048
12049 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12050 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
12051 continue;
12052 };
12053 self.edit_breakpoint_at_anchor(
12054 anchor,
12055 breakpoint,
12056 BreakpointEditAction::InvertState,
12057 cx,
12058 );
12059 }
12060 }
12061
12062 pub fn align_selections(
12063 &mut self,
12064 _: &crate::actions::AlignSelections,
12065 window: &mut Window,
12066 cx: &mut Context<Self>,
12067 ) {
12068 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12069
12070 let display_snapshot = self.display_snapshot(cx);
12071
12072 struct CursorData {
12073 anchor: Anchor,
12074 point: Point,
12075 }
12076 let cursor_data: Vec<CursorData> = self
12077 .selections
12078 .disjoint_anchors()
12079 .iter()
12080 .map(|selection| {
12081 let anchor = if selection.reversed {
12082 selection.head()
12083 } else {
12084 selection.tail()
12085 };
12086 CursorData {
12087 anchor: anchor,
12088 point: anchor.to_point(&display_snapshot.buffer_snapshot()),
12089 }
12090 })
12091 .collect();
12092
12093 let rows_anchors_count: Vec<usize> = cursor_data
12094 .iter()
12095 .map(|cursor| cursor.point.row)
12096 .chunk_by(|&row| row)
12097 .into_iter()
12098 .map(|(_, group)| group.count())
12099 .collect();
12100 let max_columns = rows_anchors_count.iter().max().copied().unwrap_or(0);
12101 let mut rows_column_offset = vec![0; rows_anchors_count.len()];
12102 let mut edits = Vec::new();
12103
12104 for column_idx in 0..max_columns {
12105 let mut cursor_index = 0;
12106
12107 // Calculate target_column => position that the selections will go
12108 let mut target_column = 0;
12109 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12110 // Skip rows that don't have this column
12111 if column_idx >= *cursor_count {
12112 cursor_index += cursor_count;
12113 continue;
12114 }
12115
12116 let point = &cursor_data[cursor_index + column_idx].point;
12117 let adjusted_column = point.column + rows_column_offset[row_idx];
12118 if adjusted_column > target_column {
12119 target_column = adjusted_column;
12120 }
12121 cursor_index += cursor_count;
12122 }
12123
12124 // Collect edits for this column
12125 cursor_index = 0;
12126 for (row_idx, cursor_count) in rows_anchors_count.iter().enumerate() {
12127 // Skip rows that don't have this column
12128 if column_idx >= *cursor_count {
12129 cursor_index += *cursor_count;
12130 continue;
12131 }
12132
12133 let point = &cursor_data[cursor_index + column_idx].point;
12134 let spaces_needed = target_column - point.column - rows_column_offset[row_idx];
12135 if spaces_needed > 0 {
12136 let anchor = cursor_data[cursor_index + column_idx]
12137 .anchor
12138 .bias_left(&display_snapshot);
12139 edits.push((anchor..anchor, " ".repeat(spaces_needed as usize)));
12140 }
12141 rows_column_offset[row_idx] += spaces_needed;
12142
12143 cursor_index += *cursor_count;
12144 }
12145 }
12146
12147 if !edits.is_empty() {
12148 self.transact(window, cx, |editor, _window, cx| {
12149 editor.edit(edits, cx);
12150 });
12151 }
12152 }
12153
12154 pub fn disable_breakpoint(
12155 &mut self,
12156 _: &crate::actions::DisableBreakpoint,
12157 window: &mut Window,
12158 cx: &mut Context<Self>,
12159 ) {
12160 if self.breakpoint_store.is_none() {
12161 return;
12162 }
12163
12164 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12165 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
12166 continue;
12167 };
12168 self.edit_breakpoint_at_anchor(
12169 anchor,
12170 breakpoint,
12171 BreakpointEditAction::InvertState,
12172 cx,
12173 );
12174 }
12175 }
12176
12177 pub fn toggle_breakpoint(
12178 &mut self,
12179 _: &crate::actions::ToggleBreakpoint,
12180 window: &mut Window,
12181 cx: &mut Context<Self>,
12182 ) {
12183 if self.breakpoint_store.is_none() {
12184 return;
12185 }
12186
12187 let snapshot = self.snapshot(window, cx);
12188 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
12189 if self.gutter_breakpoint_indicator.0.is_some() {
12190 let display_row = anchor
12191 .to_point(snapshot.buffer_snapshot())
12192 .to_display_point(&snapshot.display_snapshot)
12193 .row();
12194 self.update_breakpoint_collision_on_toggle(
12195 display_row,
12196 &BreakpointEditAction::Toggle,
12197 );
12198 }
12199
12200 if let Some(breakpoint) = breakpoint {
12201 self.edit_breakpoint_at_anchor(
12202 anchor,
12203 breakpoint,
12204 BreakpointEditAction::Toggle,
12205 cx,
12206 );
12207 } else {
12208 self.edit_breakpoint_at_anchor(
12209 anchor,
12210 Breakpoint::new_standard(),
12211 BreakpointEditAction::Toggle,
12212 cx,
12213 );
12214 }
12215 }
12216 }
12217
12218 fn update_breakpoint_collision_on_toggle(
12219 &mut self,
12220 display_row: DisplayRow,
12221 edit_action: &BreakpointEditAction,
12222 ) {
12223 if let Some(ref mut breakpoint_indicator) = self.gutter_breakpoint_indicator.0 {
12224 if breakpoint_indicator.display_row == display_row
12225 && matches!(edit_action, BreakpointEditAction::Toggle)
12226 {
12227 breakpoint_indicator.collides_with_existing_breakpoint =
12228 !breakpoint_indicator.collides_with_existing_breakpoint;
12229 }
12230 }
12231 }
12232
12233 pub fn edit_breakpoint_at_anchor(
12234 &mut self,
12235 breakpoint_position: Anchor,
12236 breakpoint: Breakpoint,
12237 edit_action: BreakpointEditAction,
12238 cx: &mut Context<Self>,
12239 ) {
12240 let Some(breakpoint_store) = &self.breakpoint_store else {
12241 return;
12242 };
12243
12244 let Some(buffer) = self
12245 .buffer
12246 .read(cx)
12247 .buffer_for_anchor(breakpoint_position, cx)
12248 else {
12249 return;
12250 };
12251
12252 breakpoint_store.update(cx, |breakpoint_store, cx| {
12253 breakpoint_store.toggle_breakpoint(
12254 buffer,
12255 BreakpointWithPosition {
12256 position: breakpoint_position.text_anchor,
12257 bp: breakpoint,
12258 },
12259 edit_action,
12260 cx,
12261 );
12262 });
12263
12264 cx.notify();
12265 }
12266
12267 #[cfg(any(test, feature = "test-support"))]
12268 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
12269 self.breakpoint_store.clone()
12270 }
12271
12272 pub fn prepare_restore_change(
12273 &self,
12274 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
12275 hunk: &MultiBufferDiffHunk,
12276 cx: &mut App,
12277 ) -> Option<()> {
12278 if hunk.is_created_file() {
12279 return None;
12280 }
12281 let buffer = self.buffer.read(cx);
12282 let diff = buffer.diff_for(hunk.buffer_id)?;
12283 let buffer = buffer.buffer(hunk.buffer_id)?;
12284 let buffer = buffer.read(cx);
12285 let original_text = diff
12286 .read(cx)
12287 .base_text(cx)
12288 .as_rope()
12289 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
12290 let buffer_snapshot = buffer.snapshot();
12291 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
12292 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
12293 probe
12294 .0
12295 .start
12296 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
12297 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
12298 }) {
12299 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
12300 Some(())
12301 } else {
12302 None
12303 }
12304 }
12305
12306 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
12307 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
12308 }
12309
12310 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
12311 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
12312 }
12313
12314 pub fn rotate_selections_forward(
12315 &mut self,
12316 _: &RotateSelectionsForward,
12317 window: &mut Window,
12318 cx: &mut Context<Self>,
12319 ) {
12320 self.rotate_selections(window, cx, false)
12321 }
12322
12323 pub fn rotate_selections_backward(
12324 &mut self,
12325 _: &RotateSelectionsBackward,
12326 window: &mut Window,
12327 cx: &mut Context<Self>,
12328 ) {
12329 self.rotate_selections(window, cx, true)
12330 }
12331
12332 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
12333 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12334 let display_snapshot = self.display_snapshot(cx);
12335 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
12336
12337 if selections.len() < 2 {
12338 return;
12339 }
12340
12341 let (edits, new_selections) = {
12342 let buffer = self.buffer.read(cx).read(cx);
12343 let has_selections = selections.iter().any(|s| !s.is_empty());
12344 if has_selections {
12345 let mut selected_texts: Vec<String> = selections
12346 .iter()
12347 .map(|selection| {
12348 buffer
12349 .text_for_range(selection.start..selection.end)
12350 .collect()
12351 })
12352 .collect();
12353
12354 if reverse {
12355 selected_texts.rotate_left(1);
12356 } else {
12357 selected_texts.rotate_right(1);
12358 }
12359
12360 let mut offset_delta: i64 = 0;
12361 let mut new_selections = Vec::new();
12362 let edits: Vec<_> = selections
12363 .iter()
12364 .zip(selected_texts.iter())
12365 .map(|(selection, new_text)| {
12366 let old_len = (selection.end.0 - selection.start.0) as i64;
12367 let new_len = new_text.len() as i64;
12368 let adjusted_start =
12369 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
12370 let adjusted_end =
12371 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
12372
12373 new_selections.push(Selection {
12374 id: selection.id,
12375 start: adjusted_start,
12376 end: adjusted_end,
12377 reversed: selection.reversed,
12378 goal: selection.goal,
12379 });
12380
12381 offset_delta += new_len - old_len;
12382 (selection.start..selection.end, new_text.clone())
12383 })
12384 .collect();
12385 (edits, new_selections)
12386 } else {
12387 let mut all_rows: Vec<u32> = selections
12388 .iter()
12389 .map(|selection| buffer.offset_to_point(selection.start).row)
12390 .collect();
12391 all_rows.sort_unstable();
12392 all_rows.dedup();
12393
12394 if all_rows.len() < 2 {
12395 return;
12396 }
12397
12398 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
12399 .iter()
12400 .map(|&row| {
12401 let start = Point::new(row, 0);
12402 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12403 buffer.point_to_offset(start)..buffer.point_to_offset(end)
12404 })
12405 .collect();
12406
12407 let mut line_texts: Vec<String> = line_ranges
12408 .iter()
12409 .map(|range| buffer.text_for_range(range.clone()).collect())
12410 .collect();
12411
12412 if reverse {
12413 line_texts.rotate_left(1);
12414 } else {
12415 line_texts.rotate_right(1);
12416 }
12417
12418 let edits = line_ranges
12419 .iter()
12420 .zip(line_texts.iter())
12421 .map(|(range, new_text)| (range.clone(), new_text.clone()))
12422 .collect();
12423
12424 let num_rows = all_rows.len();
12425 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
12426 .iter()
12427 .enumerate()
12428 .map(|(i, &row)| (row, i))
12429 .collect();
12430
12431 // Compute new line start offsets after rotation (handles CRLF)
12432 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
12433 let first_line_start = line_ranges[0].start.0;
12434 let mut new_line_starts: Vec<usize> = vec![first_line_start];
12435 for text in line_texts.iter().take(num_rows - 1) {
12436 let prev_start = *new_line_starts.last().unwrap();
12437 new_line_starts.push(prev_start + text.len() + newline_len);
12438 }
12439
12440 let new_selections = selections
12441 .iter()
12442 .map(|selection| {
12443 let point = buffer.offset_to_point(selection.start);
12444 let old_index = row_to_index[&point.row];
12445 let new_index = if reverse {
12446 (old_index + num_rows - 1) % num_rows
12447 } else {
12448 (old_index + 1) % num_rows
12449 };
12450 let new_offset =
12451 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
12452 Selection {
12453 id: selection.id,
12454 start: new_offset,
12455 end: new_offset,
12456 reversed: selection.reversed,
12457 goal: selection.goal,
12458 }
12459 })
12460 .collect();
12461
12462 (edits, new_selections)
12463 }
12464 };
12465
12466 self.transact(window, cx, |this, window, cx| {
12467 this.buffer.update(cx, |buffer, cx| {
12468 buffer.edit(edits, None, cx);
12469 });
12470 this.change_selections(Default::default(), window, cx, |s| {
12471 s.select(new_selections);
12472 });
12473 });
12474 }
12475
12476 fn manipulate_lines<M>(
12477 &mut self,
12478 window: &mut Window,
12479 cx: &mut Context<Self>,
12480 mut manipulate: M,
12481 ) where
12482 M: FnMut(&str) -> LineManipulationResult,
12483 {
12484 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12485
12486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12487 let buffer = self.buffer.read(cx).snapshot(cx);
12488
12489 let mut edits = Vec::new();
12490
12491 let selections = self.selections.all::<Point>(&display_map);
12492 let mut selections = selections.iter().peekable();
12493 let mut contiguous_row_selections = Vec::new();
12494 let mut new_selections = Vec::new();
12495 let mut added_lines = 0;
12496 let mut removed_lines = 0;
12497
12498 while let Some(selection) = selections.next() {
12499 let (start_row, end_row) = consume_contiguous_rows(
12500 &mut contiguous_row_selections,
12501 selection,
12502 &display_map,
12503 &mut selections,
12504 );
12505
12506 let start_point = Point::new(start_row.0, 0);
12507 let end_point = Point::new(
12508 end_row.previous_row().0,
12509 buffer.line_len(end_row.previous_row()),
12510 );
12511 let text = buffer
12512 .text_for_range(start_point..end_point)
12513 .collect::<String>();
12514
12515 let LineManipulationResult {
12516 new_text,
12517 line_count_before,
12518 line_count_after,
12519 } = manipulate(&text);
12520
12521 edits.push((start_point..end_point, new_text));
12522
12523 // Selections must change based on added and removed line count
12524 let start_row =
12525 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
12526 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
12527 new_selections.push(Selection {
12528 id: selection.id,
12529 start: start_row,
12530 end: end_row,
12531 goal: SelectionGoal::None,
12532 reversed: selection.reversed,
12533 });
12534
12535 if line_count_after > line_count_before {
12536 added_lines += line_count_after - line_count_before;
12537 } else if line_count_before > line_count_after {
12538 removed_lines += line_count_before - line_count_after;
12539 }
12540 }
12541
12542 self.transact(window, cx, |this, window, cx| {
12543 let buffer = this.buffer.update(cx, |buffer, cx| {
12544 buffer.edit(edits, None, cx);
12545 buffer.snapshot(cx)
12546 });
12547
12548 // Recalculate offsets on newly edited buffer
12549 let new_selections = new_selections
12550 .iter()
12551 .map(|s| {
12552 let start_point = Point::new(s.start.0, 0);
12553 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
12554 Selection {
12555 id: s.id,
12556 start: buffer.point_to_offset(start_point),
12557 end: buffer.point_to_offset(end_point),
12558 goal: s.goal,
12559 reversed: s.reversed,
12560 }
12561 })
12562 .collect();
12563
12564 this.change_selections(Default::default(), window, cx, |s| {
12565 s.select(new_selections);
12566 });
12567
12568 this.request_autoscroll(Autoscroll::fit(), cx);
12569 });
12570 }
12571
12572 fn manipulate_immutable_lines<Fn>(
12573 &mut self,
12574 window: &mut Window,
12575 cx: &mut Context<Self>,
12576 mut callback: Fn,
12577 ) where
12578 Fn: FnMut(&mut Vec<&str>),
12579 {
12580 self.manipulate_lines(window, cx, |text| {
12581 let mut lines: Vec<&str> = text.split('\n').collect();
12582 let line_count_before = lines.len();
12583
12584 callback(&mut lines);
12585
12586 LineManipulationResult {
12587 new_text: lines.join("\n"),
12588 line_count_before,
12589 line_count_after: lines.len(),
12590 }
12591 });
12592 }
12593
12594 fn manipulate_mutable_lines<Fn>(
12595 &mut self,
12596 window: &mut Window,
12597 cx: &mut Context<Self>,
12598 mut callback: Fn,
12599 ) where
12600 Fn: FnMut(&mut Vec<Cow<'_, str>>),
12601 {
12602 self.manipulate_lines(window, cx, |text| {
12603 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
12604 let line_count_before = lines.len();
12605
12606 callback(&mut lines);
12607
12608 LineManipulationResult {
12609 new_text: lines.join("\n"),
12610 line_count_before,
12611 line_count_after: lines.len(),
12612 }
12613 });
12614 }
12615
12616 pub fn convert_indentation_to_spaces(
12617 &mut self,
12618 _: &ConvertIndentationToSpaces,
12619 window: &mut Window,
12620 cx: &mut Context<Self>,
12621 ) {
12622 let settings = self.buffer.read(cx).language_settings(cx);
12623 let tab_size = settings.tab_size.get() as usize;
12624
12625 self.manipulate_mutable_lines(window, cx, |lines| {
12626 // Allocates a reasonably sized scratch buffer once for the whole loop
12627 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12628 // Avoids recomputing spaces that could be inserted many times
12629 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12630 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12631 .collect();
12632
12633 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12634 let mut chars = line.as_ref().chars();
12635 let mut col = 0;
12636 let mut changed = false;
12637
12638 for ch in chars.by_ref() {
12639 match ch {
12640 ' ' => {
12641 reindented_line.push(' ');
12642 col += 1;
12643 }
12644 '\t' => {
12645 // \t are converted to spaces depending on the current column
12646 let spaces_len = tab_size - (col % tab_size);
12647 reindented_line.extend(&space_cache[spaces_len - 1]);
12648 col += spaces_len;
12649 changed = true;
12650 }
12651 _ => {
12652 // If we dont append before break, the character is consumed
12653 reindented_line.push(ch);
12654 break;
12655 }
12656 }
12657 }
12658
12659 if !changed {
12660 reindented_line.clear();
12661 continue;
12662 }
12663 // Append the rest of the line and replace old reference with new one
12664 reindented_line.extend(chars);
12665 *line = Cow::Owned(reindented_line.clone());
12666 reindented_line.clear();
12667 }
12668 });
12669 }
12670
12671 pub fn convert_indentation_to_tabs(
12672 &mut self,
12673 _: &ConvertIndentationToTabs,
12674 window: &mut Window,
12675 cx: &mut Context<Self>,
12676 ) {
12677 let settings = self.buffer.read(cx).language_settings(cx);
12678 let tab_size = settings.tab_size.get() as usize;
12679
12680 self.manipulate_mutable_lines(window, cx, |lines| {
12681 // Allocates a reasonably sized buffer once for the whole loop
12682 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12683 // Avoids recomputing spaces that could be inserted many times
12684 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12685 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12686 .collect();
12687
12688 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12689 let mut chars = line.chars();
12690 let mut spaces_count = 0;
12691 let mut first_non_indent_char = None;
12692 let mut changed = false;
12693
12694 for ch in chars.by_ref() {
12695 match ch {
12696 ' ' => {
12697 // Keep track of spaces. Append \t when we reach tab_size
12698 spaces_count += 1;
12699 changed = true;
12700 if spaces_count == tab_size {
12701 reindented_line.push('\t');
12702 spaces_count = 0;
12703 }
12704 }
12705 '\t' => {
12706 reindented_line.push('\t');
12707 spaces_count = 0;
12708 }
12709 _ => {
12710 // Dont append it yet, we might have remaining spaces
12711 first_non_indent_char = Some(ch);
12712 break;
12713 }
12714 }
12715 }
12716
12717 if !changed {
12718 reindented_line.clear();
12719 continue;
12720 }
12721 // Remaining spaces that didn't make a full tab stop
12722 if spaces_count > 0 {
12723 reindented_line.extend(&space_cache[spaces_count - 1]);
12724 }
12725 // If we consume an extra character that was not indentation, add it back
12726 if let Some(extra_char) = first_non_indent_char {
12727 reindented_line.push(extra_char);
12728 }
12729 // Append the rest of the line and replace old reference with new one
12730 reindented_line.extend(chars);
12731 *line = Cow::Owned(reindented_line.clone());
12732 reindented_line.clear();
12733 }
12734 });
12735 }
12736
12737 pub fn convert_to_upper_case(
12738 &mut self,
12739 _: &ConvertToUpperCase,
12740 window: &mut Window,
12741 cx: &mut Context<Self>,
12742 ) {
12743 self.manipulate_text(window, cx, |text| text.to_uppercase())
12744 }
12745
12746 pub fn convert_to_lower_case(
12747 &mut self,
12748 _: &ConvertToLowerCase,
12749 window: &mut Window,
12750 cx: &mut Context<Self>,
12751 ) {
12752 self.manipulate_text(window, cx, |text| text.to_lowercase())
12753 }
12754
12755 pub fn convert_to_title_case(
12756 &mut self,
12757 _: &ConvertToTitleCase,
12758 window: &mut Window,
12759 cx: &mut Context<Self>,
12760 ) {
12761 self.manipulate_text(window, cx, |text| {
12762 Self::convert_text_case(text, Case::Title)
12763 })
12764 }
12765
12766 pub fn convert_to_snake_case(
12767 &mut self,
12768 _: &ConvertToSnakeCase,
12769 window: &mut Window,
12770 cx: &mut Context<Self>,
12771 ) {
12772 self.manipulate_text(window, cx, |text| {
12773 Self::convert_text_case(text, Case::Snake)
12774 })
12775 }
12776
12777 pub fn convert_to_kebab_case(
12778 &mut self,
12779 _: &ConvertToKebabCase,
12780 window: &mut Window,
12781 cx: &mut Context<Self>,
12782 ) {
12783 self.manipulate_text(window, cx, |text| {
12784 Self::convert_text_case(text, Case::Kebab)
12785 })
12786 }
12787
12788 pub fn convert_to_upper_camel_case(
12789 &mut self,
12790 _: &ConvertToUpperCamelCase,
12791 window: &mut Window,
12792 cx: &mut Context<Self>,
12793 ) {
12794 self.manipulate_text(window, cx, |text| {
12795 Self::convert_text_case(text, Case::UpperCamel)
12796 })
12797 }
12798
12799 pub fn convert_to_lower_camel_case(
12800 &mut self,
12801 _: &ConvertToLowerCamelCase,
12802 window: &mut Window,
12803 cx: &mut Context<Self>,
12804 ) {
12805 self.manipulate_text(window, cx, |text| {
12806 Self::convert_text_case(text, Case::Camel)
12807 })
12808 }
12809
12810 pub fn convert_to_opposite_case(
12811 &mut self,
12812 _: &ConvertToOppositeCase,
12813 window: &mut Window,
12814 cx: &mut Context<Self>,
12815 ) {
12816 self.manipulate_text(window, cx, |text| {
12817 text.chars()
12818 .fold(String::with_capacity(text.len()), |mut t, c| {
12819 if c.is_uppercase() {
12820 t.extend(c.to_lowercase());
12821 } else {
12822 t.extend(c.to_uppercase());
12823 }
12824 t
12825 })
12826 })
12827 }
12828
12829 pub fn convert_to_sentence_case(
12830 &mut self,
12831 _: &ConvertToSentenceCase,
12832 window: &mut Window,
12833 cx: &mut Context<Self>,
12834 ) {
12835 self.manipulate_text(window, cx, |text| {
12836 Self::convert_text_case(text, Case::Sentence)
12837 })
12838 }
12839
12840 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12841 self.manipulate_text(window, cx, |text| {
12842 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12843 if has_upper_case_characters {
12844 text.to_lowercase()
12845 } else {
12846 text.to_uppercase()
12847 }
12848 })
12849 }
12850
12851 pub fn convert_to_rot13(
12852 &mut self,
12853 _: &ConvertToRot13,
12854 window: &mut Window,
12855 cx: &mut Context<Self>,
12856 ) {
12857 self.manipulate_text(window, cx, |text| {
12858 text.chars()
12859 .map(|c| match c {
12860 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12861 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12862 _ => c,
12863 })
12864 .collect()
12865 })
12866 }
12867
12868 fn convert_text_case(text: &str, case: Case) -> String {
12869 text.lines()
12870 .map(|line| {
12871 let trimmed_start = line.trim_start();
12872 let leading = &line[..line.len() - trimmed_start.len()];
12873 let trimmed = trimmed_start.trim_end();
12874 let trailing = &trimmed_start[trimmed.len()..];
12875 format!("{}{}{}", leading, trimmed.to_case(case), trailing)
12876 })
12877 .join("\n")
12878 }
12879
12880 pub fn convert_to_rot47(
12881 &mut self,
12882 _: &ConvertToRot47,
12883 window: &mut Window,
12884 cx: &mut Context<Self>,
12885 ) {
12886 self.manipulate_text(window, cx, |text| {
12887 text.chars()
12888 .map(|c| {
12889 let code_point = c as u32;
12890 if code_point >= 33 && code_point <= 126 {
12891 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12892 }
12893 c
12894 })
12895 .collect()
12896 })
12897 }
12898
12899 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12900 where
12901 Fn: FnMut(&str) -> String,
12902 {
12903 let buffer = self.buffer.read(cx).snapshot(cx);
12904
12905 let mut new_selections = Vec::new();
12906 let mut edits = Vec::new();
12907 let mut selection_adjustment = 0isize;
12908
12909 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12910 let selection_is_empty = selection.is_empty();
12911
12912 let (start, end) = if selection_is_empty {
12913 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12914 (word_range.start, word_range.end)
12915 } else {
12916 (
12917 buffer.point_to_offset(selection.start),
12918 buffer.point_to_offset(selection.end),
12919 )
12920 };
12921
12922 let text = buffer.text_for_range(start..end).collect::<String>();
12923 let old_length = text.len() as isize;
12924 let text = callback(&text);
12925
12926 new_selections.push(Selection {
12927 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12928 end: MultiBufferOffset(
12929 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12930 ),
12931 goal: SelectionGoal::None,
12932 id: selection.id,
12933 reversed: selection.reversed,
12934 });
12935
12936 selection_adjustment += old_length - text.len() as isize;
12937
12938 edits.push((start..end, text));
12939 }
12940
12941 self.transact(window, cx, |this, window, cx| {
12942 this.buffer.update(cx, |buffer, cx| {
12943 buffer.edit(edits, None, cx);
12944 });
12945
12946 this.change_selections(Default::default(), window, cx, |s| {
12947 s.select(new_selections);
12948 });
12949
12950 this.request_autoscroll(Autoscroll::fit(), cx);
12951 });
12952 }
12953
12954 pub fn move_selection_on_drop(
12955 &mut self,
12956 selection: &Selection<Anchor>,
12957 target: DisplayPoint,
12958 is_cut: bool,
12959 window: &mut Window,
12960 cx: &mut Context<Self>,
12961 ) {
12962 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12963 let buffer = display_map.buffer_snapshot();
12964 let mut edits = Vec::new();
12965 let insert_point = display_map
12966 .clip_point(target, Bias::Left)
12967 .to_point(&display_map);
12968 let text = buffer
12969 .text_for_range(selection.start..selection.end)
12970 .collect::<String>();
12971 if is_cut {
12972 edits.push(((selection.start..selection.end), String::new()));
12973 }
12974 let insert_anchor = buffer.anchor_before(insert_point);
12975 edits.push(((insert_anchor..insert_anchor), text));
12976 let last_edit_start = insert_anchor.bias_left(buffer);
12977 let last_edit_end = insert_anchor.bias_right(buffer);
12978 self.transact(window, cx, |this, window, cx| {
12979 this.buffer.update(cx, |buffer, cx| {
12980 buffer.edit(edits, None, cx);
12981 });
12982 this.change_selections(Default::default(), window, cx, |s| {
12983 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12984 });
12985 });
12986 }
12987
12988 pub fn clear_selection_drag_state(&mut self) {
12989 self.selection_drag_state = SelectionDragState::None;
12990 }
12991
12992 pub fn duplicate(
12993 &mut self,
12994 upwards: bool,
12995 whole_lines: bool,
12996 window: &mut Window,
12997 cx: &mut Context<Self>,
12998 ) {
12999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13000
13001 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13002 let buffer = display_map.buffer_snapshot();
13003 let selections = self.selections.all::<Point>(&display_map);
13004
13005 let mut edits = Vec::new();
13006 let mut selections_iter = selections.iter().peekable();
13007 while let Some(selection) = selections_iter.next() {
13008 let mut rows = selection.spanned_rows(false, &display_map);
13009 // duplicate line-wise
13010 if whole_lines || selection.start == selection.end {
13011 // Avoid duplicating the same lines twice.
13012 while let Some(next_selection) = selections_iter.peek() {
13013 let next_rows = next_selection.spanned_rows(false, &display_map);
13014 if next_rows.start < rows.end {
13015 rows.end = next_rows.end;
13016 selections_iter.next().unwrap();
13017 } else {
13018 break;
13019 }
13020 }
13021
13022 // Copy the text from the selected row region and splice it either at the start
13023 // or end of the region.
13024 let start = Point::new(rows.start.0, 0);
13025 let end = Point::new(
13026 rows.end.previous_row().0,
13027 buffer.line_len(rows.end.previous_row()),
13028 );
13029
13030 let mut text = buffer.text_for_range(start..end).collect::<String>();
13031
13032 let insert_location = if upwards {
13033 // When duplicating upward, we need to insert before the current line.
13034 // If we're on the last line and it doesn't end with a newline,
13035 // we need to add a newline before the duplicated content.
13036 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
13037 && buffer.max_point().column > 0
13038 && !text.ends_with('\n');
13039
13040 if needs_leading_newline {
13041 text.insert(0, '\n');
13042 end
13043 } else {
13044 text.push('\n');
13045 Point::new(rows.start.0, 0)
13046 }
13047 } else {
13048 text.push('\n');
13049 start
13050 };
13051 edits.push((insert_location..insert_location, text));
13052 } else {
13053 // duplicate character-wise
13054 let start = selection.start;
13055 let end = selection.end;
13056 let text = buffer.text_for_range(start..end).collect::<String>();
13057 edits.push((selection.end..selection.end, text));
13058 }
13059 }
13060
13061 self.transact(window, cx, |this, window, cx| {
13062 this.buffer.update(cx, |buffer, cx| {
13063 buffer.edit(edits, None, cx);
13064 });
13065
13066 // When duplicating upward with whole lines, move the cursor to the duplicated line
13067 if upwards && whole_lines {
13068 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
13069
13070 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13071 let mut new_ranges = Vec::new();
13072 let selections = s.all::<Point>(&display_map);
13073 let mut selections_iter = selections.iter().peekable();
13074
13075 while let Some(first_selection) = selections_iter.next() {
13076 // Group contiguous selections together to find the total row span
13077 let mut group_selections = vec![first_selection];
13078 let mut rows = first_selection.spanned_rows(false, &display_map);
13079
13080 while let Some(next_selection) = selections_iter.peek() {
13081 let next_rows = next_selection.spanned_rows(false, &display_map);
13082 if next_rows.start < rows.end {
13083 rows.end = next_rows.end;
13084 group_selections.push(selections_iter.next().unwrap());
13085 } else {
13086 break;
13087 }
13088 }
13089
13090 let row_count = rows.end.0 - rows.start.0;
13091
13092 // Move all selections in this group up by the total number of duplicated rows
13093 for selection in group_selections {
13094 let new_start = Point::new(
13095 selection.start.row.saturating_sub(row_count),
13096 selection.start.column,
13097 );
13098
13099 let new_end = Point::new(
13100 selection.end.row.saturating_sub(row_count),
13101 selection.end.column,
13102 );
13103
13104 new_ranges.push(new_start..new_end);
13105 }
13106 }
13107
13108 s.select_ranges(new_ranges);
13109 });
13110 }
13111
13112 this.request_autoscroll(Autoscroll::fit(), cx);
13113 });
13114 }
13115
13116 pub fn duplicate_line_up(
13117 &mut self,
13118 _: &DuplicateLineUp,
13119 window: &mut Window,
13120 cx: &mut Context<Self>,
13121 ) {
13122 self.duplicate(true, true, window, cx);
13123 }
13124
13125 pub fn duplicate_line_down(
13126 &mut self,
13127 _: &DuplicateLineDown,
13128 window: &mut Window,
13129 cx: &mut Context<Self>,
13130 ) {
13131 self.duplicate(false, true, window, cx);
13132 }
13133
13134 pub fn duplicate_selection(
13135 &mut self,
13136 _: &DuplicateSelection,
13137 window: &mut Window,
13138 cx: &mut Context<Self>,
13139 ) {
13140 self.duplicate(false, false, window, cx);
13141 }
13142
13143 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
13144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13145 if self.mode.is_single_line() {
13146 cx.propagate();
13147 return;
13148 }
13149
13150 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13151 let buffer = self.buffer.read(cx).snapshot(cx);
13152
13153 let mut edits = Vec::new();
13154 let mut unfold_ranges = Vec::new();
13155 let mut refold_creases = Vec::new();
13156
13157 let selections = self.selections.all::<Point>(&display_map);
13158 let mut selections = selections.iter().peekable();
13159 let mut contiguous_row_selections = Vec::new();
13160 let mut new_selections = Vec::new();
13161
13162 while let Some(selection) = selections.next() {
13163 // Find all the selections that span a contiguous row range
13164 let (start_row, end_row) = consume_contiguous_rows(
13165 &mut contiguous_row_selections,
13166 selection,
13167 &display_map,
13168 &mut selections,
13169 );
13170
13171 // Move the text spanned by the row range to be before the line preceding the row range
13172 if start_row.0 > 0 {
13173 let range_to_move = Point::new(
13174 start_row.previous_row().0,
13175 buffer.line_len(start_row.previous_row()),
13176 )
13177 ..Point::new(
13178 end_row.previous_row().0,
13179 buffer.line_len(end_row.previous_row()),
13180 );
13181 let insertion_point = display_map
13182 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
13183 .0;
13184
13185 // Don't move lines across excerpts
13186 if buffer
13187 .excerpt_containing(insertion_point..range_to_move.end)
13188 .is_some()
13189 {
13190 let text = buffer
13191 .text_for_range(range_to_move.clone())
13192 .flat_map(|s| s.chars())
13193 .skip(1)
13194 .chain(['\n'])
13195 .collect::<String>();
13196
13197 edits.push((
13198 buffer.anchor_after(range_to_move.start)
13199 ..buffer.anchor_before(range_to_move.end),
13200 String::new(),
13201 ));
13202 let insertion_anchor = buffer.anchor_after(insertion_point);
13203 edits.push((insertion_anchor..insertion_anchor, text));
13204
13205 let row_delta = range_to_move.start.row - insertion_point.row + 1;
13206
13207 // Move selections up
13208 new_selections.extend(contiguous_row_selections.drain(..).map(
13209 |mut selection| {
13210 selection.start.row -= row_delta;
13211 selection.end.row -= row_delta;
13212 selection
13213 },
13214 ));
13215
13216 // Move folds up
13217 unfold_ranges.push(range_to_move.clone());
13218 for fold in display_map.folds_in_range(
13219 buffer.anchor_before(range_to_move.start)
13220 ..buffer.anchor_after(range_to_move.end),
13221 ) {
13222 let mut start = fold.range.start.to_point(&buffer);
13223 let mut end = fold.range.end.to_point(&buffer);
13224 start.row -= row_delta;
13225 end.row -= row_delta;
13226 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13227 }
13228 }
13229 }
13230
13231 // If we didn't move line(s), preserve the existing selections
13232 new_selections.append(&mut contiguous_row_selections);
13233 }
13234
13235 self.transact(window, cx, |this, window, cx| {
13236 this.unfold_ranges(&unfold_ranges, true, true, cx);
13237 this.buffer.update(cx, |buffer, cx| {
13238 for (range, text) in edits {
13239 buffer.edit([(range, text)], None, cx);
13240 }
13241 });
13242 this.fold_creases(refold_creases, true, window, cx);
13243 this.change_selections(Default::default(), window, cx, |s| {
13244 s.select(new_selections);
13245 })
13246 });
13247 }
13248
13249 pub fn move_line_down(
13250 &mut self,
13251 _: &MoveLineDown,
13252 window: &mut Window,
13253 cx: &mut Context<Self>,
13254 ) {
13255 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13256 if self.mode.is_single_line() {
13257 cx.propagate();
13258 return;
13259 }
13260
13261 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13262 let buffer = self.buffer.read(cx).snapshot(cx);
13263
13264 let mut edits = Vec::new();
13265 let mut unfold_ranges = Vec::new();
13266 let mut refold_creases = Vec::new();
13267
13268 let selections = self.selections.all::<Point>(&display_map);
13269 let mut selections = selections.iter().peekable();
13270 let mut contiguous_row_selections = Vec::new();
13271 let mut new_selections = Vec::new();
13272
13273 while let Some(selection) = selections.next() {
13274 // Find all the selections that span a contiguous row range
13275 let (start_row, end_row) = consume_contiguous_rows(
13276 &mut contiguous_row_selections,
13277 selection,
13278 &display_map,
13279 &mut selections,
13280 );
13281
13282 // Move the text spanned by the row range to be after the last line of the row range
13283 if end_row.0 <= buffer.max_point().row {
13284 let range_to_move =
13285 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
13286 let insertion_point = display_map
13287 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
13288 .0;
13289
13290 // Don't move lines across excerpt boundaries
13291 if buffer
13292 .excerpt_containing(range_to_move.start..insertion_point)
13293 .is_some()
13294 {
13295 let mut text = String::from("\n");
13296 text.extend(buffer.text_for_range(range_to_move.clone()));
13297 text.pop(); // Drop trailing newline
13298 edits.push((
13299 buffer.anchor_after(range_to_move.start)
13300 ..buffer.anchor_before(range_to_move.end),
13301 String::new(),
13302 ));
13303 let insertion_anchor = buffer.anchor_after(insertion_point);
13304 edits.push((insertion_anchor..insertion_anchor, text));
13305
13306 let row_delta = insertion_point.row - range_to_move.end.row + 1;
13307
13308 // Move selections down
13309 new_selections.extend(contiguous_row_selections.drain(..).map(
13310 |mut selection| {
13311 selection.start.row += row_delta;
13312 selection.end.row += row_delta;
13313 selection
13314 },
13315 ));
13316
13317 // Move folds down
13318 unfold_ranges.push(range_to_move.clone());
13319 for fold in display_map.folds_in_range(
13320 buffer.anchor_before(range_to_move.start)
13321 ..buffer.anchor_after(range_to_move.end),
13322 ) {
13323 let mut start = fold.range.start.to_point(&buffer);
13324 let mut end = fold.range.end.to_point(&buffer);
13325 start.row += row_delta;
13326 end.row += row_delta;
13327 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
13328 }
13329 }
13330 }
13331
13332 // If we didn't move line(s), preserve the existing selections
13333 new_selections.append(&mut contiguous_row_selections);
13334 }
13335
13336 self.transact(window, cx, |this, window, cx| {
13337 this.unfold_ranges(&unfold_ranges, true, true, cx);
13338 this.buffer.update(cx, |buffer, cx| {
13339 for (range, text) in edits {
13340 buffer.edit([(range, text)], None, cx);
13341 }
13342 });
13343 this.fold_creases(refold_creases, true, window, cx);
13344 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
13345 });
13346 }
13347
13348 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
13349 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13350 let text_layout_details = &self.text_layout_details(window, cx);
13351 self.transact(window, cx, |this, window, cx| {
13352 let edits = this.change_selections(Default::default(), window, cx, |s| {
13353 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
13354 s.move_with(&mut |display_map, selection| {
13355 if !selection.is_empty() {
13356 return;
13357 }
13358
13359 let mut head = selection.head();
13360 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
13361 if head.column() == display_map.line_len(head.row()) {
13362 transpose_offset = display_map
13363 .buffer_snapshot()
13364 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13365 }
13366
13367 if transpose_offset == MultiBufferOffset(0) {
13368 return;
13369 }
13370
13371 *head.column_mut() += 1;
13372 head = display_map.clip_point(head, Bias::Right);
13373 let goal = SelectionGoal::HorizontalPosition(
13374 display_map
13375 .x_for_display_point(head, text_layout_details)
13376 .into(),
13377 );
13378 selection.collapse_to(head, goal);
13379
13380 let transpose_start = display_map
13381 .buffer_snapshot()
13382 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
13383 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
13384 let transpose_end = display_map
13385 .buffer_snapshot()
13386 .clip_offset(transpose_offset + 1usize, Bias::Right);
13387 if let Some(ch) = display_map
13388 .buffer_snapshot()
13389 .chars_at(transpose_start)
13390 .next()
13391 {
13392 edits.push((transpose_start..transpose_offset, String::new()));
13393 edits.push((transpose_end..transpose_end, ch.to_string()));
13394 }
13395 }
13396 });
13397 edits
13398 });
13399 this.buffer
13400 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13401 let selections = this
13402 .selections
13403 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13404 this.change_selections(Default::default(), window, cx, |s| {
13405 s.select(selections);
13406 });
13407 });
13408 }
13409
13410 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
13411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13412 if self.mode.is_single_line() {
13413 cx.propagate();
13414 return;
13415 }
13416
13417 self.rewrap_impl(RewrapOptions::default(), cx)
13418 }
13419
13420 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
13421 let buffer = self.buffer.read(cx).snapshot(cx);
13422 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13423
13424 #[derive(Clone, Debug, PartialEq)]
13425 enum CommentFormat {
13426 /// single line comment, with prefix for line
13427 Line(String),
13428 /// single line within a block comment, with prefix for line
13429 BlockLine(String),
13430 /// a single line of a block comment that includes the initial delimiter
13431 BlockCommentWithStart(BlockCommentConfig),
13432 /// a single line of a block comment that includes the ending delimiter
13433 BlockCommentWithEnd(BlockCommentConfig),
13434 }
13435
13436 // Split selections to respect paragraph, indent, and comment prefix boundaries.
13437 let wrap_ranges = selections.into_iter().flat_map(|selection| {
13438 let language_settings = buffer.language_settings_at(selection.head(), cx);
13439 let language_scope = buffer.language_scope_at(selection.head());
13440
13441 let indent_and_prefix_for_row =
13442 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
13443 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
13444 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
13445 &language_scope
13446 {
13447 let indent_end = Point::new(row, indent.len);
13448 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
13449 let line_text_after_indent = buffer
13450 .text_for_range(indent_end..line_end)
13451 .collect::<String>();
13452
13453 let is_within_comment_override = buffer
13454 .language_scope_at(indent_end)
13455 .is_some_and(|scope| scope.override_name() == Some("comment"));
13456 let comment_delimiters = if is_within_comment_override {
13457 // we are within a comment syntax node, but we don't
13458 // yet know what kind of comment: block, doc or line
13459 match (
13460 language_scope.documentation_comment(),
13461 language_scope.block_comment(),
13462 ) {
13463 (Some(config), _) | (_, Some(config))
13464 if buffer.contains_str_at(indent_end, &config.start) =>
13465 {
13466 Some(CommentFormat::BlockCommentWithStart(config.clone()))
13467 }
13468 (Some(config), _) | (_, Some(config))
13469 if line_text_after_indent.ends_with(config.end.as_ref()) =>
13470 {
13471 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
13472 }
13473 (Some(config), _) | (_, Some(config))
13474 if buffer.contains_str_at(indent_end, &config.prefix) =>
13475 {
13476 Some(CommentFormat::BlockLine(config.prefix.to_string()))
13477 }
13478 (_, _) => language_scope
13479 .line_comment_prefixes()
13480 .iter()
13481 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13482 .map(|prefix| CommentFormat::Line(prefix.to_string())),
13483 }
13484 } else {
13485 // we not in an overridden comment node, but we may
13486 // be within a non-overridden line comment node
13487 language_scope
13488 .line_comment_prefixes()
13489 .iter()
13490 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
13491 .map(|prefix| CommentFormat::Line(prefix.to_string()))
13492 };
13493
13494 let rewrap_prefix = language_scope
13495 .rewrap_prefixes()
13496 .iter()
13497 .find_map(|prefix_regex| {
13498 prefix_regex.find(&line_text_after_indent).map(|mat| {
13499 if mat.start() == 0 {
13500 Some(mat.as_str().to_string())
13501 } else {
13502 None
13503 }
13504 })
13505 })
13506 .flatten();
13507 (comment_delimiters, rewrap_prefix)
13508 } else {
13509 (None, None)
13510 };
13511 (indent, comment_prefix, rewrap_prefix)
13512 };
13513
13514 let mut start_row = selection.start.row;
13515 let mut end_row = selection.end.row;
13516
13517 if selection.is_empty() {
13518 let cursor_row = selection.start.row;
13519
13520 let (mut indent_size, comment_prefix, _) = indent_and_prefix_for_row(cursor_row);
13521 let line_prefix = match &comment_prefix {
13522 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13523 Some(prefix.as_str())
13524 }
13525 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13526 prefix, ..
13527 })) => Some(prefix.as_ref()),
13528 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13529 start: _,
13530 end: _,
13531 prefix,
13532 tab_size,
13533 })) => {
13534 indent_size.len += tab_size;
13535 Some(prefix.as_ref())
13536 }
13537 None => None,
13538 };
13539 let indent_prefix = indent_size.chars().collect::<String>();
13540 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13541
13542 'expand_upwards: while start_row > 0 {
13543 let prev_row = start_row - 1;
13544 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
13545 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
13546 && !buffer.is_line_blank(MultiBufferRow(prev_row))
13547 {
13548 start_row = prev_row;
13549 } else {
13550 break 'expand_upwards;
13551 }
13552 }
13553
13554 'expand_downwards: while end_row < buffer.max_point().row {
13555 let next_row = end_row + 1;
13556 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
13557 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
13558 && !buffer.is_line_blank(MultiBufferRow(next_row))
13559 {
13560 end_row = next_row;
13561 } else {
13562 break 'expand_downwards;
13563 }
13564 }
13565 }
13566
13567 let mut non_blank_rows_iter = (start_row..=end_row)
13568 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
13569 .peekable();
13570
13571 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
13572 row
13573 } else {
13574 return Vec::new();
13575 };
13576
13577 let mut ranges = Vec::new();
13578
13579 let mut current_range_start = first_row;
13580 let mut prev_row = first_row;
13581 let (
13582 mut current_range_indent,
13583 mut current_range_comment_delimiters,
13584 mut current_range_rewrap_prefix,
13585 ) = indent_and_prefix_for_row(first_row);
13586
13587 for row in non_blank_rows_iter.skip(1) {
13588 let has_paragraph_break = row > prev_row + 1;
13589
13590 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
13591 indent_and_prefix_for_row(row);
13592
13593 let has_indent_change = row_indent != current_range_indent;
13594 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
13595
13596 let has_boundary_change = has_comment_change
13597 || row_rewrap_prefix.is_some()
13598 || (has_indent_change && current_range_comment_delimiters.is_some());
13599
13600 if has_paragraph_break || has_boundary_change {
13601 ranges.push((
13602 language_settings.clone(),
13603 Point::new(current_range_start, 0)
13604 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13605 current_range_indent,
13606 current_range_comment_delimiters.clone(),
13607 current_range_rewrap_prefix.clone(),
13608 ));
13609 current_range_start = row;
13610 current_range_indent = row_indent;
13611 current_range_comment_delimiters = row_comment_delimiters;
13612 current_range_rewrap_prefix = row_rewrap_prefix;
13613 }
13614 prev_row = row;
13615 }
13616
13617 ranges.push((
13618 language_settings.clone(),
13619 Point::new(current_range_start, 0)
13620 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
13621 current_range_indent,
13622 current_range_comment_delimiters,
13623 current_range_rewrap_prefix,
13624 ));
13625
13626 ranges
13627 });
13628
13629 let mut edits = Vec::new();
13630 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
13631
13632 for (language_settings, wrap_range, mut indent_size, comment_prefix, rewrap_prefix) in
13633 wrap_ranges
13634 {
13635 let start_row = wrap_range.start.row;
13636 let end_row = wrap_range.end.row;
13637
13638 // Skip selections that overlap with a range that has already been rewrapped.
13639 let selection_range = start_row..end_row;
13640 if rewrapped_row_ranges
13641 .iter()
13642 .any(|range| range.overlaps(&selection_range))
13643 {
13644 continue;
13645 }
13646
13647 let tab_size = language_settings.tab_size;
13648
13649 let (line_prefix, inside_comment) = match &comment_prefix {
13650 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
13651 (Some(prefix.as_str()), true)
13652 }
13653 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
13654 (Some(prefix.as_ref()), true)
13655 }
13656 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13657 start: _,
13658 end: _,
13659 prefix,
13660 tab_size,
13661 })) => {
13662 indent_size.len += tab_size;
13663 (Some(prefix.as_ref()), true)
13664 }
13665 None => (None, false),
13666 };
13667 let indent_prefix = indent_size.chars().collect::<String>();
13668 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
13669
13670 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
13671 RewrapBehavior::InComments => inside_comment,
13672 RewrapBehavior::InSelections => !wrap_range.is_empty(),
13673 RewrapBehavior::Anywhere => true,
13674 };
13675
13676 let should_rewrap = options.override_language_settings
13677 || allow_rewrap_based_on_language
13678 || self.hard_wrap.is_some();
13679 if !should_rewrap {
13680 continue;
13681 }
13682
13683 let start = Point::new(start_row, 0);
13684 let start_offset = ToOffset::to_offset(&start, &buffer);
13685 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
13686 let selection_text = buffer.text_for_range(start..end).collect::<String>();
13687 let mut first_line_delimiter = None;
13688 let mut last_line_delimiter = None;
13689 let Some(lines_without_prefixes) = selection_text
13690 .lines()
13691 .enumerate()
13692 .map(|(ix, line)| {
13693 let line_trimmed = line.trim_start();
13694 if rewrap_prefix.is_some() && ix > 0 {
13695 Ok(line_trimmed)
13696 } else if let Some(
13697 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
13698 start,
13699 prefix,
13700 end,
13701 tab_size,
13702 })
13703 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
13704 start,
13705 prefix,
13706 end,
13707 tab_size,
13708 }),
13709 ) = &comment_prefix
13710 {
13711 let line_trimmed = line_trimmed
13712 .strip_prefix(start.as_ref())
13713 .map(|s| {
13714 let mut indent_size = indent_size;
13715 indent_size.len -= tab_size;
13716 let indent_prefix: String = indent_size.chars().collect();
13717 first_line_delimiter = Some((indent_prefix, start));
13718 s.trim_start()
13719 })
13720 .unwrap_or(line_trimmed);
13721 let line_trimmed = line_trimmed
13722 .strip_suffix(end.as_ref())
13723 .map(|s| {
13724 last_line_delimiter = Some(end);
13725 s.trim_end()
13726 })
13727 .unwrap_or(line_trimmed);
13728 let line_trimmed = line_trimmed
13729 .strip_prefix(prefix.as_ref())
13730 .unwrap_or(line_trimmed);
13731 Ok(line_trimmed)
13732 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13733 line_trimmed.strip_prefix(prefix).with_context(|| {
13734 format!("line did not start with prefix {prefix:?}: {line:?}")
13735 })
13736 } else {
13737 line_trimmed
13738 .strip_prefix(&line_prefix.trim_start())
13739 .with_context(|| {
13740 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13741 })
13742 }
13743 })
13744 .collect::<Result<Vec<_>, _>>()
13745 .log_err()
13746 else {
13747 continue;
13748 };
13749
13750 let wrap_column = options.line_length.or(self.hard_wrap).unwrap_or_else(|| {
13751 buffer
13752 .language_settings_at(Point::new(start_row, 0), cx)
13753 .preferred_line_length as usize
13754 });
13755
13756 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13757 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13758 } else {
13759 line_prefix.clone()
13760 };
13761
13762 let wrapped_text = {
13763 let mut wrapped_text = wrap_with_prefix(
13764 line_prefix,
13765 subsequent_lines_prefix,
13766 lines_without_prefixes.join("\n"),
13767 wrap_column,
13768 tab_size,
13769 options.preserve_existing_whitespace,
13770 );
13771
13772 if let Some((indent, delimiter)) = first_line_delimiter {
13773 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13774 }
13775 if let Some(last_line) = last_line_delimiter {
13776 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13777 }
13778
13779 wrapped_text
13780 };
13781
13782 // TODO: should always use char-based diff while still supporting cursor behavior that
13783 // matches vim.
13784 let mut diff_options = DiffOptions::default();
13785 if options.override_language_settings {
13786 diff_options.max_word_diff_len = 0;
13787 diff_options.max_word_diff_line_count = 0;
13788 } else {
13789 diff_options.max_word_diff_len = usize::MAX;
13790 diff_options.max_word_diff_line_count = usize::MAX;
13791 }
13792
13793 for (old_range, new_text) in
13794 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13795 {
13796 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13797 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13798 edits.push((edit_start..edit_end, new_text));
13799 }
13800
13801 rewrapped_row_ranges.push(start_row..=end_row);
13802 }
13803
13804 self.buffer
13805 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13806 }
13807
13808 pub fn cut_common(
13809 &mut self,
13810 cut_no_selection_line: bool,
13811 window: &mut Window,
13812 cx: &mut Context<Self>,
13813 ) -> ClipboardItem {
13814 let mut text = String::new();
13815 let buffer = self.buffer.read(cx).snapshot(cx);
13816 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13817 let mut clipboard_selections = Vec::with_capacity(selections.len());
13818 {
13819 let max_point = buffer.max_point();
13820 let mut is_first = true;
13821 let mut prev_selection_was_entire_line = false;
13822 for selection in &mut selections {
13823 let is_entire_line =
13824 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13825 if is_entire_line {
13826 selection.start = Point::new(selection.start.row, 0);
13827 if !selection.is_empty() && selection.end.column == 0 {
13828 selection.end = cmp::min(max_point, selection.end);
13829 } else {
13830 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13831 }
13832 selection.goal = SelectionGoal::None;
13833 }
13834 if is_first {
13835 is_first = false;
13836 } else if !prev_selection_was_entire_line {
13837 text += "\n";
13838 }
13839 prev_selection_was_entire_line = is_entire_line;
13840 let mut len = 0;
13841 for chunk in buffer.text_for_range(selection.start..selection.end) {
13842 text.push_str(chunk);
13843 len += chunk.len();
13844 }
13845
13846 clipboard_selections.push(ClipboardSelection::for_buffer(
13847 len,
13848 is_entire_line,
13849 selection.range(),
13850 &buffer,
13851 self.project.as_ref(),
13852 cx,
13853 ));
13854 }
13855 }
13856
13857 self.transact(window, cx, |this, window, cx| {
13858 this.change_selections(Default::default(), window, cx, |s| {
13859 s.select(selections);
13860 });
13861 this.insert("", window, cx);
13862 });
13863 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13864 }
13865
13866 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13867 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13868 let item = self.cut_common(true, window, cx);
13869 cx.write_to_clipboard(item);
13870 }
13871
13872 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13874 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13875 s.move_with(&mut |snapshot, sel| {
13876 if sel.is_empty() {
13877 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13878 }
13879 if sel.is_empty() {
13880 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13881 }
13882 });
13883 });
13884 let item = self.cut_common(false, window, cx);
13885 cx.set_global(KillRing(item))
13886 }
13887
13888 pub fn kill_ring_yank(
13889 &mut self,
13890 _: &KillRingYank,
13891 window: &mut Window,
13892 cx: &mut Context<Self>,
13893 ) {
13894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13895 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13896 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13897 (kill_ring.text().to_string(), kill_ring.metadata_json())
13898 } else {
13899 return;
13900 }
13901 } else {
13902 return;
13903 };
13904 self.do_paste(&text, metadata, false, window, cx);
13905 }
13906
13907 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13908 self.do_copy(true, cx);
13909 }
13910
13911 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13912 self.do_copy(false, cx);
13913 }
13914
13915 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13916 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13917 let buffer = self.buffer.read(cx).read(cx);
13918 let mut text = String::new();
13919 let mut clipboard_selections = Vec::with_capacity(selections.len());
13920
13921 let max_point = buffer.max_point();
13922 let mut is_first = true;
13923 for selection in &selections {
13924 let mut start = selection.start;
13925 let mut end = selection.end;
13926 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13927 let mut add_trailing_newline = false;
13928 if is_entire_line {
13929 start = Point::new(start.row, 0);
13930 let next_line_start = Point::new(end.row + 1, 0);
13931 if next_line_start <= max_point {
13932 end = next_line_start;
13933 } else {
13934 // We're on the last line without a trailing newline.
13935 // Copy to the end of the line and add a newline afterwards.
13936 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13937 add_trailing_newline = true;
13938 }
13939 }
13940
13941 let mut trimmed_selections = Vec::new();
13942 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13943 let row = MultiBufferRow(start.row);
13944 let first_indent = buffer.indent_size_for_line(row);
13945 if first_indent.len == 0 || start.column > first_indent.len {
13946 trimmed_selections.push(start..end);
13947 } else {
13948 trimmed_selections.push(
13949 Point::new(row.0, first_indent.len)
13950 ..Point::new(row.0, buffer.line_len(row)),
13951 );
13952 for row in start.row + 1..=end.row {
13953 let mut line_len = buffer.line_len(MultiBufferRow(row));
13954 if row == end.row {
13955 line_len = end.column;
13956 }
13957 if line_len == 0 {
13958 trimmed_selections.push(Point::new(row, 0)..Point::new(row, line_len));
13959 continue;
13960 }
13961 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13962 if row_indent_size.len >= first_indent.len {
13963 trimmed_selections
13964 .push(Point::new(row, first_indent.len)..Point::new(row, line_len));
13965 } else {
13966 trimmed_selections.clear();
13967 trimmed_selections.push(start..end);
13968 break;
13969 }
13970 }
13971 }
13972 } else {
13973 trimmed_selections.push(start..end);
13974 }
13975
13976 let is_multiline_trim = trimmed_selections.len() > 1;
13977 let mut selection_len: usize = 0;
13978 let prev_selection_was_entire_line = is_entire_line && !is_multiline_trim;
13979
13980 for trimmed_range in trimmed_selections {
13981 if is_first {
13982 is_first = false;
13983 } else if is_multiline_trim || !prev_selection_was_entire_line {
13984 text.push('\n');
13985 if is_multiline_trim {
13986 selection_len += 1;
13987 }
13988 }
13989 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13990 text.push_str(chunk);
13991 selection_len += chunk.len();
13992 }
13993 if add_trailing_newline {
13994 text.push('\n');
13995 selection_len += 1;
13996 }
13997 }
13998
13999 clipboard_selections.push(ClipboardSelection::for_buffer(
14000 selection_len,
14001 is_entire_line,
14002 start..end,
14003 &buffer,
14004 self.project.as_ref(),
14005 cx,
14006 ));
14007 }
14008
14009 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
14010 text,
14011 clipboard_selections,
14012 ));
14013 }
14014
14015 pub fn do_paste(
14016 &mut self,
14017 text: &String,
14018 clipboard_selections: Option<Vec<ClipboardSelection>>,
14019 handle_entire_lines: bool,
14020 window: &mut Window,
14021 cx: &mut Context<Self>,
14022 ) {
14023 if self.read_only(cx) {
14024 return;
14025 }
14026
14027 let clipboard_text = Cow::Borrowed(text.as_str());
14028
14029 self.transact(window, cx, |this, window, cx| {
14030 let had_active_edit_prediction = this.has_active_edit_prediction();
14031 let display_map = this.display_snapshot(cx);
14032 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
14033 let cursor_offset = this
14034 .selections
14035 .last::<MultiBufferOffset>(&display_map)
14036 .head();
14037
14038 if let Some(mut clipboard_selections) = clipboard_selections {
14039 let all_selections_were_entire_line =
14040 clipboard_selections.iter().all(|s| s.is_entire_line);
14041 let first_selection_indent_column =
14042 clipboard_selections.first().map(|s| s.first_line_indent);
14043 if clipboard_selections.len() != old_selections.len() {
14044 clipboard_selections.drain(..);
14045 }
14046 let mut auto_indent_on_paste = true;
14047
14048 this.buffer.update(cx, |buffer, cx| {
14049 let snapshot = buffer.read(cx);
14050 auto_indent_on_paste = snapshot
14051 .language_settings_at(cursor_offset, cx)
14052 .auto_indent_on_paste;
14053
14054 let mut start_offset = 0;
14055 let mut edits = Vec::new();
14056 let mut original_indent_columns = Vec::new();
14057 for (ix, selection) in old_selections.iter().enumerate() {
14058 let to_insert;
14059 let entire_line;
14060 let original_indent_column;
14061 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
14062 let end_offset = start_offset + clipboard_selection.len;
14063 to_insert = &clipboard_text[start_offset..end_offset];
14064 entire_line = clipboard_selection.is_entire_line;
14065 start_offset = if entire_line {
14066 end_offset
14067 } else {
14068 end_offset + 1
14069 };
14070 original_indent_column = Some(clipboard_selection.first_line_indent);
14071 } else {
14072 to_insert = &*clipboard_text;
14073 entire_line = all_selections_were_entire_line;
14074 original_indent_column = first_selection_indent_column
14075 }
14076
14077 let (range, to_insert) =
14078 if selection.is_empty() && handle_entire_lines && entire_line {
14079 // If the corresponding selection was empty when this slice of the
14080 // clipboard text was written, then the entire line containing the
14081 // selection was copied. If this selection is also currently empty,
14082 // then paste the line before the current line of the buffer.
14083 let column = selection.start.to_point(&snapshot).column as usize;
14084 let line_start = selection.start - column;
14085 (line_start..line_start, Cow::Borrowed(to_insert))
14086 } else {
14087 let language = snapshot.language_at(selection.head());
14088 let range = selection.range();
14089 if let Some(language) = language
14090 && language.name() == "Markdown"
14091 {
14092 edit_for_markdown_paste(
14093 &snapshot,
14094 range,
14095 to_insert,
14096 url::Url::parse(to_insert).ok(),
14097 )
14098 } else {
14099 (range, Cow::Borrowed(to_insert))
14100 }
14101 };
14102
14103 edits.push((range, to_insert));
14104 original_indent_columns.push(original_indent_column);
14105 }
14106 drop(snapshot);
14107
14108 buffer.edit(
14109 edits,
14110 if auto_indent_on_paste {
14111 Some(AutoindentMode::Block {
14112 original_indent_columns,
14113 })
14114 } else {
14115 None
14116 },
14117 cx,
14118 );
14119 });
14120
14121 let selections = this
14122 .selections
14123 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
14124 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14125 } else {
14126 let url = url::Url::parse(&clipboard_text).ok();
14127
14128 let auto_indent_mode = if !clipboard_text.is_empty() {
14129 Some(AutoindentMode::Block {
14130 original_indent_columns: Vec::new(),
14131 })
14132 } else {
14133 None
14134 };
14135
14136 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
14137 let snapshot = buffer.snapshot(cx);
14138
14139 let anchors = old_selections
14140 .iter()
14141 .map(|s| {
14142 let anchor = snapshot.anchor_after(s.head());
14143 s.map(|_| anchor)
14144 })
14145 .collect::<Vec<_>>();
14146
14147 let mut edits = Vec::new();
14148
14149 // When pasting text without metadata (e.g. copied from an
14150 // external editor using multiple cursors) and the number of
14151 // lines matches the number of selections, distribute one
14152 // line per cursor instead of pasting the whole text at each.
14153 let lines: Vec<&str> = clipboard_text.split('\n').collect();
14154 let distribute_lines =
14155 old_selections.len() > 1 && lines.len() == old_selections.len();
14156
14157 for (ix, selection) in old_selections.iter().enumerate() {
14158 let language = snapshot.language_at(selection.head());
14159 let range = selection.range();
14160
14161 let text_for_cursor: &str = if distribute_lines {
14162 lines[ix]
14163 } else {
14164 &clipboard_text
14165 };
14166
14167 let (edit_range, edit_text) = if let Some(language) = language
14168 && language.name() == "Markdown"
14169 {
14170 edit_for_markdown_paste(&snapshot, range, text_for_cursor, url.clone())
14171 } else {
14172 (range, Cow::Borrowed(text_for_cursor))
14173 };
14174
14175 edits.push((edit_range, edit_text));
14176 }
14177
14178 drop(snapshot);
14179 buffer.edit(edits, auto_indent_mode, cx);
14180
14181 anchors
14182 });
14183
14184 this.change_selections(Default::default(), window, cx, |s| {
14185 s.select_anchors(selection_anchors);
14186 });
14187 }
14188
14189 // 🤔 | .. | show_in_menu |
14190 // | .. | true true
14191 // | had_edit_prediction | false true
14192
14193 let trigger_in_words =
14194 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
14195
14196 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
14197 });
14198 }
14199
14200 pub fn diff_clipboard_with_selection(
14201 &mut self,
14202 _: &DiffClipboardWithSelection,
14203 window: &mut Window,
14204 cx: &mut Context<Self>,
14205 ) {
14206 let selections = self
14207 .selections
14208 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
14209
14210 if selections.is_empty() {
14211 log::warn!("There should always be at least one selection in Zed. This is a bug.");
14212 return;
14213 };
14214
14215 let clipboard_text = cx.read_from_clipboard().and_then(|item| {
14216 item.entries().iter().find_map(|entry| match entry {
14217 ClipboardEntry::String(text) => Some(text.text().to_string()),
14218 _ => None,
14219 })
14220 });
14221
14222 let Some(clipboard_text) = clipboard_text else {
14223 log::warn!("Clipboard doesn't contain text.");
14224 return;
14225 };
14226
14227 window.dispatch_action(
14228 Box::new(DiffClipboardWithSelectionData {
14229 clipboard_text,
14230 editor: cx.entity(),
14231 }),
14232 cx,
14233 );
14234 }
14235
14236 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
14237 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14238 if let Some(item) = cx.read_from_clipboard() {
14239 let clipboard_string = item.entries().iter().find_map(|entry| match entry {
14240 ClipboardEntry::String(s) => Some(s),
14241 _ => None,
14242 });
14243 match clipboard_string {
14244 Some(clipboard_string) => self.do_paste(
14245 clipboard_string.text(),
14246 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
14247 true,
14248 window,
14249 cx,
14250 ),
14251 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
14252 }
14253 }
14254 }
14255
14256 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
14257 if self.read_only(cx) {
14258 return;
14259 }
14260
14261 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14262
14263 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
14264 if let Some((selections, _)) =
14265 self.selection_history.transaction(transaction_id).cloned()
14266 {
14267 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14268 s.select_anchors(selections.to_vec());
14269 });
14270 } else {
14271 log::error!(
14272 "No entry in selection_history found for undo. \
14273 This may correspond to a bug where undo does not update the selection. \
14274 If this is occurring, please add details to \
14275 https://github.com/zed-industries/zed/issues/22692"
14276 );
14277 }
14278 self.request_autoscroll(Autoscroll::fit(), cx);
14279 self.unmark_text(window, cx);
14280 self.refresh_edit_prediction(true, false, window, cx);
14281 cx.emit(EditorEvent::Edited { transaction_id });
14282 cx.emit(EditorEvent::TransactionUndone { transaction_id });
14283 }
14284 }
14285
14286 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
14287 if self.read_only(cx) {
14288 return;
14289 }
14290
14291 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14292
14293 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
14294 if let Some((_, Some(selections))) =
14295 self.selection_history.transaction(transaction_id).cloned()
14296 {
14297 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14298 s.select_anchors(selections.to_vec());
14299 });
14300 } else {
14301 log::error!(
14302 "No entry in selection_history found for redo. \
14303 This may correspond to a bug where undo does not update the selection. \
14304 If this is occurring, please add details to \
14305 https://github.com/zed-industries/zed/issues/22692"
14306 );
14307 }
14308 self.request_autoscroll(Autoscroll::fit(), cx);
14309 self.unmark_text(window, cx);
14310 self.refresh_edit_prediction(true, false, window, cx);
14311 cx.emit(EditorEvent::Edited { transaction_id });
14312 }
14313 }
14314
14315 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
14316 self.buffer
14317 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
14318 }
14319
14320 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
14321 self.buffer
14322 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
14323 }
14324
14325 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
14326 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14327 self.change_selections(Default::default(), window, cx, |s| {
14328 s.move_with(&mut |map, selection| {
14329 let cursor = if selection.is_empty() {
14330 movement::left(map, selection.start)
14331 } else {
14332 selection.start
14333 };
14334 selection.collapse_to(cursor, SelectionGoal::None);
14335 });
14336 })
14337 }
14338
14339 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
14340 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14341 self.change_selections(Default::default(), window, cx, |s| {
14342 s.move_heads_with(&mut |map, head, _| (movement::left(map, head), SelectionGoal::None));
14343 })
14344 }
14345
14346 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
14347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14348 self.change_selections(Default::default(), window, cx, |s| {
14349 s.move_with(&mut |map, selection| {
14350 let cursor = if selection.is_empty() {
14351 movement::right(map, selection.end)
14352 } else {
14353 selection.end
14354 };
14355 selection.collapse_to(cursor, SelectionGoal::None)
14356 });
14357 })
14358 }
14359
14360 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
14361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14362 self.change_selections(Default::default(), window, cx, |s| {
14363 s.move_heads_with(&mut |map, head, _| {
14364 (movement::right(map, head), SelectionGoal::None)
14365 });
14366 });
14367 }
14368
14369 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
14370 if self.take_rename(true, window, cx).is_some() {
14371 return;
14372 }
14373
14374 if self.mode.is_single_line() {
14375 cx.propagate();
14376 return;
14377 }
14378
14379 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14380
14381 let text_layout_details = &self.text_layout_details(window, cx);
14382 let selection_count = self.selections.count();
14383 let first_selection = self.selections.first_anchor();
14384
14385 self.change_selections(Default::default(), window, cx, |s| {
14386 s.move_with(&mut |map, selection| {
14387 if !selection.is_empty() {
14388 selection.goal = SelectionGoal::None;
14389 }
14390 let (cursor, goal) = movement::up(
14391 map,
14392 selection.start,
14393 selection.goal,
14394 false,
14395 text_layout_details,
14396 );
14397 selection.collapse_to(cursor, goal);
14398 });
14399 });
14400
14401 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14402 {
14403 cx.propagate();
14404 }
14405 }
14406
14407 pub fn move_up_by_lines(
14408 &mut self,
14409 action: &MoveUpByLines,
14410 window: &mut Window,
14411 cx: &mut Context<Self>,
14412 ) {
14413 if self.take_rename(true, window, cx).is_some() {
14414 return;
14415 }
14416
14417 if self.mode.is_single_line() {
14418 cx.propagate();
14419 return;
14420 }
14421
14422 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14423
14424 let text_layout_details = &self.text_layout_details(window, cx);
14425
14426 self.change_selections(Default::default(), window, cx, |s| {
14427 s.move_with(&mut |map, selection| {
14428 if !selection.is_empty() {
14429 selection.goal = SelectionGoal::None;
14430 }
14431 let (cursor, goal) = movement::up_by_rows(
14432 map,
14433 selection.start,
14434 action.lines,
14435 selection.goal,
14436 false,
14437 text_layout_details,
14438 );
14439 selection.collapse_to(cursor, goal);
14440 });
14441 })
14442 }
14443
14444 pub fn move_down_by_lines(
14445 &mut self,
14446 action: &MoveDownByLines,
14447 window: &mut Window,
14448 cx: &mut Context<Self>,
14449 ) {
14450 if self.take_rename(true, window, cx).is_some() {
14451 return;
14452 }
14453
14454 if self.mode.is_single_line() {
14455 cx.propagate();
14456 return;
14457 }
14458
14459 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14460
14461 let text_layout_details = &self.text_layout_details(window, cx);
14462
14463 self.change_selections(Default::default(), window, cx, |s| {
14464 s.move_with(&mut |map, selection| {
14465 if !selection.is_empty() {
14466 selection.goal = SelectionGoal::None;
14467 }
14468 let (cursor, goal) = movement::down_by_rows(
14469 map,
14470 selection.start,
14471 action.lines,
14472 selection.goal,
14473 false,
14474 text_layout_details,
14475 );
14476 selection.collapse_to(cursor, goal);
14477 });
14478 })
14479 }
14480
14481 pub fn select_down_by_lines(
14482 &mut self,
14483 action: &SelectDownByLines,
14484 window: &mut Window,
14485 cx: &mut Context<Self>,
14486 ) {
14487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14488 let text_layout_details = &self.text_layout_details(window, cx);
14489 self.change_selections(Default::default(), window, cx, |s| {
14490 s.move_heads_with(&mut |map, head, goal| {
14491 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
14492 })
14493 })
14494 }
14495
14496 pub fn select_up_by_lines(
14497 &mut self,
14498 action: &SelectUpByLines,
14499 window: &mut Window,
14500 cx: &mut Context<Self>,
14501 ) {
14502 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14503 let text_layout_details = &self.text_layout_details(window, cx);
14504 self.change_selections(Default::default(), window, cx, |s| {
14505 s.move_heads_with(&mut |map, head, goal| {
14506 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
14507 })
14508 })
14509 }
14510
14511 pub fn select_page_up(
14512 &mut self,
14513 _: &SelectPageUp,
14514 window: &mut Window,
14515 cx: &mut Context<Self>,
14516 ) {
14517 let Some(row_count) = self.visible_row_count() else {
14518 return;
14519 };
14520
14521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14522
14523 let text_layout_details = &self.text_layout_details(window, cx);
14524
14525 self.change_selections(Default::default(), window, cx, |s| {
14526 s.move_heads_with(&mut |map, head, goal| {
14527 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
14528 })
14529 })
14530 }
14531
14532 pub fn move_page_up(
14533 &mut self,
14534 action: &MovePageUp,
14535 window: &mut Window,
14536 cx: &mut Context<Self>,
14537 ) {
14538 if self.take_rename(true, window, cx).is_some() {
14539 return;
14540 }
14541
14542 if self
14543 .context_menu
14544 .borrow_mut()
14545 .as_mut()
14546 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
14547 .unwrap_or(false)
14548 {
14549 return;
14550 }
14551
14552 if matches!(self.mode, EditorMode::SingleLine) {
14553 cx.propagate();
14554 return;
14555 }
14556
14557 let Some(row_count) = self.visible_row_count() else {
14558 return;
14559 };
14560
14561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14562
14563 let effects = if action.center_cursor {
14564 SelectionEffects::scroll(Autoscroll::center())
14565 } else {
14566 SelectionEffects::default()
14567 };
14568
14569 let text_layout_details = &self.text_layout_details(window, cx);
14570
14571 self.change_selections(effects, window, cx, |s| {
14572 s.move_with(&mut |map, selection| {
14573 if !selection.is_empty() {
14574 selection.goal = SelectionGoal::None;
14575 }
14576 let (cursor, goal) = movement::up_by_rows(
14577 map,
14578 selection.end,
14579 row_count,
14580 selection.goal,
14581 false,
14582 text_layout_details,
14583 );
14584 selection.collapse_to(cursor, goal);
14585 });
14586 });
14587 }
14588
14589 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
14590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14591 let text_layout_details = &self.text_layout_details(window, cx);
14592 self.change_selections(Default::default(), window, cx, |s| {
14593 s.move_heads_with(&mut |map, head, goal| {
14594 movement::up(map, head, goal, false, text_layout_details)
14595 })
14596 })
14597 }
14598
14599 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
14600 self.take_rename(true, window, cx);
14601
14602 if self.mode.is_single_line() {
14603 cx.propagate();
14604 return;
14605 }
14606
14607 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14608
14609 let text_layout_details = &self.text_layout_details(window, cx);
14610 let selection_count = self.selections.count();
14611 let first_selection = self.selections.first_anchor();
14612
14613 self.change_selections(Default::default(), window, cx, |s| {
14614 s.move_with(&mut |map, selection| {
14615 if !selection.is_empty() {
14616 selection.goal = SelectionGoal::None;
14617 }
14618 let (cursor, goal) = movement::down(
14619 map,
14620 selection.end,
14621 selection.goal,
14622 false,
14623 text_layout_details,
14624 );
14625 selection.collapse_to(cursor, goal);
14626 });
14627 });
14628
14629 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
14630 {
14631 cx.propagate();
14632 }
14633 }
14634
14635 pub fn select_page_down(
14636 &mut self,
14637 _: &SelectPageDown,
14638 window: &mut Window,
14639 cx: &mut Context<Self>,
14640 ) {
14641 let Some(row_count) = self.visible_row_count() else {
14642 return;
14643 };
14644
14645 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14646
14647 let text_layout_details = &self.text_layout_details(window, cx);
14648
14649 self.change_selections(Default::default(), window, cx, |s| {
14650 s.move_heads_with(&mut |map, head, goal| {
14651 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
14652 })
14653 })
14654 }
14655
14656 pub fn move_page_down(
14657 &mut self,
14658 action: &MovePageDown,
14659 window: &mut Window,
14660 cx: &mut Context<Self>,
14661 ) {
14662 if self.take_rename(true, window, cx).is_some() {
14663 return;
14664 }
14665
14666 if self
14667 .context_menu
14668 .borrow_mut()
14669 .as_mut()
14670 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
14671 .unwrap_or(false)
14672 {
14673 return;
14674 }
14675
14676 if matches!(self.mode, EditorMode::SingleLine) {
14677 cx.propagate();
14678 return;
14679 }
14680
14681 let Some(row_count) = self.visible_row_count() else {
14682 return;
14683 };
14684
14685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14686
14687 let effects = if action.center_cursor {
14688 SelectionEffects::scroll(Autoscroll::center())
14689 } else {
14690 SelectionEffects::default()
14691 };
14692
14693 let text_layout_details = &self.text_layout_details(window, cx);
14694 self.change_selections(effects, window, cx, |s| {
14695 s.move_with(&mut |map, selection| {
14696 if !selection.is_empty() {
14697 selection.goal = SelectionGoal::None;
14698 }
14699 let (cursor, goal) = movement::down_by_rows(
14700 map,
14701 selection.end,
14702 row_count,
14703 selection.goal,
14704 false,
14705 text_layout_details,
14706 );
14707 selection.collapse_to(cursor, goal);
14708 });
14709 });
14710 }
14711
14712 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
14713 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14714 let text_layout_details = &self.text_layout_details(window, cx);
14715 self.change_selections(Default::default(), window, cx, |s| {
14716 s.move_heads_with(&mut |map, head, goal| {
14717 movement::down(map, head, goal, false, text_layout_details)
14718 })
14719 });
14720 }
14721
14722 pub fn context_menu_first(
14723 &mut self,
14724 _: &ContextMenuFirst,
14725 window: &mut Window,
14726 cx: &mut Context<Self>,
14727 ) {
14728 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14729 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14730 }
14731 }
14732
14733 pub fn context_menu_prev(
14734 &mut self,
14735 _: &ContextMenuPrevious,
14736 window: &mut Window,
14737 cx: &mut Context<Self>,
14738 ) {
14739 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14740 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14741 }
14742 }
14743
14744 pub fn context_menu_next(
14745 &mut self,
14746 _: &ContextMenuNext,
14747 window: &mut Window,
14748 cx: &mut Context<Self>,
14749 ) {
14750 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14751 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14752 }
14753 }
14754
14755 pub fn context_menu_last(
14756 &mut self,
14757 _: &ContextMenuLast,
14758 window: &mut Window,
14759 cx: &mut Context<Self>,
14760 ) {
14761 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14762 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14763 }
14764 }
14765
14766 pub fn signature_help_prev(
14767 &mut self,
14768 _: &SignatureHelpPrevious,
14769 _: &mut Window,
14770 cx: &mut Context<Self>,
14771 ) {
14772 if let Some(popover) = self.signature_help_state.popover_mut() {
14773 if popover.current_signature == 0 {
14774 popover.current_signature = popover.signatures.len() - 1;
14775 } else {
14776 popover.current_signature -= 1;
14777 }
14778 cx.notify();
14779 }
14780 }
14781
14782 pub fn signature_help_next(
14783 &mut self,
14784 _: &SignatureHelpNext,
14785 _: &mut Window,
14786 cx: &mut Context<Self>,
14787 ) {
14788 if let Some(popover) = self.signature_help_state.popover_mut() {
14789 if popover.current_signature + 1 == popover.signatures.len() {
14790 popover.current_signature = 0;
14791 } else {
14792 popover.current_signature += 1;
14793 }
14794 cx.notify();
14795 }
14796 }
14797
14798 pub fn move_to_previous_word_start(
14799 &mut self,
14800 _: &MoveToPreviousWordStart,
14801 window: &mut Window,
14802 cx: &mut Context<Self>,
14803 ) {
14804 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14805 self.change_selections(Default::default(), window, cx, |s| {
14806 s.move_cursors_with(&mut |map, head, _| {
14807 (
14808 movement::previous_word_start(map, head),
14809 SelectionGoal::None,
14810 )
14811 });
14812 })
14813 }
14814
14815 pub fn move_to_previous_subword_start(
14816 &mut self,
14817 _: &MoveToPreviousSubwordStart,
14818 window: &mut Window,
14819 cx: &mut Context<Self>,
14820 ) {
14821 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14822 self.change_selections(Default::default(), window, cx, |s| {
14823 s.move_cursors_with(&mut |map, head, _| {
14824 (
14825 movement::previous_subword_start(map, head),
14826 SelectionGoal::None,
14827 )
14828 });
14829 })
14830 }
14831
14832 pub fn select_to_previous_word_start(
14833 &mut self,
14834 _: &SelectToPreviousWordStart,
14835 window: &mut Window,
14836 cx: &mut Context<Self>,
14837 ) {
14838 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14839 self.change_selections(Default::default(), window, cx, |s| {
14840 s.move_heads_with(&mut |map, head, _| {
14841 (
14842 movement::previous_word_start(map, head),
14843 SelectionGoal::None,
14844 )
14845 });
14846 })
14847 }
14848
14849 pub fn select_to_previous_subword_start(
14850 &mut self,
14851 _: &SelectToPreviousSubwordStart,
14852 window: &mut Window,
14853 cx: &mut Context<Self>,
14854 ) {
14855 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14856 self.change_selections(Default::default(), window, cx, |s| {
14857 s.move_heads_with(&mut |map, head, _| {
14858 (
14859 movement::previous_subword_start(map, head),
14860 SelectionGoal::None,
14861 )
14862 });
14863 })
14864 }
14865
14866 pub fn delete_to_previous_word_start(
14867 &mut self,
14868 action: &DeleteToPreviousWordStart,
14869 window: &mut Window,
14870 cx: &mut Context<Self>,
14871 ) {
14872 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14873 self.transact(window, cx, |this, window, cx| {
14874 this.select_autoclose_pair(window, cx);
14875 this.change_selections(Default::default(), window, cx, |s| {
14876 s.move_with(&mut |map, selection| {
14877 if selection.is_empty() {
14878 let mut cursor = if action.ignore_newlines {
14879 movement::previous_word_start(map, selection.head())
14880 } else {
14881 movement::previous_word_start_or_newline(map, selection.head())
14882 };
14883 cursor = movement::adjust_greedy_deletion(
14884 map,
14885 selection.head(),
14886 cursor,
14887 action.ignore_brackets,
14888 );
14889 selection.set_head(cursor, SelectionGoal::None);
14890 }
14891 });
14892 });
14893 this.insert("", window, cx);
14894 });
14895 }
14896
14897 pub fn delete_to_previous_subword_start(
14898 &mut self,
14899 action: &DeleteToPreviousSubwordStart,
14900 window: &mut Window,
14901 cx: &mut Context<Self>,
14902 ) {
14903 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14904 self.transact(window, cx, |this, window, cx| {
14905 this.select_autoclose_pair(window, cx);
14906 this.change_selections(Default::default(), window, cx, |s| {
14907 s.move_with(&mut |map, selection| {
14908 if selection.is_empty() {
14909 let mut cursor = if action.ignore_newlines {
14910 movement::previous_subword_start(map, selection.head())
14911 } else {
14912 movement::previous_subword_start_or_newline(map, selection.head())
14913 };
14914 cursor = movement::adjust_greedy_deletion(
14915 map,
14916 selection.head(),
14917 cursor,
14918 action.ignore_brackets,
14919 );
14920 selection.set_head(cursor, SelectionGoal::None);
14921 }
14922 });
14923 });
14924 this.insert("", window, cx);
14925 });
14926 }
14927
14928 pub fn move_to_next_word_end(
14929 &mut self,
14930 _: &MoveToNextWordEnd,
14931 window: &mut Window,
14932 cx: &mut Context<Self>,
14933 ) {
14934 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14935 self.change_selections(Default::default(), window, cx, |s| {
14936 s.move_cursors_with(&mut |map, head, _| {
14937 (movement::next_word_end(map, head), SelectionGoal::None)
14938 });
14939 })
14940 }
14941
14942 pub fn move_to_next_subword_end(
14943 &mut self,
14944 _: &MoveToNextSubwordEnd,
14945 window: &mut Window,
14946 cx: &mut Context<Self>,
14947 ) {
14948 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14949 self.change_selections(Default::default(), window, cx, |s| {
14950 s.move_cursors_with(&mut |map, head, _| {
14951 (movement::next_subword_end(map, head), SelectionGoal::None)
14952 });
14953 })
14954 }
14955
14956 pub fn select_to_next_word_end(
14957 &mut self,
14958 _: &SelectToNextWordEnd,
14959 window: &mut Window,
14960 cx: &mut Context<Self>,
14961 ) {
14962 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14963 self.change_selections(Default::default(), window, cx, |s| {
14964 s.move_heads_with(&mut |map, head, _| {
14965 (movement::next_word_end(map, head), SelectionGoal::None)
14966 });
14967 })
14968 }
14969
14970 pub fn select_to_next_subword_end(
14971 &mut self,
14972 _: &SelectToNextSubwordEnd,
14973 window: &mut Window,
14974 cx: &mut Context<Self>,
14975 ) {
14976 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14977 self.change_selections(Default::default(), window, cx, |s| {
14978 s.move_heads_with(&mut |map, head, _| {
14979 (movement::next_subword_end(map, head), SelectionGoal::None)
14980 });
14981 })
14982 }
14983
14984 pub fn delete_to_next_word_end(
14985 &mut self,
14986 action: &DeleteToNextWordEnd,
14987 window: &mut Window,
14988 cx: &mut Context<Self>,
14989 ) {
14990 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14991 self.transact(window, cx, |this, window, cx| {
14992 this.change_selections(Default::default(), window, cx, |s| {
14993 s.move_with(&mut |map, selection| {
14994 if selection.is_empty() {
14995 let mut cursor = if action.ignore_newlines {
14996 movement::next_word_end(map, selection.head())
14997 } else {
14998 movement::next_word_end_or_newline(map, selection.head())
14999 };
15000 cursor = movement::adjust_greedy_deletion(
15001 map,
15002 selection.head(),
15003 cursor,
15004 action.ignore_brackets,
15005 );
15006 selection.set_head(cursor, SelectionGoal::None);
15007 }
15008 });
15009 });
15010 this.insert("", window, cx);
15011 });
15012 }
15013
15014 pub fn delete_to_next_subword_end(
15015 &mut self,
15016 action: &DeleteToNextSubwordEnd,
15017 window: &mut Window,
15018 cx: &mut Context<Self>,
15019 ) {
15020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15021 self.transact(window, cx, |this, window, cx| {
15022 this.change_selections(Default::default(), window, cx, |s| {
15023 s.move_with(&mut |map, selection| {
15024 if selection.is_empty() {
15025 let mut cursor = if action.ignore_newlines {
15026 movement::next_subword_end(map, selection.head())
15027 } else {
15028 movement::next_subword_end_or_newline(map, selection.head())
15029 };
15030 cursor = movement::adjust_greedy_deletion(
15031 map,
15032 selection.head(),
15033 cursor,
15034 action.ignore_brackets,
15035 );
15036 selection.set_head(cursor, SelectionGoal::None);
15037 }
15038 });
15039 });
15040 this.insert("", window, cx);
15041 });
15042 }
15043
15044 pub fn move_to_beginning_of_line(
15045 &mut self,
15046 action: &MoveToBeginningOfLine,
15047 window: &mut Window,
15048 cx: &mut Context<Self>,
15049 ) {
15050 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15051 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15052 self.change_selections(Default::default(), window, cx, |s| {
15053 s.move_cursors_with(&mut |map, head, _| {
15054 (
15055 movement::indented_line_beginning(
15056 map,
15057 head,
15058 action.stop_at_soft_wraps,
15059 stop_at_indent,
15060 ),
15061 SelectionGoal::None,
15062 )
15063 });
15064 })
15065 }
15066
15067 pub fn select_to_beginning_of_line(
15068 &mut self,
15069 action: &SelectToBeginningOfLine,
15070 window: &mut Window,
15071 cx: &mut Context<Self>,
15072 ) {
15073 let stop_at_indent = action.stop_at_indent && !self.mode.is_single_line();
15074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15075 self.change_selections(Default::default(), window, cx, |s| {
15076 s.move_heads_with(&mut |map, head, _| {
15077 (
15078 movement::indented_line_beginning(
15079 map,
15080 head,
15081 action.stop_at_soft_wraps,
15082 stop_at_indent,
15083 ),
15084 SelectionGoal::None,
15085 )
15086 });
15087 });
15088 }
15089
15090 pub fn delete_to_beginning_of_line(
15091 &mut self,
15092 action: &DeleteToBeginningOfLine,
15093 window: &mut Window,
15094 cx: &mut Context<Self>,
15095 ) {
15096 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15097 self.transact(window, cx, |this, window, cx| {
15098 this.change_selections(Default::default(), window, cx, |s| {
15099 s.move_with(&mut |_, selection| {
15100 selection.reversed = true;
15101 });
15102 });
15103
15104 this.select_to_beginning_of_line(
15105 &SelectToBeginningOfLine {
15106 stop_at_soft_wraps: false,
15107 stop_at_indent: action.stop_at_indent,
15108 },
15109 window,
15110 cx,
15111 );
15112 this.backspace(&Backspace, window, cx);
15113 });
15114 }
15115
15116 pub fn move_to_end_of_line(
15117 &mut self,
15118 action: &MoveToEndOfLine,
15119 window: &mut Window,
15120 cx: &mut Context<Self>,
15121 ) {
15122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15123 self.change_selections(Default::default(), window, cx, |s| {
15124 s.move_cursors_with(&mut |map, head, _| {
15125 (
15126 movement::line_end(map, head, action.stop_at_soft_wraps),
15127 SelectionGoal::None,
15128 )
15129 });
15130 })
15131 }
15132
15133 pub fn select_to_end_of_line(
15134 &mut self,
15135 action: &SelectToEndOfLine,
15136 window: &mut Window,
15137 cx: &mut Context<Self>,
15138 ) {
15139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15140 self.change_selections(Default::default(), window, cx, |s| {
15141 s.move_heads_with(&mut |map, head, _| {
15142 (
15143 movement::line_end(map, head, action.stop_at_soft_wraps),
15144 SelectionGoal::None,
15145 )
15146 });
15147 })
15148 }
15149
15150 pub fn delete_to_end_of_line(
15151 &mut self,
15152 _: &DeleteToEndOfLine,
15153 window: &mut Window,
15154 cx: &mut Context<Self>,
15155 ) {
15156 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15157 self.transact(window, cx, |this, window, cx| {
15158 this.select_to_end_of_line(
15159 &SelectToEndOfLine {
15160 stop_at_soft_wraps: false,
15161 },
15162 window,
15163 cx,
15164 );
15165 this.delete(&Delete, window, cx);
15166 });
15167 }
15168
15169 pub fn cut_to_end_of_line(
15170 &mut self,
15171 action: &CutToEndOfLine,
15172 window: &mut Window,
15173 cx: &mut Context<Self>,
15174 ) {
15175 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15176 self.transact(window, cx, |this, window, cx| {
15177 this.select_to_end_of_line(
15178 &SelectToEndOfLine {
15179 stop_at_soft_wraps: false,
15180 },
15181 window,
15182 cx,
15183 );
15184 if !action.stop_at_newlines {
15185 this.change_selections(Default::default(), window, cx, |s| {
15186 s.move_with(&mut |_, sel| {
15187 if sel.is_empty() {
15188 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
15189 }
15190 });
15191 });
15192 }
15193 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15194 let item = this.cut_common(false, window, cx);
15195 cx.write_to_clipboard(item);
15196 });
15197 }
15198
15199 pub fn move_to_start_of_paragraph(
15200 &mut self,
15201 _: &MoveToStartOfParagraph,
15202 window: &mut Window,
15203 cx: &mut Context<Self>,
15204 ) {
15205 if matches!(self.mode, EditorMode::SingleLine) {
15206 cx.propagate();
15207 return;
15208 }
15209 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15210 self.change_selections(Default::default(), window, cx, |s| {
15211 s.move_with(&mut |map, selection| {
15212 selection.collapse_to(
15213 movement::start_of_paragraph(map, selection.head(), 1),
15214 SelectionGoal::None,
15215 )
15216 });
15217 })
15218 }
15219
15220 pub fn move_to_end_of_paragraph(
15221 &mut self,
15222 _: &MoveToEndOfParagraph,
15223 window: &mut Window,
15224 cx: &mut Context<Self>,
15225 ) {
15226 if matches!(self.mode, EditorMode::SingleLine) {
15227 cx.propagate();
15228 return;
15229 }
15230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15231 self.change_selections(Default::default(), window, cx, |s| {
15232 s.move_with(&mut |map, selection| {
15233 selection.collapse_to(
15234 movement::end_of_paragraph(map, selection.head(), 1),
15235 SelectionGoal::None,
15236 )
15237 });
15238 })
15239 }
15240
15241 pub fn select_to_start_of_paragraph(
15242 &mut self,
15243 _: &SelectToStartOfParagraph,
15244 window: &mut Window,
15245 cx: &mut Context<Self>,
15246 ) {
15247 if matches!(self.mode, EditorMode::SingleLine) {
15248 cx.propagate();
15249 return;
15250 }
15251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15252 self.change_selections(Default::default(), window, cx, |s| {
15253 s.move_heads_with(&mut |map, head, _| {
15254 (
15255 movement::start_of_paragraph(map, head, 1),
15256 SelectionGoal::None,
15257 )
15258 });
15259 })
15260 }
15261
15262 pub fn select_to_end_of_paragraph(
15263 &mut self,
15264 _: &SelectToEndOfParagraph,
15265 window: &mut Window,
15266 cx: &mut Context<Self>,
15267 ) {
15268 if matches!(self.mode, EditorMode::SingleLine) {
15269 cx.propagate();
15270 return;
15271 }
15272 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15273 self.change_selections(Default::default(), window, cx, |s| {
15274 s.move_heads_with(&mut |map, head, _| {
15275 (
15276 movement::end_of_paragraph(map, head, 1),
15277 SelectionGoal::None,
15278 )
15279 });
15280 })
15281 }
15282
15283 pub fn move_to_start_of_excerpt(
15284 &mut self,
15285 _: &MoveToStartOfExcerpt,
15286 window: &mut Window,
15287 cx: &mut Context<Self>,
15288 ) {
15289 if matches!(self.mode, EditorMode::SingleLine) {
15290 cx.propagate();
15291 return;
15292 }
15293 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15294 self.change_selections(Default::default(), window, cx, |s| {
15295 s.move_with(&mut |map, selection| {
15296 selection.collapse_to(
15297 movement::start_of_excerpt(
15298 map,
15299 selection.head(),
15300 workspace::searchable::Direction::Prev,
15301 ),
15302 SelectionGoal::None,
15303 )
15304 });
15305 })
15306 }
15307
15308 pub fn move_to_start_of_next_excerpt(
15309 &mut self,
15310 _: &MoveToStartOfNextExcerpt,
15311 window: &mut Window,
15312 cx: &mut Context<Self>,
15313 ) {
15314 if matches!(self.mode, EditorMode::SingleLine) {
15315 cx.propagate();
15316 return;
15317 }
15318
15319 self.change_selections(Default::default(), window, cx, |s| {
15320 s.move_with(&mut |map, selection| {
15321 selection.collapse_to(
15322 movement::start_of_excerpt(
15323 map,
15324 selection.head(),
15325 workspace::searchable::Direction::Next,
15326 ),
15327 SelectionGoal::None,
15328 )
15329 });
15330 })
15331 }
15332
15333 pub fn move_to_end_of_excerpt(
15334 &mut self,
15335 _: &MoveToEndOfExcerpt,
15336 window: &mut Window,
15337 cx: &mut Context<Self>,
15338 ) {
15339 if matches!(self.mode, EditorMode::SingleLine) {
15340 cx.propagate();
15341 return;
15342 }
15343 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15344 self.change_selections(Default::default(), window, cx, |s| {
15345 s.move_with(&mut |map, selection| {
15346 selection.collapse_to(
15347 movement::end_of_excerpt(
15348 map,
15349 selection.head(),
15350 workspace::searchable::Direction::Next,
15351 ),
15352 SelectionGoal::None,
15353 )
15354 });
15355 })
15356 }
15357
15358 pub fn move_to_end_of_previous_excerpt(
15359 &mut self,
15360 _: &MoveToEndOfPreviousExcerpt,
15361 window: &mut Window,
15362 cx: &mut Context<Self>,
15363 ) {
15364 if matches!(self.mode, EditorMode::SingleLine) {
15365 cx.propagate();
15366 return;
15367 }
15368 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15369 self.change_selections(Default::default(), window, cx, |s| {
15370 s.move_with(&mut |map, selection| {
15371 selection.collapse_to(
15372 movement::end_of_excerpt(
15373 map,
15374 selection.head(),
15375 workspace::searchable::Direction::Prev,
15376 ),
15377 SelectionGoal::None,
15378 )
15379 });
15380 })
15381 }
15382
15383 pub fn select_to_start_of_excerpt(
15384 &mut self,
15385 _: &SelectToStartOfExcerpt,
15386 window: &mut Window,
15387 cx: &mut Context<Self>,
15388 ) {
15389 if matches!(self.mode, EditorMode::SingleLine) {
15390 cx.propagate();
15391 return;
15392 }
15393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15394 self.change_selections(Default::default(), window, cx, |s| {
15395 s.move_heads_with(&mut |map, head, _| {
15396 (
15397 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15398 SelectionGoal::None,
15399 )
15400 });
15401 })
15402 }
15403
15404 pub fn select_to_start_of_next_excerpt(
15405 &mut self,
15406 _: &SelectToStartOfNextExcerpt,
15407 window: &mut Window,
15408 cx: &mut Context<Self>,
15409 ) {
15410 if matches!(self.mode, EditorMode::SingleLine) {
15411 cx.propagate();
15412 return;
15413 }
15414 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15415 self.change_selections(Default::default(), window, cx, |s| {
15416 s.move_heads_with(&mut |map, head, _| {
15417 (
15418 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
15419 SelectionGoal::None,
15420 )
15421 });
15422 })
15423 }
15424
15425 pub fn select_to_end_of_excerpt(
15426 &mut self,
15427 _: &SelectToEndOfExcerpt,
15428 window: &mut Window,
15429 cx: &mut Context<Self>,
15430 ) {
15431 if matches!(self.mode, EditorMode::SingleLine) {
15432 cx.propagate();
15433 return;
15434 }
15435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15436 self.change_selections(Default::default(), window, cx, |s| {
15437 s.move_heads_with(&mut |map, head, _| {
15438 (
15439 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
15440 SelectionGoal::None,
15441 )
15442 });
15443 })
15444 }
15445
15446 pub fn select_to_end_of_previous_excerpt(
15447 &mut self,
15448 _: &SelectToEndOfPreviousExcerpt,
15449 window: &mut Window,
15450 cx: &mut Context<Self>,
15451 ) {
15452 if matches!(self.mode, EditorMode::SingleLine) {
15453 cx.propagate();
15454 return;
15455 }
15456 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15457 self.change_selections(Default::default(), window, cx, |s| {
15458 s.move_heads_with(&mut |map, head, _| {
15459 (
15460 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
15461 SelectionGoal::None,
15462 )
15463 });
15464 })
15465 }
15466
15467 pub fn move_to_beginning(
15468 &mut self,
15469 _: &MoveToBeginning,
15470 window: &mut Window,
15471 cx: &mut Context<Self>,
15472 ) {
15473 if matches!(self.mode, EditorMode::SingleLine) {
15474 cx.propagate();
15475 return;
15476 }
15477 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15478 self.change_selections(Default::default(), window, cx, |s| {
15479 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
15480 });
15481 }
15482
15483 pub fn select_to_beginning(
15484 &mut self,
15485 _: &SelectToBeginning,
15486 window: &mut Window,
15487 cx: &mut Context<Self>,
15488 ) {
15489 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
15490 selection.set_head(Point::zero(), SelectionGoal::None);
15491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15492 self.change_selections(Default::default(), window, cx, |s| {
15493 s.select(vec![selection]);
15494 });
15495 }
15496
15497 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
15498 if matches!(self.mode, EditorMode::SingleLine) {
15499 cx.propagate();
15500 return;
15501 }
15502 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15503 let cursor = self.buffer.read(cx).read(cx).len();
15504 self.change_selections(Default::default(), window, cx, |s| {
15505 s.select_ranges(vec![cursor..cursor])
15506 });
15507 }
15508
15509 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
15510 self.nav_history = nav_history;
15511 }
15512
15513 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
15514 self.nav_history.as_ref()
15515 }
15516
15517 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
15518 self.push_to_nav_history(
15519 self.selections.newest_anchor().head(),
15520 None,
15521 false,
15522 true,
15523 cx,
15524 );
15525 }
15526
15527 fn navigation_data(&self, cursor_anchor: Anchor, cx: &mut Context<Self>) -> NavigationData {
15528 let display_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15529 let buffer = self.buffer.read(cx).read(cx);
15530 let cursor_position = cursor_anchor.to_point(&buffer);
15531 let scroll_anchor = self.scroll_manager.native_anchor(&display_snapshot, cx);
15532 let scroll_top_row = scroll_anchor.top_row(&buffer);
15533 drop(buffer);
15534
15535 NavigationData {
15536 cursor_anchor,
15537 cursor_position,
15538 scroll_anchor,
15539 scroll_top_row,
15540 }
15541 }
15542
15543 fn navigation_entry(
15544 &self,
15545 cursor_anchor: Anchor,
15546 cx: &mut Context<Self>,
15547 ) -> Option<NavigationEntry> {
15548 let Some(history) = self.nav_history.clone() else {
15549 return None;
15550 };
15551 let data = self.navigation_data(cursor_anchor, cx);
15552 Some(history.navigation_entry(Some(Arc::new(data) as Arc<dyn Any + Send + Sync>)))
15553 }
15554
15555 fn push_to_nav_history(
15556 &mut self,
15557 cursor_anchor: Anchor,
15558 new_position: Option<Point>,
15559 is_deactivate: bool,
15560 always: bool,
15561 cx: &mut Context<Self>,
15562 ) {
15563 let data = self.navigation_data(cursor_anchor, cx);
15564 if let Some(nav_history) = self.nav_history.as_mut() {
15565 if let Some(new_position) = new_position {
15566 let row_delta = (new_position.row as i64 - data.cursor_position.row as i64).abs();
15567 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
15568 return;
15569 }
15570 }
15571
15572 let cursor_row = data.cursor_position.row;
15573 nav_history.push(Some(data), Some(cursor_row), cx);
15574 cx.emit(EditorEvent::PushedToNavHistory {
15575 anchor: cursor_anchor,
15576 is_deactivate,
15577 })
15578 }
15579 }
15580
15581 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
15582 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15583 let buffer = self.buffer.read(cx).snapshot(cx);
15584 let mut selection = self
15585 .selections
15586 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
15587 selection.set_head(buffer.len(), SelectionGoal::None);
15588 self.change_selections(Default::default(), window, cx, |s| {
15589 s.select(vec![selection]);
15590 });
15591 }
15592
15593 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
15594 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15595 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15596 s.select_ranges([Anchor::min()..Anchor::max()]);
15597 });
15598 }
15599
15600 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
15601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15602 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15603 let mut selections = self.selections.all::<Point>(&display_map);
15604 let max_point = display_map.buffer_snapshot().max_point();
15605 for selection in &mut selections {
15606 let rows = selection.spanned_rows(true, &display_map);
15607 selection.start = Point::new(rows.start.0, 0);
15608 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
15609 selection.reversed = false;
15610 }
15611 self.change_selections(Default::default(), window, cx, |s| {
15612 s.select(selections);
15613 });
15614 }
15615
15616 pub fn split_selection_into_lines(
15617 &mut self,
15618 action: &SplitSelectionIntoLines,
15619 window: &mut Window,
15620 cx: &mut Context<Self>,
15621 ) {
15622 let selections = self
15623 .selections
15624 .all::<Point>(&self.display_snapshot(cx))
15625 .into_iter()
15626 .map(|selection| selection.start..selection.end)
15627 .collect::<Vec<_>>();
15628 self.unfold_ranges(&selections, true, false, cx);
15629
15630 let mut new_selection_ranges = Vec::new();
15631 {
15632 let buffer = self.buffer.read(cx).read(cx);
15633 for selection in selections {
15634 for row in selection.start.row..selection.end.row {
15635 let line_start = Point::new(row, 0);
15636 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
15637
15638 if action.keep_selections {
15639 // Keep the selection range for each line
15640 let selection_start = if row == selection.start.row {
15641 selection.start
15642 } else {
15643 line_start
15644 };
15645 new_selection_ranges.push(selection_start..line_end);
15646 } else {
15647 // Collapse to cursor at end of line
15648 new_selection_ranges.push(line_end..line_end);
15649 }
15650 }
15651
15652 let is_multiline_selection = selection.start.row != selection.end.row;
15653 // Don't insert last one if it's a multi-line selection ending at the start of a line,
15654 // so this action feels more ergonomic when paired with other selection operations
15655 let should_skip_last = is_multiline_selection && selection.end.column == 0;
15656 if !should_skip_last {
15657 if action.keep_selections {
15658 if is_multiline_selection {
15659 let line_start = Point::new(selection.end.row, 0);
15660 new_selection_ranges.push(line_start..selection.end);
15661 } else {
15662 new_selection_ranges.push(selection.start..selection.end);
15663 }
15664 } else {
15665 new_selection_ranges.push(selection.end..selection.end);
15666 }
15667 }
15668 }
15669 }
15670 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15671 s.select_ranges(new_selection_ranges);
15672 });
15673 }
15674
15675 pub fn add_selection_above(
15676 &mut self,
15677 action: &AddSelectionAbove,
15678 window: &mut Window,
15679 cx: &mut Context<Self>,
15680 ) {
15681 self.add_selection(true, action.skip_soft_wrap, window, cx);
15682 }
15683
15684 pub fn add_selection_below(
15685 &mut self,
15686 action: &AddSelectionBelow,
15687 window: &mut Window,
15688 cx: &mut Context<Self>,
15689 ) {
15690 self.add_selection(false, action.skip_soft_wrap, window, cx);
15691 }
15692
15693 fn add_selection(
15694 &mut self,
15695 above: bool,
15696 skip_soft_wrap: bool,
15697 window: &mut Window,
15698 cx: &mut Context<Self>,
15699 ) {
15700 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15701
15702 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15703 let all_selections = self.selections.all::<Point>(&display_map);
15704 let text_layout_details = self.text_layout_details(window, cx);
15705
15706 let (mut columnar_selections, new_selections_to_columnarize) = {
15707 if let Some(state) = self.add_selections_state.as_ref() {
15708 let columnar_selection_ids: HashSet<_> = state
15709 .groups
15710 .iter()
15711 .flat_map(|group| group.stack.iter())
15712 .copied()
15713 .collect();
15714
15715 all_selections
15716 .into_iter()
15717 .partition(|s| columnar_selection_ids.contains(&s.id))
15718 } else {
15719 (Vec::new(), all_selections)
15720 }
15721 };
15722
15723 let mut state = self
15724 .add_selections_state
15725 .take()
15726 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
15727
15728 for selection in new_selections_to_columnarize {
15729 let range = selection.display_range(&display_map).sorted();
15730 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
15731 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
15732 let positions = start_x.min(end_x)..start_x.max(end_x);
15733 let mut stack = Vec::new();
15734 for row in range.start.row().0..=range.end.row().0 {
15735 if let Some(selection) = self.selections.build_columnar_selection(
15736 &display_map,
15737 DisplayRow(row),
15738 &positions,
15739 selection.reversed,
15740 &text_layout_details,
15741 ) {
15742 stack.push(selection.id);
15743 columnar_selections.push(selection);
15744 }
15745 }
15746 if !stack.is_empty() {
15747 if above {
15748 stack.reverse();
15749 }
15750 state.groups.push(AddSelectionsGroup { above, stack });
15751 }
15752 }
15753
15754 let mut final_selections = Vec::new();
15755 let end_row = if above {
15756 DisplayRow(0)
15757 } else {
15758 display_map.max_point().row()
15759 };
15760
15761 // When `skip_soft_wrap` is true, we use UTF-16 columns instead of pixel
15762 // positions to place new selections, so we need to keep track of the
15763 // column range of the oldest selection in each group, because
15764 // intermediate selections may have been clamped to shorter lines.
15765 let mut goal_columns_by_selection_id = if skip_soft_wrap {
15766 let mut map = HashMap::default();
15767 for group in state.groups.iter() {
15768 if let Some(oldest_id) = group.stack.first() {
15769 if let Some(oldest_selection) =
15770 columnar_selections.iter().find(|s| s.id == *oldest_id)
15771 {
15772 let snapshot = display_map.buffer_snapshot();
15773 let start_col =
15774 snapshot.point_to_point_utf16(oldest_selection.start).column;
15775 let end_col = snapshot.point_to_point_utf16(oldest_selection.end).column;
15776 let goal_columns = start_col.min(end_col)..start_col.max(end_col);
15777 for id in &group.stack {
15778 map.insert(*id, goal_columns.clone());
15779 }
15780 }
15781 }
15782 }
15783 map
15784 } else {
15785 HashMap::default()
15786 };
15787
15788 let mut last_added_item_per_group = HashMap::default();
15789 for group in state.groups.iter_mut() {
15790 if let Some(last_id) = group.stack.last() {
15791 last_added_item_per_group.insert(*last_id, group);
15792 }
15793 }
15794
15795 for selection in columnar_selections {
15796 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15797 if above == group.above {
15798 let range = selection.display_range(&display_map).sorted();
15799 debug_assert_eq!(range.start.row(), range.end.row());
15800 let row = range.start.row();
15801 let positions =
15802 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15803 Pixels::from(start)..Pixels::from(end)
15804 } else {
15805 let start_x =
15806 display_map.x_for_display_point(range.start, &text_layout_details);
15807 let end_x =
15808 display_map.x_for_display_point(range.end, &text_layout_details);
15809 start_x.min(end_x)..start_x.max(end_x)
15810 };
15811
15812 let maybe_new_selection = if skip_soft_wrap {
15813 let goal_columns = goal_columns_by_selection_id
15814 .remove(&selection.id)
15815 .unwrap_or_else(|| {
15816 let snapshot = display_map.buffer_snapshot();
15817 let start_col =
15818 snapshot.point_to_point_utf16(selection.start).column;
15819 let end_col = snapshot.point_to_point_utf16(selection.end).column;
15820 start_col.min(end_col)..start_col.max(end_col)
15821 });
15822 self.selections.find_next_columnar_selection_by_buffer_row(
15823 &display_map,
15824 row,
15825 end_row,
15826 above,
15827 &goal_columns,
15828 selection.reversed,
15829 &text_layout_details,
15830 )
15831 } else {
15832 self.selections.find_next_columnar_selection_by_display_row(
15833 &display_map,
15834 row,
15835 end_row,
15836 above,
15837 &positions,
15838 selection.reversed,
15839 &text_layout_details,
15840 )
15841 };
15842
15843 if let Some(new_selection) = maybe_new_selection {
15844 group.stack.push(new_selection.id);
15845 if above {
15846 final_selections.push(new_selection);
15847 final_selections.push(selection);
15848 } else {
15849 final_selections.push(selection);
15850 final_selections.push(new_selection);
15851 }
15852 } else {
15853 final_selections.push(selection);
15854 }
15855 } else {
15856 group.stack.pop();
15857 }
15858 } else {
15859 final_selections.push(selection);
15860 }
15861 }
15862
15863 self.change_selections(Default::default(), window, cx, |s| {
15864 s.select(final_selections);
15865 });
15866
15867 let final_selection_ids: HashSet<_> = self
15868 .selections
15869 .all::<Point>(&display_map)
15870 .iter()
15871 .map(|s| s.id)
15872 .collect();
15873 state.groups.retain_mut(|group| {
15874 // selections might get merged above so we remove invalid items from stacks
15875 group.stack.retain(|id| final_selection_ids.contains(id));
15876
15877 // single selection in stack can be treated as initial state
15878 group.stack.len() > 1
15879 });
15880
15881 if !state.groups.is_empty() {
15882 self.add_selections_state = Some(state);
15883 }
15884 }
15885
15886 pub fn insert_snippet_at_selections(
15887 &mut self,
15888 action: &InsertSnippet,
15889 window: &mut Window,
15890 cx: &mut Context<Self>,
15891 ) {
15892 self.try_insert_snippet_at_selections(action, window, cx)
15893 .log_err();
15894 }
15895
15896 fn try_insert_snippet_at_selections(
15897 &mut self,
15898 action: &InsertSnippet,
15899 window: &mut Window,
15900 cx: &mut Context<Self>,
15901 ) -> Result<()> {
15902 let insertion_ranges = self
15903 .selections
15904 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15905 .into_iter()
15906 .map(|selection| selection.range())
15907 .collect_vec();
15908
15909 let snippet = if let Some(snippet_body) = &action.snippet {
15910 if action.language.is_none() && action.name.is_none() {
15911 Snippet::parse(snippet_body)?
15912 } else {
15913 bail!("`snippet` is mutually exclusive with `language` and `name`")
15914 }
15915 } else if let Some(name) = &action.name {
15916 let project = self.project().context("no project")?;
15917 let snippet_store = project.read(cx).snippets().read(cx);
15918 let snippet = snippet_store
15919 .snippets_for(action.language.clone(), cx)
15920 .into_iter()
15921 .find(|snippet| snippet.name == *name)
15922 .context("snippet not found")?;
15923 Snippet::parse(&snippet.body)?
15924 } else {
15925 // todo(andrew): open modal to select snippet
15926 bail!("`name` or `snippet` is required")
15927 };
15928
15929 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15930 }
15931
15932 fn select_match_ranges(
15933 &mut self,
15934 range: Range<MultiBufferOffset>,
15935 reversed: bool,
15936 replace_newest: bool,
15937 auto_scroll: Option<Autoscroll>,
15938 window: &mut Window,
15939 cx: &mut Context<Editor>,
15940 ) {
15941 self.unfold_ranges(
15942 std::slice::from_ref(&range),
15943 false,
15944 auto_scroll.is_some(),
15945 cx,
15946 );
15947 let effects = if let Some(scroll) = auto_scroll {
15948 SelectionEffects::scroll(scroll)
15949 } else {
15950 SelectionEffects::no_scroll()
15951 };
15952 self.change_selections(effects, window, cx, |s| {
15953 if replace_newest {
15954 s.delete(s.newest_anchor().id);
15955 }
15956 if reversed {
15957 s.insert_range(range.end..range.start);
15958 } else {
15959 s.insert_range(range);
15960 }
15961 });
15962 }
15963
15964 pub fn select_next_match_internal(
15965 &mut self,
15966 display_map: &DisplaySnapshot,
15967 replace_newest: bool,
15968 autoscroll: Option<Autoscroll>,
15969 window: &mut Window,
15970 cx: &mut Context<Self>,
15971 ) -> Result<()> {
15972 let buffer = display_map.buffer_snapshot();
15973 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15974 if let Some(mut select_next_state) = self.select_next_state.take() {
15975 let query = &select_next_state.query;
15976 if !select_next_state.done {
15977 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15978 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15979 let mut next_selected_range = None;
15980
15981 let bytes_after_last_selection =
15982 buffer.bytes_in_range(last_selection.end..buffer.len());
15983 let bytes_before_first_selection =
15984 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15985 let query_matches = query
15986 .stream_find_iter(bytes_after_last_selection)
15987 .map(|result| (last_selection.end, result))
15988 .chain(
15989 query
15990 .stream_find_iter(bytes_before_first_selection)
15991 .map(|result| (MultiBufferOffset(0), result)),
15992 );
15993
15994 for (start_offset, query_match) in query_matches {
15995 let query_match = query_match.unwrap(); // can only fail due to I/O
15996 let offset_range =
15997 start_offset + query_match.start()..start_offset + query_match.end();
15998
15999 if !select_next_state.wordwise
16000 || (!buffer.is_inside_word(offset_range.start, None)
16001 && !buffer.is_inside_word(offset_range.end, None))
16002 {
16003 let idx = selections
16004 .partition_point(|selection| selection.end <= offset_range.start);
16005 let overlaps = selections
16006 .get(idx)
16007 .map_or(false, |selection| selection.start < offset_range.end);
16008
16009 if !overlaps {
16010 next_selected_range = Some(offset_range);
16011 break;
16012 }
16013 }
16014 }
16015
16016 if let Some(next_selected_range) = next_selected_range {
16017 self.select_match_ranges(
16018 next_selected_range,
16019 last_selection.reversed,
16020 replace_newest,
16021 autoscroll,
16022 window,
16023 cx,
16024 );
16025 } else {
16026 select_next_state.done = true;
16027 }
16028 }
16029
16030 self.select_next_state = Some(select_next_state);
16031 } else {
16032 let mut only_carets = true;
16033 let mut same_text_selected = true;
16034 let mut selected_text = None;
16035
16036 let mut selections_iter = selections.iter().peekable();
16037 while let Some(selection) = selections_iter.next() {
16038 if selection.start != selection.end {
16039 only_carets = false;
16040 }
16041
16042 if same_text_selected {
16043 if selected_text.is_none() {
16044 selected_text =
16045 Some(buffer.text_for_range(selection.range()).collect::<String>());
16046 }
16047
16048 if let Some(next_selection) = selections_iter.peek() {
16049 if next_selection.len() == selection.len() {
16050 let next_selected_text = buffer
16051 .text_for_range(next_selection.range())
16052 .collect::<String>();
16053 if Some(next_selected_text) != selected_text {
16054 same_text_selected = false;
16055 selected_text = None;
16056 }
16057 } else {
16058 same_text_selected = false;
16059 selected_text = None;
16060 }
16061 }
16062 }
16063 }
16064
16065 if only_carets {
16066 for selection in &mut selections {
16067 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16068 selection.start = word_range.start;
16069 selection.end = word_range.end;
16070 selection.goal = SelectionGoal::None;
16071 selection.reversed = false;
16072 self.select_match_ranges(
16073 selection.start..selection.end,
16074 selection.reversed,
16075 replace_newest,
16076 autoscroll,
16077 window,
16078 cx,
16079 );
16080 }
16081
16082 if selections.len() == 1 {
16083 let selection = selections
16084 .last()
16085 .expect("ensured that there's only one selection");
16086 let query = buffer
16087 .text_for_range(selection.start..selection.end)
16088 .collect::<String>();
16089 let is_empty = query.is_empty();
16090 let select_state = SelectNextState {
16091 query: self.build_query(&[query], cx)?,
16092 wordwise: true,
16093 done: is_empty,
16094 };
16095 self.select_next_state = Some(select_state);
16096 } else {
16097 self.select_next_state = None;
16098 }
16099 } else if let Some(selected_text) = selected_text {
16100 self.select_next_state = Some(SelectNextState {
16101 query: self.build_query(&[selected_text], cx)?,
16102 wordwise: false,
16103 done: false,
16104 });
16105 self.select_next_match_internal(
16106 display_map,
16107 replace_newest,
16108 autoscroll,
16109 window,
16110 cx,
16111 )?;
16112 }
16113 }
16114 Ok(())
16115 }
16116
16117 pub fn select_all_matches(
16118 &mut self,
16119 _action: &SelectAllMatches,
16120 window: &mut Window,
16121 cx: &mut Context<Self>,
16122 ) -> Result<()> {
16123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16124
16125 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16126
16127 self.select_next_match_internal(&display_map, false, None, window, cx)?;
16128 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
16129 else {
16130 return Ok(());
16131 };
16132
16133 let mut new_selections = Vec::new();
16134 let initial_selection = self.selections.oldest::<MultiBufferOffset>(&display_map);
16135 let reversed = initial_selection.reversed;
16136 let buffer = display_map.buffer_snapshot();
16137 let query_matches = select_next_state
16138 .query
16139 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
16140
16141 for query_match in query_matches.into_iter() {
16142 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
16143 let offset_range = if reversed {
16144 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
16145 } else {
16146 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
16147 };
16148
16149 let is_partial_word_match = select_next_state.wordwise
16150 && (buffer.is_inside_word(offset_range.start, None)
16151 || buffer.is_inside_word(offset_range.end, None));
16152
16153 let is_initial_selection = MultiBufferOffset(query_match.start())
16154 == initial_selection.start
16155 && MultiBufferOffset(query_match.end()) == initial_selection.end;
16156
16157 if !is_partial_word_match && !is_initial_selection {
16158 new_selections.push(offset_range);
16159 }
16160 }
16161
16162 // Ensure that the initial range is the last selection, as
16163 // `MutableSelectionsCollection::select_ranges` makes the last selection
16164 // the newest selection, which the editor then relies on as the primary
16165 // cursor for scroll targeting. Without this, the last match would then
16166 // be automatically focused when the user started editing the selected
16167 // matches.
16168 let initial_directed_range = if reversed {
16169 initial_selection.end..initial_selection.start
16170 } else {
16171 initial_selection.start..initial_selection.end
16172 };
16173 new_selections.push(initial_directed_range);
16174
16175 select_next_state.done = true;
16176 self.unfold_ranges(&new_selections, false, false, cx);
16177 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16178 selections.select_ranges(new_selections)
16179 });
16180
16181 Ok(())
16182 }
16183
16184 pub fn select_next(
16185 &mut self,
16186 action: &SelectNext,
16187 window: &mut Window,
16188 cx: &mut Context<Self>,
16189 ) -> Result<()> {
16190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16191 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16192 self.select_next_match_internal(
16193 &display_map,
16194 action.replace_newest,
16195 Some(Autoscroll::newest()),
16196 window,
16197 cx,
16198 )
16199 }
16200
16201 pub fn select_previous(
16202 &mut self,
16203 action: &SelectPrevious,
16204 window: &mut Window,
16205 cx: &mut Context<Self>,
16206 ) -> Result<()> {
16207 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16208 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16209 let buffer = display_map.buffer_snapshot();
16210 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
16211 if let Some(mut select_prev_state) = self.select_prev_state.take() {
16212 let query = &select_prev_state.query;
16213 if !select_prev_state.done {
16214 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
16215 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
16216 let mut next_selected_range = None;
16217 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
16218 let bytes_before_last_selection =
16219 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
16220 let bytes_after_first_selection =
16221 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
16222 let query_matches = query
16223 .stream_find_iter(bytes_before_last_selection)
16224 .map(|result| (last_selection.start, result))
16225 .chain(
16226 query
16227 .stream_find_iter(bytes_after_first_selection)
16228 .map(|result| (buffer.len(), result)),
16229 );
16230 for (end_offset, query_match) in query_matches {
16231 let query_match = query_match.unwrap(); // can only fail due to I/O
16232 let offset_range =
16233 end_offset - query_match.end()..end_offset - query_match.start();
16234
16235 if !select_prev_state.wordwise
16236 || (!buffer.is_inside_word(offset_range.start, None)
16237 && !buffer.is_inside_word(offset_range.end, None))
16238 {
16239 next_selected_range = Some(offset_range);
16240 break;
16241 }
16242 }
16243
16244 if let Some(next_selected_range) = next_selected_range {
16245 self.select_match_ranges(
16246 next_selected_range,
16247 last_selection.reversed,
16248 action.replace_newest,
16249 Some(Autoscroll::newest()),
16250 window,
16251 cx,
16252 );
16253 } else {
16254 select_prev_state.done = true;
16255 }
16256 }
16257
16258 self.select_prev_state = Some(select_prev_state);
16259 } else {
16260 let mut only_carets = true;
16261 let mut same_text_selected = true;
16262 let mut selected_text = None;
16263
16264 let mut selections_iter = selections.iter().peekable();
16265 while let Some(selection) = selections_iter.next() {
16266 if selection.start != selection.end {
16267 only_carets = false;
16268 }
16269
16270 if same_text_selected {
16271 if selected_text.is_none() {
16272 selected_text =
16273 Some(buffer.text_for_range(selection.range()).collect::<String>());
16274 }
16275
16276 if let Some(next_selection) = selections_iter.peek() {
16277 if next_selection.len() == selection.len() {
16278 let next_selected_text = buffer
16279 .text_for_range(next_selection.range())
16280 .collect::<String>();
16281 if Some(next_selected_text) != selected_text {
16282 same_text_selected = false;
16283 selected_text = None;
16284 }
16285 } else {
16286 same_text_selected = false;
16287 selected_text = None;
16288 }
16289 }
16290 }
16291 }
16292
16293 if only_carets {
16294 for selection in &mut selections {
16295 let (word_range, _) = buffer.surrounding_word(selection.start, None);
16296 selection.start = word_range.start;
16297 selection.end = word_range.end;
16298 selection.goal = SelectionGoal::None;
16299 selection.reversed = false;
16300 self.select_match_ranges(
16301 selection.start..selection.end,
16302 selection.reversed,
16303 action.replace_newest,
16304 Some(Autoscroll::newest()),
16305 window,
16306 cx,
16307 );
16308 }
16309 if selections.len() == 1 {
16310 let selection = selections
16311 .last()
16312 .expect("ensured that there's only one selection");
16313 let query = buffer
16314 .text_for_range(selection.start..selection.end)
16315 .collect::<String>();
16316 let is_empty = query.is_empty();
16317 let select_state = SelectNextState {
16318 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
16319 wordwise: true,
16320 done: is_empty,
16321 };
16322 self.select_prev_state = Some(select_state);
16323 } else {
16324 self.select_prev_state = None;
16325 }
16326 } else if let Some(selected_text) = selected_text {
16327 self.select_prev_state = Some(SelectNextState {
16328 query: self
16329 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
16330 wordwise: false,
16331 done: false,
16332 });
16333 self.select_previous(action, window, cx)?;
16334 }
16335 }
16336 Ok(())
16337 }
16338
16339 /// Builds an `AhoCorasick` automaton from the provided patterns, while
16340 /// setting the case sensitivity based on the global
16341 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
16342 /// editor's settings.
16343 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
16344 where
16345 I: IntoIterator<Item = P>,
16346 P: AsRef<[u8]>,
16347 {
16348 let case_sensitive = self
16349 .select_next_is_case_sensitive
16350 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
16351
16352 let mut builder = AhoCorasickBuilder::new();
16353 builder.ascii_case_insensitive(!case_sensitive);
16354 builder.build(patterns)
16355 }
16356
16357 pub fn find_next_match(
16358 &mut self,
16359 _: &FindNextMatch,
16360 window: &mut Window,
16361 cx: &mut Context<Self>,
16362 ) -> Result<()> {
16363 let selections = self.selections.disjoint_anchors_arc();
16364 match selections.first() {
16365 Some(first) if selections.len() >= 2 => {
16366 self.change_selections(Default::default(), window, cx, |s| {
16367 s.select_ranges([first.range()]);
16368 });
16369 }
16370 _ => self.select_next(
16371 &SelectNext {
16372 replace_newest: true,
16373 },
16374 window,
16375 cx,
16376 )?,
16377 }
16378 Ok(())
16379 }
16380
16381 pub fn find_previous_match(
16382 &mut self,
16383 _: &FindPreviousMatch,
16384 window: &mut Window,
16385 cx: &mut Context<Self>,
16386 ) -> Result<()> {
16387 let selections = self.selections.disjoint_anchors_arc();
16388 match selections.last() {
16389 Some(last) if selections.len() >= 2 => {
16390 self.change_selections(Default::default(), window, cx, |s| {
16391 s.select_ranges([last.range()]);
16392 });
16393 }
16394 _ => self.select_previous(
16395 &SelectPrevious {
16396 replace_newest: true,
16397 },
16398 window,
16399 cx,
16400 )?,
16401 }
16402 Ok(())
16403 }
16404
16405 pub fn toggle_comments(
16406 &mut self,
16407 action: &ToggleComments,
16408 window: &mut Window,
16409 cx: &mut Context<Self>,
16410 ) {
16411 if self.read_only(cx) {
16412 return;
16413 }
16414 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
16415 let text_layout_details = &self.text_layout_details(window, cx);
16416 self.transact(window, cx, |this, window, cx| {
16417 let mut selections = this
16418 .selections
16419 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
16420 let mut edits = Vec::new();
16421 let mut selection_edit_ranges = Vec::new();
16422 let mut last_toggled_row = None;
16423 let snapshot = this.buffer.read(cx).read(cx);
16424 let empty_str: Arc<str> = Arc::default();
16425 let mut suffixes_inserted = Vec::new();
16426 let ignore_indent = action.ignore_indent;
16427
16428 fn comment_prefix_range(
16429 snapshot: &MultiBufferSnapshot,
16430 row: MultiBufferRow,
16431 comment_prefix: &str,
16432 comment_prefix_whitespace: &str,
16433 ignore_indent: bool,
16434 ) -> Range<Point> {
16435 let indent_size = if ignore_indent {
16436 0
16437 } else {
16438 snapshot.indent_size_for_line(row).len
16439 };
16440
16441 let start = Point::new(row.0, indent_size);
16442
16443 let mut line_bytes = snapshot
16444 .bytes_in_range(start..snapshot.max_point())
16445 .flatten()
16446 .copied();
16447
16448 // If this line currently begins with the line comment prefix, then record
16449 // the range containing the prefix.
16450 if line_bytes
16451 .by_ref()
16452 .take(comment_prefix.len())
16453 .eq(comment_prefix.bytes())
16454 {
16455 // Include any whitespace that matches the comment prefix.
16456 let matching_whitespace_len = line_bytes
16457 .zip(comment_prefix_whitespace.bytes())
16458 .take_while(|(a, b)| a == b)
16459 .count() as u32;
16460 let end = Point::new(
16461 start.row,
16462 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
16463 );
16464 start..end
16465 } else {
16466 start..start
16467 }
16468 }
16469
16470 fn comment_suffix_range(
16471 snapshot: &MultiBufferSnapshot,
16472 row: MultiBufferRow,
16473 comment_suffix: &str,
16474 comment_suffix_has_leading_space: bool,
16475 ) -> Range<Point> {
16476 let end = Point::new(row.0, snapshot.line_len(row));
16477 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
16478
16479 let mut line_end_bytes = snapshot
16480 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
16481 .flatten()
16482 .copied();
16483
16484 let leading_space_len = if suffix_start_column > 0
16485 && line_end_bytes.next() == Some(b' ')
16486 && comment_suffix_has_leading_space
16487 {
16488 1
16489 } else {
16490 0
16491 };
16492
16493 // If this line currently begins with the line comment prefix, then record
16494 // the range containing the prefix.
16495 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
16496 let start = Point::new(end.row, suffix_start_column - leading_space_len);
16497 start..end
16498 } else {
16499 end..end
16500 }
16501 }
16502
16503 // TODO: Handle selections that cross excerpts
16504 for selection in &mut selections {
16505 let start_column = snapshot
16506 .indent_size_for_line(MultiBufferRow(selection.start.row))
16507 .len;
16508 let language = if let Some(language) =
16509 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
16510 {
16511 language
16512 } else {
16513 continue;
16514 };
16515
16516 selection_edit_ranges.clear();
16517
16518 // If multiple selections contain a given row, avoid processing that
16519 // row more than once.
16520 let mut start_row = MultiBufferRow(selection.start.row);
16521 if last_toggled_row == Some(start_row) {
16522 start_row = start_row.next_row();
16523 }
16524 let end_row =
16525 if selection.end.row > selection.start.row && selection.end.column == 0 {
16526 MultiBufferRow(selection.end.row - 1)
16527 } else {
16528 MultiBufferRow(selection.end.row)
16529 };
16530 last_toggled_row = Some(end_row);
16531
16532 if start_row > end_row {
16533 continue;
16534 }
16535
16536 // If the language has line comments, toggle those.
16537 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
16538
16539 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
16540 if ignore_indent {
16541 full_comment_prefixes = full_comment_prefixes
16542 .into_iter()
16543 .map(|s| Arc::from(s.trim_end()))
16544 .collect();
16545 }
16546
16547 if !full_comment_prefixes.is_empty() {
16548 let first_prefix = full_comment_prefixes
16549 .first()
16550 .expect("prefixes is non-empty");
16551 let prefix_trimmed_lengths = full_comment_prefixes
16552 .iter()
16553 .map(|p| p.trim_end_matches(' ').len())
16554 .collect::<SmallVec<[usize; 4]>>();
16555
16556 let mut all_selection_lines_are_comments = true;
16557
16558 for row in start_row.0..=end_row.0 {
16559 let row = MultiBufferRow(row);
16560 if start_row < end_row && snapshot.is_line_blank(row) {
16561 continue;
16562 }
16563
16564 let prefix_range = full_comment_prefixes
16565 .iter()
16566 .zip(prefix_trimmed_lengths.iter().copied())
16567 .map(|(prefix, trimmed_prefix_len)| {
16568 comment_prefix_range(
16569 snapshot.deref(),
16570 row,
16571 &prefix[..trimmed_prefix_len],
16572 &prefix[trimmed_prefix_len..],
16573 ignore_indent,
16574 )
16575 })
16576 .max_by_key(|range| range.end.column - range.start.column)
16577 .expect("prefixes is non-empty");
16578
16579 if prefix_range.is_empty() {
16580 all_selection_lines_are_comments = false;
16581 }
16582
16583 selection_edit_ranges.push(prefix_range);
16584 }
16585
16586 if all_selection_lines_are_comments {
16587 edits.extend(
16588 selection_edit_ranges
16589 .iter()
16590 .cloned()
16591 .map(|range| (range, empty_str.clone())),
16592 );
16593 } else {
16594 let min_column = selection_edit_ranges
16595 .iter()
16596 .map(|range| range.start.column)
16597 .min()
16598 .unwrap_or(0);
16599 edits.extend(selection_edit_ranges.iter().map(|range| {
16600 let position = Point::new(range.start.row, min_column);
16601 (position..position, first_prefix.clone())
16602 }));
16603 }
16604 } else if let Some(BlockCommentConfig {
16605 start: full_comment_prefix,
16606 end: comment_suffix,
16607 ..
16608 }) = language.block_comment()
16609 {
16610 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
16611 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
16612 let prefix_range = comment_prefix_range(
16613 snapshot.deref(),
16614 start_row,
16615 comment_prefix,
16616 comment_prefix_whitespace,
16617 ignore_indent,
16618 );
16619 let suffix_range = comment_suffix_range(
16620 snapshot.deref(),
16621 end_row,
16622 comment_suffix.trim_start_matches(' '),
16623 comment_suffix.starts_with(' '),
16624 );
16625
16626 if prefix_range.is_empty() || suffix_range.is_empty() {
16627 edits.push((
16628 prefix_range.start..prefix_range.start,
16629 full_comment_prefix.clone(),
16630 ));
16631 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
16632 suffixes_inserted.push((end_row, comment_suffix.len()));
16633 } else {
16634 edits.push((prefix_range, empty_str.clone()));
16635 edits.push((suffix_range, empty_str.clone()));
16636 }
16637 } else {
16638 continue;
16639 }
16640 }
16641
16642 drop(snapshot);
16643 this.buffer.update(cx, |buffer, cx| {
16644 buffer.edit(edits, None, cx);
16645 });
16646
16647 // Adjust selections so that they end before any comment suffixes that
16648 // were inserted.
16649 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
16650 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16651 let snapshot = this.buffer.read(cx).read(cx);
16652 for selection in &mut selections {
16653 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
16654 match row.cmp(&MultiBufferRow(selection.end.row)) {
16655 Ordering::Less => {
16656 suffixes_inserted.next();
16657 continue;
16658 }
16659 Ordering::Greater => break,
16660 Ordering::Equal => {
16661 if selection.end.column == snapshot.line_len(row) {
16662 if selection.is_empty() {
16663 selection.start.column -= suffix_len as u32;
16664 }
16665 selection.end.column -= suffix_len as u32;
16666 }
16667 break;
16668 }
16669 }
16670 }
16671 }
16672
16673 drop(snapshot);
16674 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
16675
16676 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
16677 let selections_on_single_row = selections.windows(2).all(|selections| {
16678 selections[0].start.row == selections[1].start.row
16679 && selections[0].end.row == selections[1].end.row
16680 && selections[0].start.row == selections[0].end.row
16681 });
16682 let selections_selecting = selections
16683 .iter()
16684 .any(|selection| selection.start != selection.end);
16685 let advance_downwards = action.advance_downwards
16686 && selections_on_single_row
16687 && !selections_selecting
16688 && !matches!(this.mode, EditorMode::SingleLine);
16689
16690 if advance_downwards {
16691 let snapshot = this.buffer.read(cx).snapshot(cx);
16692
16693 this.change_selections(Default::default(), window, cx, |s| {
16694 s.move_cursors_with(&mut |display_snapshot, display_point, _| {
16695 let mut point = display_point.to_point(display_snapshot);
16696 point.row += 1;
16697 point = snapshot.clip_point(point, Bias::Left);
16698 let display_point = point.to_display_point(display_snapshot);
16699 let goal = SelectionGoal::HorizontalPosition(
16700 display_snapshot
16701 .x_for_display_point(display_point, text_layout_details)
16702 .into(),
16703 );
16704 (display_point, goal)
16705 })
16706 });
16707 }
16708 });
16709 }
16710
16711 pub fn select_enclosing_symbol(
16712 &mut self,
16713 _: &SelectEnclosingSymbol,
16714 window: &mut Window,
16715 cx: &mut Context<Self>,
16716 ) {
16717 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16718
16719 let buffer = self.buffer.read(cx).snapshot(cx);
16720 let old_selections = self
16721 .selections
16722 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16723 .into_boxed_slice();
16724
16725 fn update_selection(
16726 selection: &Selection<MultiBufferOffset>,
16727 buffer_snap: &MultiBufferSnapshot,
16728 ) -> Option<Selection<MultiBufferOffset>> {
16729 let cursor = selection.head();
16730 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
16731 for symbol in symbols.iter().rev() {
16732 let start = symbol.range.start.to_offset(buffer_snap);
16733 let end = symbol.range.end.to_offset(buffer_snap);
16734 let new_range = start..end;
16735 if start < selection.start || end > selection.end {
16736 return Some(Selection {
16737 id: selection.id,
16738 start: new_range.start,
16739 end: new_range.end,
16740 goal: SelectionGoal::None,
16741 reversed: selection.reversed,
16742 });
16743 }
16744 }
16745 None
16746 }
16747
16748 let mut selected_larger_symbol = false;
16749 let new_selections = old_selections
16750 .iter()
16751 .map(|selection| match update_selection(selection, &buffer) {
16752 Some(new_selection) => {
16753 if new_selection.range() != selection.range() {
16754 selected_larger_symbol = true;
16755 }
16756 new_selection
16757 }
16758 None => selection.clone(),
16759 })
16760 .collect::<Vec<_>>();
16761
16762 if selected_larger_symbol {
16763 self.change_selections(Default::default(), window, cx, |s| {
16764 s.select(new_selections);
16765 });
16766 }
16767 }
16768
16769 pub fn select_larger_syntax_node(
16770 &mut self,
16771 _: &SelectLargerSyntaxNode,
16772 window: &mut Window,
16773 cx: &mut Context<Self>,
16774 ) {
16775 let Some(visible_row_count) = self.visible_row_count() else {
16776 return;
16777 };
16778 let old_selections: Box<[_]> = self
16779 .selections
16780 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16781 .into();
16782 if old_selections.is_empty() {
16783 return;
16784 }
16785
16786 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16787
16788 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16789 let buffer = self.buffer.read(cx).snapshot(cx);
16790
16791 let mut selected_larger_node = false;
16792 let mut new_selections = old_selections
16793 .iter()
16794 .map(|selection| {
16795 let old_range = selection.start..selection.end;
16796
16797 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16798 // manually select word at selection
16799 if ["string_content", "inline"].contains(&node.kind()) {
16800 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16801 // ignore if word is already selected
16802 if !word_range.is_empty() && old_range != word_range {
16803 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16804 // only select word if start and end point belongs to same word
16805 if word_range == last_word_range {
16806 selected_larger_node = true;
16807 return Selection {
16808 id: selection.id,
16809 start: word_range.start,
16810 end: word_range.end,
16811 goal: SelectionGoal::None,
16812 reversed: selection.reversed,
16813 };
16814 }
16815 }
16816 }
16817 }
16818
16819 let mut new_range = old_range.clone();
16820 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16821 new_range = range;
16822 if !node.is_named() {
16823 continue;
16824 }
16825 if !display_map.intersects_fold(new_range.start)
16826 && !display_map.intersects_fold(new_range.end)
16827 {
16828 break;
16829 }
16830 }
16831
16832 selected_larger_node |= new_range != old_range;
16833 Selection {
16834 id: selection.id,
16835 start: new_range.start,
16836 end: new_range.end,
16837 goal: SelectionGoal::None,
16838 reversed: selection.reversed,
16839 }
16840 })
16841 .collect::<Vec<_>>();
16842
16843 if !selected_larger_node {
16844 return; // don't put this call in the history
16845 }
16846
16847 // scroll based on transformation done to the last selection created by the user
16848 let (last_old, last_new) = old_selections
16849 .last()
16850 .zip(new_selections.last().cloned())
16851 .expect("old_selections isn't empty");
16852
16853 let is_selection_reversed = if new_selections.len() == 1 {
16854 let should_be_reversed = last_old.start != last_new.start;
16855 new_selections.last_mut().expect("checked above").reversed = should_be_reversed;
16856 should_be_reversed
16857 } else {
16858 last_new.reversed
16859 };
16860
16861 if selected_larger_node {
16862 self.select_syntax_node_history.disable_clearing = true;
16863 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16864 s.select(new_selections.clone());
16865 });
16866 self.select_syntax_node_history.disable_clearing = false;
16867 }
16868
16869 let start_row = last_new.start.to_display_point(&display_map).row().0;
16870 let end_row = last_new.end.to_display_point(&display_map).row().0;
16871 let selection_height = end_row - start_row + 1;
16872 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16873
16874 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16875 let scroll_behavior = if fits_on_the_screen {
16876 self.request_autoscroll(Autoscroll::fit(), cx);
16877 SelectSyntaxNodeScrollBehavior::FitSelection
16878 } else if is_selection_reversed {
16879 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16880 SelectSyntaxNodeScrollBehavior::CursorTop
16881 } else {
16882 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16883 SelectSyntaxNodeScrollBehavior::CursorBottom
16884 };
16885
16886 let old_selections: Box<[Selection<Anchor>]> = old_selections
16887 .iter()
16888 .map(|s| s.map(|offset| buffer.anchor_before(offset)))
16889 .collect();
16890 self.select_syntax_node_history.push((
16891 old_selections,
16892 scroll_behavior,
16893 is_selection_reversed,
16894 ));
16895 }
16896
16897 pub fn select_smaller_syntax_node(
16898 &mut self,
16899 _: &SelectSmallerSyntaxNode,
16900 window: &mut Window,
16901 cx: &mut Context<Self>,
16902 ) {
16903 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16904
16905 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16906 self.select_syntax_node_history.pop()
16907 {
16908 if let Some(selection) = selections.last_mut() {
16909 selection.reversed = is_selection_reversed;
16910 }
16911
16912 let snapshot = self.buffer.read(cx).snapshot(cx);
16913 let selections: Vec<Selection<MultiBufferOffset>> = selections
16914 .iter()
16915 .map(|s| s.map(|anchor| anchor.to_offset(&snapshot)))
16916 .collect();
16917
16918 self.select_syntax_node_history.disable_clearing = true;
16919 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16920 s.select(selections);
16921 });
16922 self.select_syntax_node_history.disable_clearing = false;
16923
16924 match scroll_behavior {
16925 SelectSyntaxNodeScrollBehavior::CursorTop => {
16926 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16927 }
16928 SelectSyntaxNodeScrollBehavior::FitSelection => {
16929 self.request_autoscroll(Autoscroll::fit(), cx);
16930 }
16931 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16932 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16933 }
16934 }
16935 }
16936 }
16937
16938 pub fn unwrap_syntax_node(
16939 &mut self,
16940 _: &UnwrapSyntaxNode,
16941 window: &mut Window,
16942 cx: &mut Context<Self>,
16943 ) {
16944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16945
16946 let buffer = self.buffer.read(cx).snapshot(cx);
16947 let selections = self
16948 .selections
16949 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16950 .into_iter()
16951 // subtracting the offset requires sorting
16952 .sorted_by_key(|i| i.start);
16953
16954 let full_edits = selections
16955 .into_iter()
16956 .filter_map(|selection| {
16957 let child = if selection.is_empty()
16958 && let Some((_, ancestor_range)) =
16959 buffer.syntax_ancestor(selection.start..selection.end)
16960 {
16961 ancestor_range
16962 } else {
16963 selection.range()
16964 };
16965
16966 let mut parent = child.clone();
16967 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16968 parent = ancestor_range;
16969 if parent.start < child.start || parent.end > child.end {
16970 break;
16971 }
16972 }
16973
16974 if parent == child {
16975 return None;
16976 }
16977 let text = buffer.text_for_range(child).collect::<String>();
16978 Some((selection.id, parent, text))
16979 })
16980 .collect::<Vec<_>>();
16981 if full_edits.is_empty() {
16982 return;
16983 }
16984
16985 self.transact(window, cx, |this, window, cx| {
16986 this.buffer.update(cx, |buffer, cx| {
16987 buffer.edit(
16988 full_edits
16989 .iter()
16990 .map(|(_, p, t)| (p.clone(), t.clone()))
16991 .collect::<Vec<_>>(),
16992 None,
16993 cx,
16994 );
16995 });
16996 this.change_selections(Default::default(), window, cx, |s| {
16997 let mut offset = 0;
16998 let mut selections = vec![];
16999 for (id, parent, text) in full_edits {
17000 let start = parent.start - offset;
17001 offset += (parent.end - parent.start) - text.len();
17002 selections.push(Selection {
17003 id,
17004 start,
17005 end: start + text.len(),
17006 reversed: false,
17007 goal: Default::default(),
17008 });
17009 }
17010 s.select(selections);
17011 });
17012 });
17013 }
17014
17015 pub fn select_next_syntax_node(
17016 &mut self,
17017 _: &SelectNextSyntaxNode,
17018 window: &mut Window,
17019 cx: &mut Context<Self>,
17020 ) {
17021 let old_selections: Box<[_]> = self
17022 .selections
17023 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17024 .into();
17025 if old_selections.is_empty() {
17026 return;
17027 }
17028
17029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17030
17031 let buffer = self.buffer.read(cx).snapshot(cx);
17032 let mut selected_sibling = false;
17033
17034 let new_selections = old_selections
17035 .iter()
17036 .map(|selection| {
17037 let old_range = selection.start..selection.end;
17038
17039 let old_range =
17040 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17041 let excerpt = buffer.excerpt_containing(old_range.clone());
17042
17043 if let Some(mut excerpt) = excerpt
17044 && let Some(node) = excerpt
17045 .buffer()
17046 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
17047 {
17048 let new_range = excerpt.map_range_from_buffer(
17049 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17050 );
17051 selected_sibling = true;
17052 Selection {
17053 id: selection.id,
17054 start: new_range.start,
17055 end: new_range.end,
17056 goal: SelectionGoal::None,
17057 reversed: selection.reversed,
17058 }
17059 } else {
17060 selection.clone()
17061 }
17062 })
17063 .collect::<Vec<_>>();
17064
17065 if selected_sibling {
17066 self.change_selections(
17067 SelectionEffects::scroll(Autoscroll::fit()),
17068 window,
17069 cx,
17070 |s| {
17071 s.select(new_selections);
17072 },
17073 );
17074 }
17075 }
17076
17077 pub fn select_prev_syntax_node(
17078 &mut self,
17079 _: &SelectPreviousSyntaxNode,
17080 window: &mut Window,
17081 cx: &mut Context<Self>,
17082 ) {
17083 let old_selections: Box<[_]> = self
17084 .selections
17085 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17086 .into();
17087 if old_selections.is_empty() {
17088 return;
17089 }
17090
17091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17092
17093 let buffer = self.buffer.read(cx).snapshot(cx);
17094 let mut selected_sibling = false;
17095
17096 let new_selections = old_selections
17097 .iter()
17098 .map(|selection| {
17099 let old_range = selection.start..selection.end;
17100 let old_range =
17101 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
17102 let excerpt = buffer.excerpt_containing(old_range.clone());
17103
17104 if let Some(mut excerpt) = excerpt
17105 && let Some(node) = excerpt
17106 .buffer()
17107 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
17108 {
17109 let new_range = excerpt.map_range_from_buffer(
17110 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
17111 );
17112 selected_sibling = true;
17113 Selection {
17114 id: selection.id,
17115 start: new_range.start,
17116 end: new_range.end,
17117 goal: SelectionGoal::None,
17118 reversed: selection.reversed,
17119 }
17120 } else {
17121 selection.clone()
17122 }
17123 })
17124 .collect::<Vec<_>>();
17125
17126 if selected_sibling {
17127 self.change_selections(
17128 SelectionEffects::scroll(Autoscroll::fit()),
17129 window,
17130 cx,
17131 |s| {
17132 s.select(new_selections);
17133 },
17134 );
17135 }
17136 }
17137
17138 pub fn move_to_start_of_larger_syntax_node(
17139 &mut self,
17140 _: &MoveToStartOfLargerSyntaxNode,
17141 window: &mut Window,
17142 cx: &mut Context<Self>,
17143 ) {
17144 self.move_cursors_to_syntax_nodes(window, cx, false);
17145 }
17146
17147 pub fn move_to_end_of_larger_syntax_node(
17148 &mut self,
17149 _: &MoveToEndOfLargerSyntaxNode,
17150 window: &mut Window,
17151 cx: &mut Context<Self>,
17152 ) {
17153 self.move_cursors_to_syntax_nodes(window, cx, true);
17154 }
17155
17156 fn find_syntax_node_boundary(
17157 &self,
17158 selection_pos: MultiBufferOffset,
17159 move_to_end: bool,
17160 display_map: &DisplaySnapshot,
17161 buffer: &MultiBufferSnapshot,
17162 ) -> MultiBufferOffset {
17163 let old_range = selection_pos..selection_pos;
17164 let mut new_pos = selection_pos;
17165 let mut search_range = old_range;
17166 while let Some((node, range)) = buffer.syntax_ancestor(search_range.clone()) {
17167 search_range = range.clone();
17168 if !node.is_named()
17169 || display_map.intersects_fold(range.start)
17170 || display_map.intersects_fold(range.end)
17171 // If cursor is already at the end of the syntax node, continue searching
17172 || (move_to_end && range.end == selection_pos)
17173 // If cursor is already at the start of the syntax node, continue searching
17174 || (!move_to_end && range.start == selection_pos)
17175 {
17176 continue;
17177 }
17178
17179 // If we found a string_content node, find the largest parent that is still string_content
17180 // Enables us to skip to the end of strings without taking multiple steps inside the string
17181 let (_, final_range) = if node.kind() == "string_content" {
17182 let mut current_node = node;
17183 let mut current_range = range;
17184 while let Some((parent, parent_range)) =
17185 buffer.syntax_ancestor(current_range.clone())
17186 {
17187 if parent.kind() == "string_content" {
17188 current_node = parent;
17189 current_range = parent_range;
17190 } else {
17191 break;
17192 }
17193 }
17194
17195 (current_node, current_range)
17196 } else {
17197 (node, range)
17198 };
17199
17200 new_pos = if move_to_end {
17201 final_range.end
17202 } else {
17203 final_range.start
17204 };
17205
17206 break;
17207 }
17208
17209 new_pos
17210 }
17211
17212 fn move_cursors_to_syntax_nodes(
17213 &mut self,
17214 window: &mut Window,
17215 cx: &mut Context<Self>,
17216 move_to_end: bool,
17217 ) -> bool {
17218 let old_selections: Box<[_]> = self
17219 .selections
17220 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
17221 .into();
17222 if old_selections.is_empty() {
17223 return false;
17224 }
17225
17226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17227
17228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17229 let buffer = self.buffer.read(cx).snapshot(cx);
17230
17231 let mut any_cursor_moved = false;
17232 let new_selections = old_selections
17233 .iter()
17234 .map(|selection| {
17235 if !selection.is_empty() {
17236 return selection.clone();
17237 }
17238
17239 let selection_pos = selection.head();
17240 let new_pos = self.find_syntax_node_boundary(
17241 selection_pos,
17242 move_to_end,
17243 &display_map,
17244 &buffer,
17245 );
17246
17247 any_cursor_moved |= new_pos != selection_pos;
17248
17249 Selection {
17250 id: selection.id,
17251 start: new_pos,
17252 end: new_pos,
17253 goal: SelectionGoal::None,
17254 reversed: false,
17255 }
17256 })
17257 .collect::<Vec<_>>();
17258
17259 self.change_selections(Default::default(), window, cx, |s| {
17260 s.select(new_selections);
17261 });
17262 self.request_autoscroll(Autoscroll::newest(), cx);
17263
17264 any_cursor_moved
17265 }
17266
17267 pub fn select_to_start_of_larger_syntax_node(
17268 &mut self,
17269 _: &SelectToStartOfLargerSyntaxNode,
17270 window: &mut Window,
17271 cx: &mut Context<Self>,
17272 ) {
17273 self.select_to_syntax_nodes(window, cx, false);
17274 }
17275
17276 pub fn select_to_end_of_larger_syntax_node(
17277 &mut self,
17278 _: &SelectToEndOfLargerSyntaxNode,
17279 window: &mut Window,
17280 cx: &mut Context<Self>,
17281 ) {
17282 self.select_to_syntax_nodes(window, cx, true);
17283 }
17284
17285 fn select_to_syntax_nodes(
17286 &mut self,
17287 window: &mut Window,
17288 cx: &mut Context<Self>,
17289 move_to_end: bool,
17290 ) {
17291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17292
17293 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17294 let buffer = self.buffer.read(cx).snapshot(cx);
17295 let old_selections = self.selections.all::<MultiBufferOffset>(&display_map);
17296
17297 let new_selections = old_selections
17298 .iter()
17299 .map(|selection| {
17300 let new_pos = self.find_syntax_node_boundary(
17301 selection.head(),
17302 move_to_end,
17303 &display_map,
17304 &buffer,
17305 );
17306
17307 let mut new_selection = selection.clone();
17308 new_selection.set_head(new_pos, SelectionGoal::None);
17309 new_selection
17310 })
17311 .collect::<Vec<_>>();
17312
17313 self.change_selections(Default::default(), window, cx, |s| {
17314 s.select(new_selections);
17315 });
17316 }
17317
17318 pub fn move_to_enclosing_bracket(
17319 &mut self,
17320 _: &MoveToEnclosingBracket,
17321 window: &mut Window,
17322 cx: &mut Context<Self>,
17323 ) {
17324 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17325 self.change_selections(Default::default(), window, cx, |s| {
17326 s.move_offsets_with(&mut |snapshot, selection| {
17327 let Some(enclosing_bracket_ranges) =
17328 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
17329 else {
17330 return;
17331 };
17332
17333 let mut best_length = usize::MAX;
17334 let mut best_inside = false;
17335 let mut best_in_bracket_range = false;
17336 let mut best_destination = None;
17337 for (open, close) in enclosing_bracket_ranges {
17338 let close = close.to_inclusive();
17339 let length = *close.end() - open.start;
17340 let inside = selection.start >= open.end && selection.end <= *close.start();
17341 let in_bracket_range = open.to_inclusive().contains(&selection.head())
17342 || close.contains(&selection.head());
17343
17344 // If best is next to a bracket and current isn't, skip
17345 if !in_bracket_range && best_in_bracket_range {
17346 continue;
17347 }
17348
17349 // Prefer smaller lengths unless best is inside and current isn't
17350 if length > best_length && (best_inside || !inside) {
17351 continue;
17352 }
17353
17354 best_length = length;
17355 best_inside = inside;
17356 best_in_bracket_range = in_bracket_range;
17357 best_destination = Some(
17358 if close.contains(&selection.start) && close.contains(&selection.end) {
17359 if inside { open.end } else { open.start }
17360 } else if inside {
17361 *close.start()
17362 } else {
17363 *close.end()
17364 },
17365 );
17366 }
17367
17368 if let Some(destination) = best_destination {
17369 selection.collapse_to(destination, SelectionGoal::None);
17370 }
17371 })
17372 });
17373 }
17374
17375 pub fn undo_selection(
17376 &mut self,
17377 _: &UndoSelection,
17378 window: &mut Window,
17379 cx: &mut Context<Self>,
17380 ) {
17381 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17382 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
17383 self.selection_history.mode = SelectionHistoryMode::Undoing;
17384 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17385 this.end_selection(window, cx);
17386 this.change_selections(
17387 SelectionEffects::scroll(Autoscroll::newest()),
17388 window,
17389 cx,
17390 |s| s.select_anchors(entry.selections.to_vec()),
17391 );
17392 });
17393 self.selection_history.mode = SelectionHistoryMode::Normal;
17394
17395 self.select_next_state = entry.select_next_state;
17396 self.select_prev_state = entry.select_prev_state;
17397 self.add_selections_state = entry.add_selections_state;
17398 }
17399 }
17400
17401 pub fn redo_selection(
17402 &mut self,
17403 _: &RedoSelection,
17404 window: &mut Window,
17405 cx: &mut Context<Self>,
17406 ) {
17407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17408 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
17409 self.selection_history.mode = SelectionHistoryMode::Redoing;
17410 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17411 this.end_selection(window, cx);
17412 this.change_selections(
17413 SelectionEffects::scroll(Autoscroll::newest()),
17414 window,
17415 cx,
17416 |s| s.select_anchors(entry.selections.to_vec()),
17417 );
17418 });
17419 self.selection_history.mode = SelectionHistoryMode::Normal;
17420
17421 self.select_next_state = entry.select_next_state;
17422 self.select_prev_state = entry.select_prev_state;
17423 self.add_selections_state = entry.add_selections_state;
17424 }
17425 }
17426
17427 pub fn expand_excerpts(
17428 &mut self,
17429 action: &ExpandExcerpts,
17430 _: &mut Window,
17431 cx: &mut Context<Self>,
17432 ) {
17433 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
17434 }
17435
17436 pub fn expand_excerpts_down(
17437 &mut self,
17438 action: &ExpandExcerptsDown,
17439 _: &mut Window,
17440 cx: &mut Context<Self>,
17441 ) {
17442 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
17443 }
17444
17445 pub fn expand_excerpts_up(
17446 &mut self,
17447 action: &ExpandExcerptsUp,
17448 _: &mut Window,
17449 cx: &mut Context<Self>,
17450 ) {
17451 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
17452 }
17453
17454 pub fn expand_excerpts_for_direction(
17455 &mut self,
17456 lines: u32,
17457 direction: ExpandExcerptDirection,
17458 cx: &mut Context<Self>,
17459 ) {
17460 let selections = self.selections.disjoint_anchors_arc();
17461
17462 let lines = if lines == 0 {
17463 EditorSettings::get_global(cx).expand_excerpt_lines
17464 } else {
17465 lines
17466 };
17467
17468 let snapshot = self.buffer.read(cx).snapshot(cx);
17469 let excerpt_ids = selections
17470 .iter()
17471 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
17472 .unique()
17473 .sorted()
17474 .collect::<Vec<_>>();
17475
17476 if self.delegate_expand_excerpts {
17477 cx.emit(EditorEvent::ExpandExcerptsRequested {
17478 excerpt_ids,
17479 lines,
17480 direction,
17481 });
17482 return;
17483 }
17484
17485 self.buffer.update(cx, |buffer, cx| {
17486 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
17487 })
17488 }
17489
17490 pub fn expand_excerpt(
17491 &mut self,
17492 excerpt: ExcerptId,
17493 direction: ExpandExcerptDirection,
17494 window: &mut Window,
17495 cx: &mut Context<Self>,
17496 ) {
17497 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
17498
17499 if self.delegate_expand_excerpts {
17500 cx.emit(EditorEvent::ExpandExcerptsRequested {
17501 excerpt_ids: vec![excerpt],
17502 lines: lines_to_expand,
17503 direction,
17504 });
17505 return;
17506 }
17507
17508 let current_scroll_position = self.scroll_position(cx);
17509 let mut scroll = None;
17510
17511 if direction == ExpandExcerptDirection::Down {
17512 let multi_buffer = self.buffer.read(cx);
17513 let snapshot = multi_buffer.snapshot(cx);
17514 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
17515 && let Some(buffer) = multi_buffer.buffer(buffer_id)
17516 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
17517 {
17518 let buffer_snapshot = buffer.read(cx).snapshot();
17519 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
17520 let last_row = buffer_snapshot.max_point().row;
17521 let lines_below = last_row.saturating_sub(excerpt_end_row);
17522 if lines_below >= lines_to_expand {
17523 scroll = Some(
17524 current_scroll_position
17525 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
17526 );
17527 }
17528 }
17529 }
17530 if direction == ExpandExcerptDirection::Up
17531 && self
17532 .buffer
17533 .read(cx)
17534 .snapshot(cx)
17535 .excerpt_before(excerpt)
17536 .is_none()
17537 {
17538 scroll = Some(current_scroll_position);
17539 }
17540
17541 self.buffer.update(cx, |buffer, cx| {
17542 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
17543 });
17544
17545 if let Some(new_scroll_position) = scroll {
17546 self.set_scroll_position(new_scroll_position, window, cx);
17547 }
17548 }
17549
17550 pub fn go_to_singleton_buffer_point(
17551 &mut self,
17552 point: Point,
17553 window: &mut Window,
17554 cx: &mut Context<Self>,
17555 ) {
17556 self.go_to_singleton_buffer_range(point..point, window, cx);
17557 }
17558
17559 pub fn go_to_singleton_buffer_range(
17560 &mut self,
17561 range: Range<Point>,
17562 window: &mut Window,
17563 cx: &mut Context<Self>,
17564 ) {
17565 let multibuffer = self.buffer().read(cx);
17566 let Some(buffer) = multibuffer.as_singleton() else {
17567 return;
17568 };
17569 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
17570 return;
17571 };
17572 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
17573 return;
17574 };
17575 self.change_selections(
17576 SelectionEffects::default().nav_history(true),
17577 window,
17578 cx,
17579 |s| s.select_anchor_ranges([start..end]),
17580 );
17581 }
17582
17583 pub fn go_to_diagnostic(
17584 &mut self,
17585 action: &GoToDiagnostic,
17586 window: &mut Window,
17587 cx: &mut Context<Self>,
17588 ) {
17589 if !self.diagnostics_enabled() {
17590 return;
17591 }
17592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17593 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
17594 }
17595
17596 pub fn go_to_prev_diagnostic(
17597 &mut self,
17598 action: &GoToPreviousDiagnostic,
17599 window: &mut Window,
17600 cx: &mut Context<Self>,
17601 ) {
17602 if !self.diagnostics_enabled() {
17603 return;
17604 }
17605 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17606 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
17607 }
17608
17609 pub fn go_to_diagnostic_impl(
17610 &mut self,
17611 direction: Direction,
17612 severity: GoToDiagnosticSeverityFilter,
17613 window: &mut Window,
17614 cx: &mut Context<Self>,
17615 ) {
17616 let buffer = self.buffer.read(cx).snapshot(cx);
17617 let selection = self
17618 .selections
17619 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17620
17621 let mut active_group_id = None;
17622 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
17623 && active_group.active_range.start.to_offset(&buffer) == selection.start
17624 {
17625 active_group_id = Some(active_group.group_id);
17626 }
17627
17628 fn filtered<'a>(
17629 severity: GoToDiagnosticSeverityFilter,
17630 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
17631 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
17632 diagnostics
17633 .filter(move |entry| severity.matches(entry.diagnostic.severity))
17634 .filter(|entry| entry.range.start != entry.range.end)
17635 .filter(|entry| !entry.diagnostic.is_unnecessary)
17636 }
17637
17638 let before = filtered(
17639 severity,
17640 buffer
17641 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
17642 .filter(|entry| entry.range.start <= selection.start),
17643 );
17644 let after = filtered(
17645 severity,
17646 buffer
17647 .diagnostics_in_range(selection.start..buffer.len())
17648 .filter(|entry| entry.range.start >= selection.start),
17649 );
17650
17651 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
17652 if direction == Direction::Prev {
17653 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
17654 {
17655 for diagnostic in prev_diagnostics.into_iter().rev() {
17656 if diagnostic.range.start != selection.start
17657 || active_group_id
17658 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
17659 {
17660 found = Some(diagnostic);
17661 break 'outer;
17662 }
17663 }
17664 }
17665 } else {
17666 for diagnostic in after.chain(before) {
17667 if diagnostic.range.start != selection.start
17668 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
17669 {
17670 found = Some(diagnostic);
17671 break;
17672 }
17673 }
17674 }
17675 let Some(next_diagnostic) = found else {
17676 return;
17677 };
17678
17679 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
17680 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
17681 return;
17682 };
17683 let snapshot = self.snapshot(window, cx);
17684 if snapshot.intersects_fold(next_diagnostic.range.start) {
17685 self.unfold_ranges(
17686 std::slice::from_ref(&next_diagnostic.range),
17687 true,
17688 false,
17689 cx,
17690 );
17691 }
17692 self.change_selections(Default::default(), window, cx, |s| {
17693 s.select_ranges(vec![
17694 next_diagnostic.range.start..next_diagnostic.range.start,
17695 ])
17696 });
17697 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
17698 self.refresh_edit_prediction(false, true, window, cx);
17699 }
17700
17701 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
17702 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17703 let snapshot = self.snapshot(window, cx);
17704 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17705 self.go_to_hunk_before_or_after_position(
17706 &snapshot,
17707 selection.head(),
17708 Direction::Next,
17709 true,
17710 window,
17711 cx,
17712 );
17713 }
17714
17715 pub fn go_to_hunk_before_or_after_position(
17716 &mut self,
17717 snapshot: &EditorSnapshot,
17718 position: Point,
17719 direction: Direction,
17720 wrap_around: bool,
17721 window: &mut Window,
17722 cx: &mut Context<Editor>,
17723 ) {
17724 let row = if direction == Direction::Next {
17725 self.hunk_after_position(snapshot, position, wrap_around)
17726 .map(|hunk| hunk.row_range.start)
17727 } else {
17728 self.hunk_before_position(snapshot, position, wrap_around)
17729 };
17730
17731 if let Some(row) = row {
17732 let destination = Point::new(row.0, 0);
17733 let autoscroll = Autoscroll::center();
17734
17735 self.unfold_ranges(&[destination..destination], false, false, cx);
17736 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17737 s.select_ranges([destination..destination]);
17738 });
17739 }
17740 }
17741
17742 fn hunk_after_position(
17743 &mut self,
17744 snapshot: &EditorSnapshot,
17745 position: Point,
17746 wrap_around: bool,
17747 ) -> Option<MultiBufferDiffHunk> {
17748 let result = snapshot
17749 .buffer_snapshot()
17750 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17751 .find(|hunk| hunk.row_range.start.0 > position.row);
17752
17753 if wrap_around {
17754 result.or_else(|| {
17755 snapshot
17756 .buffer_snapshot()
17757 .diff_hunks_in_range(Point::zero()..position)
17758 .find(|hunk| hunk.row_range.end.0 < position.row)
17759 })
17760 } else {
17761 result
17762 }
17763 }
17764
17765 fn go_to_prev_hunk(
17766 &mut self,
17767 _: &GoToPreviousHunk,
17768 window: &mut Window,
17769 cx: &mut Context<Self>,
17770 ) {
17771 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17772 let snapshot = self.snapshot(window, cx);
17773 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17774 self.go_to_hunk_before_or_after_position(
17775 &snapshot,
17776 selection.head(),
17777 Direction::Prev,
17778 true,
17779 window,
17780 cx,
17781 );
17782 }
17783
17784 fn hunk_before_position(
17785 &mut self,
17786 snapshot: &EditorSnapshot,
17787 position: Point,
17788 wrap_around: bool,
17789 ) -> Option<MultiBufferRow> {
17790 let result = snapshot.buffer_snapshot().diff_hunk_before(position);
17791
17792 if wrap_around {
17793 result.or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17794 } else {
17795 result
17796 }
17797 }
17798
17799 fn go_to_next_change(
17800 &mut self,
17801 _: &GoToNextChange,
17802 window: &mut Window,
17803 cx: &mut Context<Self>,
17804 ) {
17805 if let Some(selections) = self
17806 .change_list
17807 .next_change(1, Direction::Next)
17808 .map(|s| s.to_vec())
17809 {
17810 self.change_selections(Default::default(), window, cx, |s| {
17811 let map = s.display_snapshot();
17812 s.select_display_ranges(selections.iter().map(|a| {
17813 let point = a.to_display_point(&map);
17814 point..point
17815 }))
17816 })
17817 }
17818 }
17819
17820 fn go_to_previous_change(
17821 &mut self,
17822 _: &GoToPreviousChange,
17823 window: &mut Window,
17824 cx: &mut Context<Self>,
17825 ) {
17826 if let Some(selections) = self
17827 .change_list
17828 .next_change(1, Direction::Prev)
17829 .map(|s| s.to_vec())
17830 {
17831 self.change_selections(Default::default(), window, cx, |s| {
17832 let map = s.display_snapshot();
17833 s.select_display_ranges(selections.iter().map(|a| {
17834 let point = a.to_display_point(&map);
17835 point..point
17836 }))
17837 })
17838 }
17839 }
17840
17841 pub fn go_to_next_document_highlight(
17842 &mut self,
17843 _: &GoToNextDocumentHighlight,
17844 window: &mut Window,
17845 cx: &mut Context<Self>,
17846 ) {
17847 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17848 }
17849
17850 pub fn go_to_prev_document_highlight(
17851 &mut self,
17852 _: &GoToPreviousDocumentHighlight,
17853 window: &mut Window,
17854 cx: &mut Context<Self>,
17855 ) {
17856 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17857 }
17858
17859 pub fn go_to_document_highlight_before_or_after_position(
17860 &mut self,
17861 direction: Direction,
17862 window: &mut Window,
17863 cx: &mut Context<Editor>,
17864 ) {
17865 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17866 let snapshot = self.snapshot(window, cx);
17867 let buffer = &snapshot.buffer_snapshot();
17868 let position = self
17869 .selections
17870 .newest::<Point>(&snapshot.display_snapshot)
17871 .head();
17872 let anchor_position = buffer.anchor_after(position);
17873
17874 // Get all document highlights (both read and write)
17875 let mut all_highlights = Vec::new();
17876
17877 if let Some((_, read_highlights)) = self
17878 .background_highlights
17879 .get(&HighlightKey::DocumentHighlightRead)
17880 {
17881 all_highlights.extend(read_highlights.iter());
17882 }
17883
17884 if let Some((_, write_highlights)) = self
17885 .background_highlights
17886 .get(&HighlightKey::DocumentHighlightWrite)
17887 {
17888 all_highlights.extend(write_highlights.iter());
17889 }
17890
17891 if all_highlights.is_empty() {
17892 return;
17893 }
17894
17895 // Sort highlights by position
17896 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17897
17898 let target_highlight = match direction {
17899 Direction::Next => {
17900 // Find the first highlight after the current position
17901 all_highlights
17902 .iter()
17903 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17904 }
17905 Direction::Prev => {
17906 // Find the last highlight before the current position
17907 all_highlights
17908 .iter()
17909 .rev()
17910 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17911 }
17912 };
17913
17914 if let Some(highlight) = target_highlight {
17915 let destination = highlight.start.to_point(buffer);
17916 let autoscroll = Autoscroll::center();
17917
17918 self.unfold_ranges(&[destination..destination], false, false, cx);
17919 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17920 s.select_ranges([destination..destination]);
17921 });
17922 }
17923 }
17924
17925 fn go_to_line<T: 'static>(
17926 &mut self,
17927 position: Anchor,
17928 highlight_color: Option<Hsla>,
17929 window: &mut Window,
17930 cx: &mut Context<Self>,
17931 ) {
17932 let snapshot = self.snapshot(window, cx).display_snapshot;
17933 let position = position.to_point(&snapshot.buffer_snapshot());
17934 let start = snapshot
17935 .buffer_snapshot()
17936 .clip_point(Point::new(position.row, 0), Bias::Left);
17937 let end = start + Point::new(1, 0);
17938 let start = snapshot.buffer_snapshot().anchor_before(start);
17939 let end = snapshot.buffer_snapshot().anchor_before(end);
17940
17941 self.highlight_rows::<T>(
17942 start..end,
17943 highlight_color
17944 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17945 Default::default(),
17946 cx,
17947 );
17948
17949 if self.buffer.read(cx).is_singleton() {
17950 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17951 }
17952 }
17953
17954 pub fn go_to_definition(
17955 &mut self,
17956 _: &GoToDefinition,
17957 window: &mut Window,
17958 cx: &mut Context<Self>,
17959 ) -> Task<Result<Navigated>> {
17960 let definition =
17961 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17962 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17963 cx.spawn_in(window, async move |editor, cx| {
17964 if definition.await? == Navigated::Yes {
17965 return Ok(Navigated::Yes);
17966 }
17967 match fallback_strategy {
17968 GoToDefinitionFallback::None => Ok(Navigated::No),
17969 GoToDefinitionFallback::FindAllReferences => {
17970 match editor.update_in(cx, |editor, window, cx| {
17971 editor.find_all_references(&FindAllReferences::default(), window, cx)
17972 })? {
17973 Some(references) => references.await,
17974 None => Ok(Navigated::No),
17975 }
17976 }
17977 }
17978 })
17979 }
17980
17981 pub fn go_to_declaration(
17982 &mut self,
17983 _: &GoToDeclaration,
17984 window: &mut Window,
17985 cx: &mut Context<Self>,
17986 ) -> Task<Result<Navigated>> {
17987 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17988 }
17989
17990 pub fn go_to_declaration_split(
17991 &mut self,
17992 _: &GoToDeclaration,
17993 window: &mut Window,
17994 cx: &mut Context<Self>,
17995 ) -> Task<Result<Navigated>> {
17996 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17997 }
17998
17999 pub fn go_to_implementation(
18000 &mut self,
18001 _: &GoToImplementation,
18002 window: &mut Window,
18003 cx: &mut Context<Self>,
18004 ) -> Task<Result<Navigated>> {
18005 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
18006 }
18007
18008 pub fn go_to_implementation_split(
18009 &mut self,
18010 _: &GoToImplementationSplit,
18011 window: &mut Window,
18012 cx: &mut Context<Self>,
18013 ) -> Task<Result<Navigated>> {
18014 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
18015 }
18016
18017 pub fn go_to_type_definition(
18018 &mut self,
18019 _: &GoToTypeDefinition,
18020 window: &mut Window,
18021 cx: &mut Context<Self>,
18022 ) -> Task<Result<Navigated>> {
18023 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
18024 }
18025
18026 pub fn go_to_definition_split(
18027 &mut self,
18028 _: &GoToDefinitionSplit,
18029 window: &mut Window,
18030 cx: &mut Context<Self>,
18031 ) -> Task<Result<Navigated>> {
18032 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
18033 }
18034
18035 pub fn go_to_type_definition_split(
18036 &mut self,
18037 _: &GoToTypeDefinitionSplit,
18038 window: &mut Window,
18039 cx: &mut Context<Self>,
18040 ) -> Task<Result<Navigated>> {
18041 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
18042 }
18043
18044 fn go_to_definition_of_kind(
18045 &mut self,
18046 kind: GotoDefinitionKind,
18047 split: bool,
18048 window: &mut Window,
18049 cx: &mut Context<Self>,
18050 ) -> Task<Result<Navigated>> {
18051 let Some(provider) = self.semantics_provider.clone() else {
18052 return Task::ready(Ok(Navigated::No));
18053 };
18054 let head = self
18055 .selections
18056 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
18057 .head();
18058 let buffer = self.buffer.read(cx);
18059 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
18060 return Task::ready(Ok(Navigated::No));
18061 };
18062 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
18063 return Task::ready(Ok(Navigated::No));
18064 };
18065
18066 let nav_entry = self.navigation_entry(self.selections.newest_anchor().head(), cx);
18067
18068 cx.spawn_in(window, async move |editor, cx| {
18069 let Some(definitions) = definitions.await? else {
18070 return Ok(Navigated::No);
18071 };
18072 let navigated = editor
18073 .update_in(cx, |editor, window, cx| {
18074 editor.navigate_to_hover_links(
18075 Some(kind),
18076 definitions
18077 .into_iter()
18078 .filter(|location| {
18079 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
18080 })
18081 .map(HoverLink::Text)
18082 .collect::<Vec<_>>(),
18083 nav_entry,
18084 split,
18085 window,
18086 cx,
18087 )
18088 })?
18089 .await?;
18090 anyhow::Ok(navigated)
18091 })
18092 }
18093
18094 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
18095 let selection = self.selections.newest_anchor();
18096 let head = selection.head();
18097 let tail = selection.tail();
18098
18099 let Some((buffer, start_position)) =
18100 self.buffer.read(cx).text_anchor_for_position(head, cx)
18101 else {
18102 return;
18103 };
18104
18105 let end_position = if head != tail {
18106 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
18107 return;
18108 };
18109 Some(pos)
18110 } else {
18111 None
18112 };
18113
18114 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
18115 let url = if let Some(end_pos) = end_position {
18116 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
18117 } else {
18118 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
18119 };
18120
18121 if let Some(url) = url {
18122 cx.update(|window, cx| {
18123 if parse_zed_link(&url, cx).is_some() {
18124 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18125 } else {
18126 cx.open_url(&url);
18127 }
18128 })?;
18129 }
18130
18131 anyhow::Ok(())
18132 });
18133
18134 url_finder.detach();
18135 }
18136
18137 pub fn open_selected_filename(
18138 &mut self,
18139 _: &OpenSelectedFilename,
18140 window: &mut Window,
18141 cx: &mut Context<Self>,
18142 ) {
18143 let Some(workspace) = self.workspace() else {
18144 return;
18145 };
18146
18147 let position = self.selections.newest_anchor().head();
18148
18149 let Some((buffer, buffer_position)) =
18150 self.buffer.read(cx).text_anchor_for_position(position, cx)
18151 else {
18152 return;
18153 };
18154
18155 let project = self.project.clone();
18156
18157 cx.spawn_in(window, async move |_, cx| {
18158 let result = find_file(&buffer, project, buffer_position, cx).await;
18159
18160 if let Some((_, path)) = result {
18161 workspace
18162 .update_in(cx, |workspace, window, cx| {
18163 workspace.open_resolved_path(path, window, cx)
18164 })?
18165 .await?;
18166 }
18167 anyhow::Ok(())
18168 })
18169 .detach();
18170 }
18171
18172 pub(crate) fn navigate_to_hover_links(
18173 &mut self,
18174 kind: Option<GotoDefinitionKind>,
18175 definitions: Vec<HoverLink>,
18176 origin: Option<NavigationEntry>,
18177 split: bool,
18178 window: &mut Window,
18179 cx: &mut Context<Editor>,
18180 ) -> Task<Result<Navigated>> {
18181 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
18182 let mut first_url_or_file = None;
18183 let definitions: Vec<_> = definitions
18184 .into_iter()
18185 .filter_map(|def| match def {
18186 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
18187 HoverLink::InlayHint(lsp_location, server_id) => {
18188 let computation =
18189 self.compute_target_location(lsp_location, server_id, window, cx);
18190 Some(cx.background_spawn(computation))
18191 }
18192 HoverLink::Url(url) => {
18193 first_url_or_file = Some(Either::Left(url));
18194 None
18195 }
18196 HoverLink::File(path) => {
18197 first_url_or_file = Some(Either::Right(path));
18198 None
18199 }
18200 })
18201 .collect();
18202
18203 let workspace = self.workspace();
18204
18205 let excerpt_context_lines = multi_buffer::excerpt_context_lines(cx);
18206 cx.spawn_in(window, async move |editor, cx| {
18207 let locations: Vec<Location> = future::join_all(definitions)
18208 .await
18209 .into_iter()
18210 .filter_map(|location| location.transpose())
18211 .collect::<Result<_>>()
18212 .context("location tasks")?;
18213 let mut locations = cx.update(|_, cx| {
18214 locations
18215 .into_iter()
18216 .map(|location| {
18217 let buffer = location.buffer.read(cx);
18218 (location.buffer, location.range.to_point(buffer))
18219 })
18220 .into_group_map()
18221 })?;
18222 let mut num_locations = 0;
18223 for ranges in locations.values_mut() {
18224 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18225 ranges.dedup();
18226 let fits_in_one_excerpt = ranges
18227 .iter()
18228 .tuple_windows()
18229 .all(|(a, b)| b.start.row - a.end.row <= 2 * excerpt_context_lines);
18230 num_locations += if fits_in_one_excerpt { 1 } else { ranges.len() };
18231 }
18232
18233 if num_locations > 1 {
18234 let tab_kind = match kind {
18235 Some(GotoDefinitionKind::Implementation) => "Implementations",
18236 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
18237 Some(GotoDefinitionKind::Declaration) => "Declarations",
18238 Some(GotoDefinitionKind::Type) => "Types",
18239 };
18240 let title = editor
18241 .update_in(cx, |_, _, cx| {
18242 let target = locations
18243 .iter()
18244 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18245 .map(|(buffer, location)| {
18246 buffer
18247 .read(cx)
18248 .text_for_range(location.clone())
18249 .collect::<String>()
18250 })
18251 .filter(|text| !text.contains('\n'))
18252 .unique()
18253 .take(3)
18254 .join(", ");
18255 if target.is_empty() {
18256 tab_kind.to_owned()
18257 } else {
18258 format!("{tab_kind} for {target}")
18259 }
18260 })
18261 .context("buffer title")?;
18262
18263 let Some(workspace) = workspace else {
18264 return Ok(Navigated::No);
18265 };
18266
18267 let opened = workspace
18268 .update_in(cx, |workspace, window, cx| {
18269 let allow_preview = PreviewTabsSettings::get_global(cx)
18270 .enable_preview_multibuffer_from_code_navigation;
18271 if let Some((target_editor, target_pane)) =
18272 Self::open_locations_in_multibuffer(
18273 workspace,
18274 locations,
18275 title,
18276 split,
18277 allow_preview,
18278 MultibufferSelectionMode::First,
18279 window,
18280 cx,
18281 )
18282 {
18283 // We create our own nav history instead of using
18284 // `target_editor.nav_history` because `nav_history`
18285 // seems to be populated asynchronously when an item
18286 // is added to a pane
18287 let mut nav_history = target_pane
18288 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18289 target_editor.update(cx, |editor, cx| {
18290 let nav_data = editor
18291 .navigation_data(editor.selections.newest_anchor().head(), cx);
18292 let target =
18293 Some(nav_history.navigation_entry(Some(
18294 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18295 )));
18296 nav_history.push_tag(origin, target);
18297 })
18298 }
18299 })
18300 .is_ok();
18301
18302 anyhow::Ok(Navigated::from_bool(opened))
18303 } else if num_locations == 0 {
18304 // If there is one url or file, open it directly
18305 match first_url_or_file {
18306 Some(Either::Left(url)) => {
18307 cx.update(|window, cx| {
18308 if parse_zed_link(&url, cx).is_some() {
18309 window
18310 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
18311 } else {
18312 cx.open_url(&url);
18313 }
18314 })?;
18315 Ok(Navigated::Yes)
18316 }
18317 Some(Either::Right(path)) => {
18318 // TODO(andrew): respect preview tab settings
18319 // `enable_keep_preview_on_code_navigation` and
18320 // `enable_preview_file_from_code_navigation`
18321 let Some(workspace) = workspace else {
18322 return Ok(Navigated::No);
18323 };
18324 workspace
18325 .update_in(cx, |workspace, window, cx| {
18326 workspace.open_resolved_path(path, window, cx)
18327 })?
18328 .await?;
18329 Ok(Navigated::Yes)
18330 }
18331 None => Ok(Navigated::No),
18332 }
18333 } else {
18334 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18335
18336 editor.update_in(cx, |editor, window, cx| {
18337 let target_ranges = target_ranges
18338 .into_iter()
18339 .map(|r| editor.range_for_match(&r))
18340 .map(collapse_multiline_range)
18341 .collect::<Vec<_>>();
18342 if !split
18343 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
18344 {
18345 let multibuffer = editor.buffer.read(cx);
18346 let target_ranges = target_ranges
18347 .into_iter()
18348 .filter_map(|r| {
18349 let start = multibuffer.buffer_point_to_anchor(
18350 &target_buffer,
18351 r.start,
18352 cx,
18353 )?;
18354 let end = multibuffer.buffer_point_to_anchor(
18355 &target_buffer,
18356 r.end,
18357 cx,
18358 )?;
18359 Some(start..end)
18360 })
18361 .collect::<Vec<_>>();
18362 if target_ranges.is_empty() {
18363 return Navigated::No;
18364 }
18365
18366 editor.change_selections(
18367 SelectionEffects::default().nav_history(true),
18368 window,
18369 cx,
18370 |s| s.select_anchor_ranges(target_ranges),
18371 );
18372
18373 let target =
18374 editor.navigation_entry(editor.selections.newest_anchor().head(), cx);
18375 if let Some(mut nav_history) = editor.nav_history.clone() {
18376 nav_history.push_tag(origin, target);
18377 }
18378 } else {
18379 let Some(workspace) = workspace else {
18380 return Navigated::No;
18381 };
18382 let pane = workspace.read(cx).active_pane().clone();
18383 window.defer(cx, move |window, cx| {
18384 let (target_editor, target_pane): (Entity<Self>, Entity<Pane>) =
18385 workspace.update(cx, |workspace, cx| {
18386 let pane = if split {
18387 workspace.adjacent_pane(window, cx)
18388 } else {
18389 workspace.active_pane().clone()
18390 };
18391
18392 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18393 let keep_old_preview = preview_tabs_settings
18394 .enable_keep_preview_on_code_navigation;
18395 let allow_new_preview = preview_tabs_settings
18396 .enable_preview_file_from_code_navigation;
18397
18398 let editor = workspace.open_project_item(
18399 pane.clone(),
18400 target_buffer.clone(),
18401 true,
18402 true,
18403 keep_old_preview,
18404 allow_new_preview,
18405 window,
18406 cx,
18407 );
18408 (editor, pane)
18409 });
18410 // We create our own nav history instead of using
18411 // `target_editor.nav_history` because `nav_history`
18412 // seems to be populated asynchronously when an item
18413 // is added to a pane
18414 let mut nav_history = target_pane
18415 .update(cx, |pane, _| pane.nav_history_for_item(&target_editor));
18416 target_editor.update(cx, |target_editor, cx| {
18417 // When selecting a definition in a different buffer, disable the nav history
18418 // to avoid creating a history entry at the previous cursor location.
18419 pane.update(cx, |pane, _| pane.disable_history());
18420
18421 let multibuffer = target_editor.buffer.read(cx);
18422 let Some(target_buffer) = multibuffer.as_singleton() else {
18423 return Navigated::No;
18424 };
18425 let target_ranges = target_ranges
18426 .into_iter()
18427 .filter_map(|r| {
18428 let start = multibuffer.buffer_point_to_anchor(
18429 &target_buffer,
18430 r.start,
18431 cx,
18432 )?;
18433 let end = multibuffer.buffer_point_to_anchor(
18434 &target_buffer,
18435 r.end,
18436 cx,
18437 )?;
18438 Some(start..end)
18439 })
18440 .collect::<Vec<_>>();
18441 if target_ranges.is_empty() {
18442 return Navigated::No;
18443 }
18444
18445 target_editor.change_selections(
18446 SelectionEffects::default().nav_history(true),
18447 window,
18448 cx,
18449 |s| s.select_anchor_ranges(target_ranges),
18450 );
18451
18452 let nav_data = target_editor.navigation_data(
18453 target_editor.selections.newest_anchor().head(),
18454 cx,
18455 );
18456 let target =
18457 Some(nav_history.navigation_entry(Some(
18458 Arc::new(nav_data) as Arc<dyn Any + Send + Sync>
18459 )));
18460 nav_history.push_tag(origin, target);
18461 pane.update(cx, |pane, _| pane.enable_history());
18462 Navigated::Yes
18463 });
18464 });
18465 }
18466 Navigated::Yes
18467 })
18468 }
18469 })
18470 }
18471
18472 fn compute_target_location(
18473 &self,
18474 lsp_location: lsp::Location,
18475 server_id: LanguageServerId,
18476 window: &mut Window,
18477 cx: &mut Context<Self>,
18478 ) -> Task<anyhow::Result<Option<Location>>> {
18479 let Some(project) = self.project.clone() else {
18480 return Task::ready(Ok(None));
18481 };
18482
18483 cx.spawn_in(window, async move |editor, cx| {
18484 let location_task = editor.update(cx, |_, cx| {
18485 project.update(cx, |project, cx| {
18486 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
18487 })
18488 })?;
18489 let location = Some({
18490 let target_buffer_handle = location_task.await.context("open local buffer")?;
18491 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
18492 let target_start = target_buffer
18493 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
18494 let target_end = target_buffer
18495 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
18496 target_buffer.anchor_after(target_start)
18497 ..target_buffer.anchor_before(target_end)
18498 });
18499 Location {
18500 buffer: target_buffer_handle,
18501 range,
18502 }
18503 });
18504 Ok(location)
18505 })
18506 }
18507
18508 fn go_to_next_reference(
18509 &mut self,
18510 _: &GoToNextReference,
18511 window: &mut Window,
18512 cx: &mut Context<Self>,
18513 ) {
18514 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
18515 if let Some(task) = task {
18516 task.detach();
18517 };
18518 }
18519
18520 fn go_to_prev_reference(
18521 &mut self,
18522 _: &GoToPreviousReference,
18523 window: &mut Window,
18524 cx: &mut Context<Self>,
18525 ) {
18526 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
18527 if let Some(task) = task {
18528 task.detach();
18529 };
18530 }
18531
18532 fn go_to_symbol_by_offset(
18533 &mut self,
18534 window: &mut Window,
18535 cx: &mut Context<Self>,
18536 offset: i8,
18537 ) -> Task<Result<()>> {
18538 let editor_snapshot = self.snapshot(window, cx);
18539
18540 // We don't care about multi-buffer symbols
18541 let Some((excerpt_id, _, _)) = editor_snapshot.as_singleton() else {
18542 return Task::ready(Ok(()));
18543 };
18544
18545 let cursor_offset = self
18546 .selections
18547 .newest::<MultiBufferOffset>(&editor_snapshot.display_snapshot)
18548 .head();
18549
18550 cx.spawn_in(window, async move |editor, wcx| -> Result<()> {
18551 let Ok(Some(remote_id)) = editor.update(wcx, |ed, cx| {
18552 let buffer = ed.buffer.read(cx).as_singleton()?;
18553 Some(buffer.read(cx).remote_id())
18554 }) else {
18555 return Ok(());
18556 };
18557
18558 let task = editor.update(wcx, |ed, cx| ed.buffer_outline_items(remote_id, cx))?;
18559 let outline_items: Vec<OutlineItem<text::Anchor>> = task.await;
18560
18561 let multi_snapshot = editor_snapshot.buffer();
18562 let buffer_range = |range: &Range<_>| {
18563 Anchor::range_in_buffer(excerpt_id, range.clone()).to_offset(multi_snapshot)
18564 };
18565
18566 wcx.update_window(wcx.window_handle(), |_, window, acx| {
18567 let current_idx = outline_items
18568 .iter()
18569 .enumerate()
18570 .filter_map(|(idx, item)| {
18571 // Find the closest outline item by distance between outline text and cursor location
18572 let source_range = buffer_range(&item.source_range_for_text);
18573 let distance_to_closest_endpoint = cmp::min(
18574 (source_range.start.0 as isize - cursor_offset.0 as isize).abs(),
18575 (source_range.end.0 as isize - cursor_offset.0 as isize).abs(),
18576 );
18577
18578 let item_towards_offset =
18579 (source_range.start.0 as isize - cursor_offset.0 as isize).signum()
18580 == (offset as isize).signum();
18581
18582 let source_range_contains_cursor = source_range.contains(&cursor_offset);
18583
18584 // To pick the next outline to jump to, we should jump in the direction of the offset, and
18585 // we should not already be within the outline's source range. We then pick the closest outline
18586 // item.
18587 (item_towards_offset && !source_range_contains_cursor)
18588 .then_some((distance_to_closest_endpoint, idx))
18589 })
18590 .min()
18591 .map(|(_, idx)| idx);
18592
18593 let Some(idx) = current_idx else {
18594 return;
18595 };
18596
18597 let range = buffer_range(&outline_items[idx].source_range_for_text);
18598 let selection = [range.start..range.start];
18599
18600 let _ = editor
18601 .update(acx, |editor, ecx| {
18602 editor.change_selections(
18603 SelectionEffects::scroll(Autoscroll::newest()),
18604 window,
18605 ecx,
18606 |s| s.select_ranges(selection),
18607 );
18608 })
18609 .ok();
18610 })?;
18611
18612 Ok(())
18613 })
18614 }
18615
18616 fn go_to_next_symbol(
18617 &mut self,
18618 _: &GoToNextSymbol,
18619 window: &mut Window,
18620 cx: &mut Context<Self>,
18621 ) {
18622 self.go_to_symbol_by_offset(window, cx, 1).detach();
18623 }
18624
18625 fn go_to_previous_symbol(
18626 &mut self,
18627 _: &GoToPreviousSymbol,
18628 window: &mut Window,
18629 cx: &mut Context<Self>,
18630 ) {
18631 self.go_to_symbol_by_offset(window, cx, -1).detach();
18632 }
18633
18634 pub fn go_to_reference_before_or_after_position(
18635 &mut self,
18636 direction: Direction,
18637 count: usize,
18638 window: &mut Window,
18639 cx: &mut Context<Self>,
18640 ) -> Option<Task<Result<()>>> {
18641 let selection = self.selections.newest_anchor();
18642 let head = selection.head();
18643
18644 let multi_buffer = self.buffer.read(cx);
18645
18646 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
18647 let workspace = self.workspace()?;
18648 let project = workspace.read(cx).project().clone();
18649 let references =
18650 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
18651 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
18652 let Some(locations) = references.await? else {
18653 return Ok(());
18654 };
18655
18656 if locations.is_empty() {
18657 // totally normal - the cursor may be on something which is not
18658 // a symbol (e.g. a keyword)
18659 log::info!("no references found under cursor");
18660 return Ok(());
18661 }
18662
18663 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
18664
18665 let (locations, current_location_index) =
18666 multi_buffer.update(cx, |multi_buffer, cx| {
18667 let mut locations = locations
18668 .into_iter()
18669 .filter_map(|loc| {
18670 let start = multi_buffer.buffer_anchor_to_anchor(
18671 &loc.buffer,
18672 loc.range.start,
18673 cx,
18674 )?;
18675 let end = multi_buffer.buffer_anchor_to_anchor(
18676 &loc.buffer,
18677 loc.range.end,
18678 cx,
18679 )?;
18680 Some(start..end)
18681 })
18682 .collect::<Vec<_>>();
18683
18684 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18685 // There is an O(n) implementation, but given this list will be
18686 // small (usually <100 items), the extra O(log(n)) factor isn't
18687 // worth the (surprisingly large amount of) extra complexity.
18688 locations
18689 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
18690
18691 let head_offset = head.to_offset(&multi_buffer_snapshot);
18692
18693 let current_location_index = locations.iter().position(|loc| {
18694 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
18695 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
18696 });
18697
18698 (locations, current_location_index)
18699 });
18700
18701 let Some(current_location_index) = current_location_index else {
18702 // This indicates something has gone wrong, because we already
18703 // handle the "no references" case above
18704 log::error!(
18705 "failed to find current reference under cursor. Total references: {}",
18706 locations.len()
18707 );
18708 return Ok(());
18709 };
18710
18711 let destination_location_index = match direction {
18712 Direction::Next => (current_location_index + count) % locations.len(),
18713 Direction::Prev => {
18714 (current_location_index + locations.len() - count % locations.len())
18715 % locations.len()
18716 }
18717 };
18718
18719 // TODO(cameron): is this needed?
18720 // the thinking is to avoid "jumping to the current location" (avoid
18721 // polluting "jumplist" in vim terms)
18722 if current_location_index == destination_location_index {
18723 return Ok(());
18724 }
18725
18726 let Range { start, end } = locations[destination_location_index];
18727
18728 editor.update_in(cx, |editor, window, cx| {
18729 let effects = SelectionEffects::default();
18730
18731 editor.unfold_ranges(&[start..end], false, false, cx);
18732 editor.change_selections(effects, window, cx, |s| {
18733 s.select_ranges([start..start]);
18734 });
18735 })?;
18736
18737 Ok(())
18738 }))
18739 }
18740
18741 pub fn find_all_references(
18742 &mut self,
18743 action: &FindAllReferences,
18744 window: &mut Window,
18745 cx: &mut Context<Self>,
18746 ) -> Option<Task<Result<Navigated>>> {
18747 let always_open_multibuffer = action.always_open_multibuffer;
18748 let selection = self.selections.newest_anchor();
18749 let multi_buffer = self.buffer.read(cx);
18750 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18751 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
18752 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
18753 let head = selection_offset.head();
18754
18755 let head_anchor = multi_buffer_snapshot.anchor_at(
18756 head,
18757 if head < selection_offset.tail() {
18758 Bias::Right
18759 } else {
18760 Bias::Left
18761 },
18762 );
18763
18764 match self
18765 .find_all_references_task_sources
18766 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18767 {
18768 Ok(_) => {
18769 log::info!(
18770 "Ignoring repeated FindAllReferences invocation with the position of already running task"
18771 );
18772 return None;
18773 }
18774 Err(i) => {
18775 self.find_all_references_task_sources.insert(i, head_anchor);
18776 }
18777 }
18778
18779 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
18780 let workspace = self.workspace()?;
18781 let project = workspace.read(cx).project().clone();
18782 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
18783 Some(cx.spawn_in(window, async move |editor, cx| {
18784 let _cleanup = cx.on_drop(&editor, move |editor, _| {
18785 if let Ok(i) = editor
18786 .find_all_references_task_sources
18787 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
18788 {
18789 editor.find_all_references_task_sources.remove(i);
18790 }
18791 });
18792
18793 let Some(locations) = references.await? else {
18794 return anyhow::Ok(Navigated::No);
18795 };
18796 let mut locations = cx.update(|_, cx| {
18797 locations
18798 .into_iter()
18799 .map(|location| {
18800 let buffer = location.buffer.read(cx);
18801 (location.buffer, location.range.to_point(buffer))
18802 })
18803 // if special-casing the single-match case, remove ranges
18804 // that intersect current selection
18805 .filter(|(location_buffer, location)| {
18806 if always_open_multibuffer || &buffer != location_buffer {
18807 return true;
18808 }
18809
18810 !location.contains_inclusive(&selection_point.range())
18811 })
18812 .into_group_map()
18813 })?;
18814 if locations.is_empty() {
18815 return anyhow::Ok(Navigated::No);
18816 }
18817 for ranges in locations.values_mut() {
18818 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18819 ranges.dedup();
18820 }
18821 let mut num_locations = 0;
18822 for ranges in locations.values_mut() {
18823 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
18824 ranges.dedup();
18825 num_locations += ranges.len();
18826 }
18827
18828 if num_locations == 1 && !always_open_multibuffer {
18829 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
18830 let target_range = target_ranges.first().unwrap().clone();
18831
18832 return editor.update_in(cx, |editor, window, cx| {
18833 let range = target_range.to_point(target_buffer.read(cx));
18834 let range = editor.range_for_match(&range);
18835 let range = range.start..range.start;
18836
18837 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
18838 editor.go_to_singleton_buffer_range(range, window, cx);
18839 } else {
18840 let pane = workspace.read(cx).active_pane().clone();
18841 window.defer(cx, move |window, cx| {
18842 let target_editor: Entity<Self> =
18843 workspace.update(cx, |workspace, cx| {
18844 let pane = workspace.active_pane().clone();
18845
18846 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
18847 let keep_old_preview = preview_tabs_settings
18848 .enable_keep_preview_on_code_navigation;
18849 let allow_new_preview = preview_tabs_settings
18850 .enable_preview_file_from_code_navigation;
18851
18852 workspace.open_project_item(
18853 pane,
18854 target_buffer.clone(),
18855 true,
18856 true,
18857 keep_old_preview,
18858 allow_new_preview,
18859 window,
18860 cx,
18861 )
18862 });
18863 target_editor.update(cx, |target_editor, cx| {
18864 // When selecting a definition in a different buffer, disable the nav history
18865 // to avoid creating a history entry at the previous cursor location.
18866 pane.update(cx, |pane, _| pane.disable_history());
18867 target_editor.go_to_singleton_buffer_range(range, window, cx);
18868 pane.update(cx, |pane, _| pane.enable_history());
18869 });
18870 });
18871 }
18872 Navigated::No
18873 });
18874 }
18875
18876 workspace.update_in(cx, |workspace, window, cx| {
18877 let target = locations
18878 .iter()
18879 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
18880 .map(|(buffer, location)| {
18881 buffer
18882 .read(cx)
18883 .text_for_range(location.clone())
18884 .collect::<String>()
18885 })
18886 .filter(|text| !text.contains('\n'))
18887 .unique()
18888 .take(3)
18889 .join(", ");
18890 let title = if target.is_empty() {
18891 "References".to_owned()
18892 } else {
18893 format!("References to {target}")
18894 };
18895 let allow_preview = PreviewTabsSettings::get_global(cx)
18896 .enable_preview_multibuffer_from_code_navigation;
18897 Self::open_locations_in_multibuffer(
18898 workspace,
18899 locations,
18900 title,
18901 false,
18902 allow_preview,
18903 MultibufferSelectionMode::First,
18904 window,
18905 cx,
18906 );
18907 Navigated::Yes
18908 })
18909 }))
18910 }
18911
18912 /// Opens a multibuffer with the given project locations in it.
18913 pub fn open_locations_in_multibuffer(
18914 workspace: &mut Workspace,
18915 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
18916 title: String,
18917 split: bool,
18918 allow_preview: bool,
18919 multibuffer_selection_mode: MultibufferSelectionMode,
18920 window: &mut Window,
18921 cx: &mut Context<Workspace>,
18922 ) -> Option<(Entity<Editor>, Entity<Pane>)> {
18923 if locations.is_empty() {
18924 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
18925 return None;
18926 }
18927
18928 let capability = workspace.project().read(cx).capability();
18929 let mut ranges = <Vec<Range<Anchor>>>::new();
18930
18931 // a key to find existing multibuffer editors with the same set of locations
18932 // to prevent us from opening more and more multibuffer tabs for searches and the like
18933 let mut key = (title.clone(), vec![]);
18934 let excerpt_buffer = cx.new(|cx| {
18935 let key = &mut key.1;
18936 let mut multibuffer = MultiBuffer::new(capability);
18937 for (buffer, mut ranges_for_buffer) in locations {
18938 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
18939 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18940 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18941 PathKey::for_buffer(&buffer, cx),
18942 buffer.clone(),
18943 ranges_for_buffer,
18944 multibuffer_context_lines(cx),
18945 cx,
18946 );
18947 ranges.extend(new_ranges)
18948 }
18949
18950 multibuffer.with_title(title)
18951 });
18952 let existing = workspace.active_pane().update(cx, |pane, cx| {
18953 pane.items()
18954 .filter_map(|item| item.downcast::<Editor>())
18955 .find(|editor| {
18956 editor
18957 .read(cx)
18958 .lookup_key
18959 .as_ref()
18960 .and_then(|it| {
18961 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18962 })
18963 .is_some_and(|it| *it == key)
18964 })
18965 });
18966 let was_existing = existing.is_some();
18967 let editor = existing.unwrap_or_else(|| {
18968 cx.new(|cx| {
18969 let mut editor = Editor::for_multibuffer(
18970 excerpt_buffer,
18971 Some(workspace.project().clone()),
18972 window,
18973 cx,
18974 );
18975 editor.lookup_key = Some(Box::new(key));
18976 editor
18977 })
18978 });
18979 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18980 MultibufferSelectionMode::First => {
18981 if let Some(first_range) = ranges.first() {
18982 editor.change_selections(
18983 SelectionEffects::no_scroll(),
18984 window,
18985 cx,
18986 |selections| {
18987 selections.clear_disjoint();
18988 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18989 },
18990 );
18991 }
18992 editor.highlight_background(
18993 HighlightKey::Editor,
18994 &ranges,
18995 |_, theme| theme.colors().editor_highlighted_line_background,
18996 cx,
18997 );
18998 }
18999 MultibufferSelectionMode::All => {
19000 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19001 selections.clear_disjoint();
19002 selections.select_anchor_ranges(ranges);
19003 });
19004 }
19005 });
19006
19007 let item = Box::new(editor.clone());
19008
19009 let pane = if split {
19010 workspace.adjacent_pane(window, cx)
19011 } else {
19012 workspace.active_pane().clone()
19013 };
19014 let activate_pane = split;
19015
19016 let mut destination_index = None;
19017 pane.update(cx, |pane, cx| {
19018 if allow_preview && !was_existing {
19019 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
19020 }
19021 if was_existing && !allow_preview {
19022 pane.unpreview_item_if_preview(item.item_id());
19023 }
19024 pane.add_item(item, activate_pane, true, destination_index, window, cx);
19025 });
19026
19027 Some((editor, pane))
19028 }
19029
19030 pub fn rename(
19031 &mut self,
19032 _: &Rename,
19033 window: &mut Window,
19034 cx: &mut Context<Self>,
19035 ) -> Option<Task<Result<()>>> {
19036 use language::ToOffset as _;
19037
19038 let provider = self.semantics_provider.clone()?;
19039 let selection = self.selections.newest_anchor().clone();
19040 let (cursor_buffer, cursor_buffer_position) = self
19041 .buffer
19042 .read(cx)
19043 .text_anchor_for_position(selection.head(), cx)?;
19044 let (tail_buffer, cursor_buffer_position_end) = self
19045 .buffer
19046 .read(cx)
19047 .text_anchor_for_position(selection.tail(), cx)?;
19048 if tail_buffer != cursor_buffer {
19049 return None;
19050 }
19051
19052 let snapshot = cursor_buffer.read(cx).snapshot();
19053 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
19054 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
19055 let prepare_rename = provider
19056 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
19057 .unwrap_or_else(|| Task::ready(Ok(None)));
19058 drop(snapshot);
19059
19060 Some(cx.spawn_in(window, async move |this, cx| {
19061 let rename_range = if let Some(range) = prepare_rename.await? {
19062 Some(range)
19063 } else {
19064 this.update(cx, |this, cx| {
19065 let buffer = this.buffer.read(cx).snapshot(cx);
19066 let mut buffer_highlights = this
19067 .document_highlights_for_position(selection.head(), &buffer)
19068 .filter(|highlight| {
19069 highlight.start.excerpt_id == selection.head().excerpt_id
19070 && highlight.end.excerpt_id == selection.head().excerpt_id
19071 });
19072 buffer_highlights
19073 .next()
19074 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
19075 })?
19076 };
19077 if let Some(rename_range) = rename_range {
19078 this.update_in(cx, |this, window, cx| {
19079 let snapshot = cursor_buffer.read(cx).snapshot();
19080 let rename_buffer_range = rename_range.to_offset(&snapshot);
19081 let cursor_offset_in_rename_range =
19082 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
19083 let cursor_offset_in_rename_range_end =
19084 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
19085
19086 this.take_rename(false, window, cx);
19087 let buffer = this.buffer.read(cx).read(cx);
19088 let cursor_offset = selection.head().to_offset(&buffer);
19089 let rename_start =
19090 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
19091 let rename_end = rename_start + rename_buffer_range.len();
19092 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
19093 let mut old_highlight_id = None;
19094 let old_name: Arc<str> = buffer
19095 .chunks(rename_start..rename_end, true)
19096 .map(|chunk| {
19097 if old_highlight_id.is_none() {
19098 old_highlight_id = chunk.syntax_highlight_id;
19099 }
19100 chunk.text
19101 })
19102 .collect::<String>()
19103 .into();
19104
19105 drop(buffer);
19106
19107 // Position the selection in the rename editor so that it matches the current selection.
19108 this.show_local_selections = false;
19109 let rename_editor = cx.new(|cx| {
19110 let mut editor = Editor::single_line(window, cx);
19111 editor.buffer.update(cx, |buffer, cx| {
19112 buffer.edit(
19113 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
19114 None,
19115 cx,
19116 )
19117 });
19118 let cursor_offset_in_rename_range =
19119 MultiBufferOffset(cursor_offset_in_rename_range);
19120 let cursor_offset_in_rename_range_end =
19121 MultiBufferOffset(cursor_offset_in_rename_range_end);
19122 let rename_selection_range = match cursor_offset_in_rename_range
19123 .cmp(&cursor_offset_in_rename_range_end)
19124 {
19125 Ordering::Equal => {
19126 editor.select_all(&SelectAll, window, cx);
19127 return editor;
19128 }
19129 Ordering::Less => {
19130 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
19131 }
19132 Ordering::Greater => {
19133 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
19134 }
19135 };
19136 if rename_selection_range.end.0 > old_name.len() {
19137 editor.select_all(&SelectAll, window, cx);
19138 } else {
19139 editor.change_selections(Default::default(), window, cx, |s| {
19140 s.select_ranges([rename_selection_range]);
19141 });
19142 }
19143 editor
19144 });
19145 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
19146 if e == &EditorEvent::Focused {
19147 cx.emit(EditorEvent::FocusedIn)
19148 }
19149 })
19150 .detach();
19151
19152 let write_highlights =
19153 this.clear_background_highlights(HighlightKey::DocumentHighlightWrite, cx);
19154 let read_highlights =
19155 this.clear_background_highlights(HighlightKey::DocumentHighlightRead, cx);
19156 let ranges = write_highlights
19157 .iter()
19158 .flat_map(|(_, ranges)| ranges.iter())
19159 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
19160 .cloned()
19161 .collect();
19162
19163 this.highlight_text(
19164 HighlightKey::Rename,
19165 ranges,
19166 HighlightStyle {
19167 fade_out: Some(0.6),
19168 ..Default::default()
19169 },
19170 cx,
19171 );
19172 let rename_focus_handle = rename_editor.focus_handle(cx);
19173 window.focus(&rename_focus_handle, cx);
19174 let block_id = this.insert_blocks(
19175 [BlockProperties {
19176 style: BlockStyle::Flex,
19177 placement: BlockPlacement::Below(range.start),
19178 height: Some(1),
19179 render: Arc::new({
19180 let rename_editor = rename_editor.clone();
19181 move |cx: &mut BlockContext| {
19182 let mut text_style = cx.editor_style.text.clone();
19183 if let Some(highlight_style) = old_highlight_id
19184 .and_then(|h| cx.editor_style.syntax.get(h).cloned())
19185 {
19186 text_style = text_style.highlight(highlight_style);
19187 }
19188 div()
19189 .block_mouse_except_scroll()
19190 .pl(cx.anchor_x)
19191 .child(EditorElement::new(
19192 &rename_editor,
19193 EditorStyle {
19194 background: cx.theme().system().transparent,
19195 local_player: cx.editor_style.local_player,
19196 text: text_style,
19197 scrollbar_width: cx.editor_style.scrollbar_width,
19198 syntax: cx.editor_style.syntax.clone(),
19199 status: cx.editor_style.status.clone(),
19200 inlay_hints_style: HighlightStyle {
19201 font_weight: Some(FontWeight::BOLD),
19202 ..make_inlay_hints_style(cx.app)
19203 },
19204 edit_prediction_styles: make_suggestion_styles(
19205 cx.app,
19206 ),
19207 ..EditorStyle::default()
19208 },
19209 ))
19210 .into_any_element()
19211 }
19212 }),
19213 priority: 0,
19214 }],
19215 Some(Autoscroll::fit()),
19216 cx,
19217 )[0];
19218 this.pending_rename = Some(RenameState {
19219 range,
19220 old_name,
19221 editor: rename_editor,
19222 block_id,
19223 });
19224 })?;
19225 }
19226
19227 Ok(())
19228 }))
19229 }
19230
19231 pub fn confirm_rename(
19232 &mut self,
19233 _: &ConfirmRename,
19234 window: &mut Window,
19235 cx: &mut Context<Self>,
19236 ) -> Option<Task<Result<()>>> {
19237 let rename = self.take_rename(false, window, cx)?;
19238 let workspace = self.workspace()?.downgrade();
19239 let (buffer, start) = self
19240 .buffer
19241 .read(cx)
19242 .text_anchor_for_position(rename.range.start, cx)?;
19243 let (end_buffer, _) = self
19244 .buffer
19245 .read(cx)
19246 .text_anchor_for_position(rename.range.end, cx)?;
19247 if buffer != end_buffer {
19248 return None;
19249 }
19250
19251 let old_name = rename.old_name;
19252 let new_name = rename.editor.read(cx).text(cx);
19253
19254 let rename = self.semantics_provider.as_ref()?.perform_rename(
19255 &buffer,
19256 start,
19257 new_name.clone(),
19258 cx,
19259 )?;
19260
19261 Some(cx.spawn_in(window, async move |editor, cx| {
19262 let project_transaction = rename.await?;
19263 Self::open_project_transaction(
19264 &editor,
19265 workspace,
19266 project_transaction,
19267 format!("Rename: {} → {}", old_name, new_name),
19268 cx,
19269 )
19270 .await?;
19271
19272 editor.update(cx, |editor, cx| {
19273 editor.refresh_document_highlights(cx);
19274 })?;
19275 Ok(())
19276 }))
19277 }
19278
19279 fn take_rename(
19280 &mut self,
19281 moving_cursor: bool,
19282 window: &mut Window,
19283 cx: &mut Context<Self>,
19284 ) -> Option<RenameState> {
19285 let rename = self.pending_rename.take()?;
19286 if rename.editor.focus_handle(cx).is_focused(window) {
19287 window.focus(&self.focus_handle, cx);
19288 }
19289
19290 self.remove_blocks(
19291 [rename.block_id].into_iter().collect(),
19292 Some(Autoscroll::fit()),
19293 cx,
19294 );
19295 self.clear_highlights(HighlightKey::Rename, cx);
19296 self.show_local_selections = true;
19297
19298 if moving_cursor {
19299 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
19300 editor
19301 .selections
19302 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
19303 .head()
19304 });
19305
19306 // Update the selection to match the position of the selection inside
19307 // the rename editor.
19308 let snapshot = self.buffer.read(cx).read(cx);
19309 let rename_range = rename.range.to_offset(&snapshot);
19310 let cursor_in_editor = snapshot
19311 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
19312 .min(rename_range.end);
19313 drop(snapshot);
19314
19315 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19316 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
19317 });
19318 } else {
19319 self.refresh_document_highlights(cx);
19320 }
19321
19322 Some(rename)
19323 }
19324
19325 pub fn pending_rename(&self) -> Option<&RenameState> {
19326 self.pending_rename.as_ref()
19327 }
19328
19329 fn format(
19330 &mut self,
19331 _: &Format,
19332 window: &mut Window,
19333 cx: &mut Context<Self>,
19334 ) -> Option<Task<Result<()>>> {
19335 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19336
19337 let project = match &self.project {
19338 Some(project) => project.clone(),
19339 None => return None,
19340 };
19341
19342 Some(self.perform_format(
19343 project,
19344 FormatTrigger::Manual,
19345 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
19346 window,
19347 cx,
19348 ))
19349 }
19350
19351 fn format_selections(
19352 &mut self,
19353 _: &FormatSelections,
19354 window: &mut Window,
19355 cx: &mut Context<Self>,
19356 ) -> Option<Task<Result<()>>> {
19357 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19358
19359 let project = match &self.project {
19360 Some(project) => project.clone(),
19361 None => return None,
19362 };
19363
19364 let ranges = self
19365 .selections
19366 .all_adjusted(&self.display_snapshot(cx))
19367 .into_iter()
19368 .map(|selection| selection.range())
19369 .collect_vec();
19370
19371 Some(self.perform_format(
19372 project,
19373 FormatTrigger::Manual,
19374 FormatTarget::Ranges(ranges),
19375 window,
19376 cx,
19377 ))
19378 }
19379
19380 fn perform_format(
19381 &mut self,
19382 project: Entity<Project>,
19383 trigger: FormatTrigger,
19384 target: FormatTarget,
19385 window: &mut Window,
19386 cx: &mut Context<Self>,
19387 ) -> Task<Result<()>> {
19388 let buffer = self.buffer.clone();
19389 let (buffers, target) = match target {
19390 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
19391 FormatTarget::Ranges(selection_ranges) => {
19392 let multi_buffer = buffer.read(cx);
19393 let snapshot = multi_buffer.read(cx);
19394 let mut buffers = HashSet::default();
19395 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
19396 BTreeMap::new();
19397 for selection_range in selection_ranges {
19398 for (buffer, buffer_range, _) in
19399 snapshot.range_to_buffer_ranges(selection_range.start..=selection_range.end)
19400 {
19401 let buffer_id = buffer.remote_id();
19402 let start = buffer.anchor_before(buffer_range.start);
19403 let end = buffer.anchor_after(buffer_range.end);
19404 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
19405 buffer_id_to_ranges
19406 .entry(buffer_id)
19407 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
19408 .or_insert_with(|| vec![start..end]);
19409 }
19410 }
19411 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
19412 }
19413 };
19414
19415 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
19416 let selections_prev = transaction_id_prev
19417 .and_then(|transaction_id_prev| {
19418 // default to selections as they were after the last edit, if we have them,
19419 // instead of how they are now.
19420 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
19421 // will take you back to where you made the last edit, instead of staying where you scrolled
19422 self.selection_history
19423 .transaction(transaction_id_prev)
19424 .map(|t| t.0.clone())
19425 })
19426 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
19427
19428 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
19429 let format = project.update(cx, |project, cx| {
19430 project.format(buffers, target, true, trigger, cx)
19431 });
19432
19433 cx.spawn_in(window, async move |editor, cx| {
19434 let transaction = futures::select_biased! {
19435 transaction = format.log_err().fuse() => transaction,
19436 () = timeout => {
19437 log::warn!("timed out waiting for formatting");
19438 None
19439 }
19440 };
19441
19442 buffer.update(cx, |buffer, cx| {
19443 if let Some(transaction) = transaction
19444 && !buffer.is_singleton()
19445 {
19446 buffer.push_transaction(&transaction.0, cx);
19447 }
19448 cx.notify();
19449 });
19450
19451 if let Some(transaction_id_now) =
19452 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
19453 {
19454 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
19455 if has_new_transaction {
19456 editor
19457 .update(cx, |editor, _| {
19458 editor
19459 .selection_history
19460 .insert_transaction(transaction_id_now, selections_prev);
19461 })
19462 .ok();
19463 }
19464 }
19465
19466 Ok(())
19467 })
19468 }
19469
19470 fn organize_imports(
19471 &mut self,
19472 _: &OrganizeImports,
19473 window: &mut Window,
19474 cx: &mut Context<Self>,
19475 ) -> Option<Task<Result<()>>> {
19476 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19477 let project = match &self.project {
19478 Some(project) => project.clone(),
19479 None => return None,
19480 };
19481 Some(self.perform_code_action_kind(
19482 project,
19483 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
19484 window,
19485 cx,
19486 ))
19487 }
19488
19489 fn perform_code_action_kind(
19490 &mut self,
19491 project: Entity<Project>,
19492 kind: CodeActionKind,
19493 window: &mut Window,
19494 cx: &mut Context<Self>,
19495 ) -> Task<Result<()>> {
19496 let buffer = self.buffer.clone();
19497 let buffers = buffer.read(cx).all_buffers();
19498 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
19499 let apply_action = project.update(cx, |project, cx| {
19500 project.apply_code_action_kind(buffers, kind, true, cx)
19501 });
19502 cx.spawn_in(window, async move |_, cx| {
19503 let transaction = futures::select_biased! {
19504 () = timeout => {
19505 log::warn!("timed out waiting for executing code action");
19506 None
19507 }
19508 transaction = apply_action.log_err().fuse() => transaction,
19509 };
19510 buffer.update(cx, |buffer, cx| {
19511 // check if we need this
19512 if let Some(transaction) = transaction
19513 && !buffer.is_singleton()
19514 {
19515 buffer.push_transaction(&transaction.0, cx);
19516 }
19517 cx.notify();
19518 });
19519 Ok(())
19520 })
19521 }
19522
19523 pub fn restart_language_server(
19524 &mut self,
19525 _: &RestartLanguageServer,
19526 _: &mut Window,
19527 cx: &mut Context<Self>,
19528 ) {
19529 if let Some(project) = self.project.clone() {
19530 self.buffer.update(cx, |multi_buffer, cx| {
19531 project.update(cx, |project, cx| {
19532 project.restart_language_servers_for_buffers(
19533 multi_buffer.all_buffers().into_iter().collect(),
19534 HashSet::default(),
19535 cx,
19536 );
19537 });
19538 })
19539 }
19540 }
19541
19542 pub fn stop_language_server(
19543 &mut self,
19544 _: &StopLanguageServer,
19545 _: &mut Window,
19546 cx: &mut Context<Self>,
19547 ) {
19548 if let Some(project) = self.project.clone() {
19549 self.buffer.update(cx, |multi_buffer, cx| {
19550 project.update(cx, |project, cx| {
19551 project.stop_language_servers_for_buffers(
19552 multi_buffer.all_buffers().into_iter().collect(),
19553 HashSet::default(),
19554 cx,
19555 );
19556 });
19557 });
19558 }
19559 }
19560
19561 fn cancel_language_server_work(
19562 workspace: &mut Workspace,
19563 _: &actions::CancelLanguageServerWork,
19564 _: &mut Window,
19565 cx: &mut Context<Workspace>,
19566 ) {
19567 let project = workspace.project();
19568 let buffers = workspace
19569 .active_item(cx)
19570 .and_then(|item| item.act_as::<Editor>(cx))
19571 .map_or(HashSet::default(), |editor| {
19572 editor.read(cx).buffer.read(cx).all_buffers()
19573 });
19574 project.update(cx, |project, cx| {
19575 project.cancel_language_server_work_for_buffers(buffers, cx);
19576 });
19577 }
19578
19579 fn show_character_palette(
19580 &mut self,
19581 _: &ShowCharacterPalette,
19582 window: &mut Window,
19583 _: &mut Context<Self>,
19584 ) {
19585 window.show_character_palette();
19586 }
19587
19588 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
19589 if !self.diagnostics_enabled() {
19590 return;
19591 }
19592
19593 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
19594 let buffer = self.buffer.read(cx).snapshot(cx);
19595 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
19596 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
19597 let is_valid = buffer
19598 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
19599 .any(|entry| {
19600 entry.diagnostic.is_primary
19601 && !entry.range.is_empty()
19602 && entry.range.start == primary_range_start
19603 && entry.diagnostic.message == active_diagnostics.active_message
19604 });
19605
19606 if !is_valid {
19607 self.dismiss_diagnostics(cx);
19608 }
19609 }
19610 }
19611
19612 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
19613 match &self.active_diagnostics {
19614 ActiveDiagnostic::Group(group) => Some(group),
19615 _ => None,
19616 }
19617 }
19618
19619 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
19620 if !self.diagnostics_enabled() {
19621 return;
19622 }
19623 self.dismiss_diagnostics(cx);
19624 self.active_diagnostics = ActiveDiagnostic::All;
19625 }
19626
19627 fn activate_diagnostics(
19628 &mut self,
19629 buffer_id: BufferId,
19630 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
19631 window: &mut Window,
19632 cx: &mut Context<Self>,
19633 ) {
19634 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19635 return;
19636 }
19637 self.dismiss_diagnostics(cx);
19638 let snapshot = self.snapshot(window, cx);
19639 let buffer = self.buffer.read(cx).snapshot(cx);
19640 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
19641 return;
19642 };
19643
19644 let diagnostic_group = buffer
19645 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
19646 .collect::<Vec<_>>();
19647
19648 let language_registry = self
19649 .project()
19650 .map(|project| project.read(cx).languages().clone());
19651
19652 let blocks = renderer.render_group(
19653 diagnostic_group,
19654 buffer_id,
19655 snapshot,
19656 cx.weak_entity(),
19657 language_registry,
19658 cx,
19659 );
19660
19661 let blocks = self.display_map.update(cx, |display_map, cx| {
19662 display_map.insert_blocks(blocks, cx).into_iter().collect()
19663 });
19664 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
19665 active_range: buffer.anchor_before(diagnostic.range.start)
19666 ..buffer.anchor_after(diagnostic.range.end),
19667 active_message: diagnostic.diagnostic.message.clone(),
19668 group_id: diagnostic.diagnostic.group_id,
19669 blocks,
19670 });
19671 cx.notify();
19672 }
19673
19674 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
19675 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
19676 return;
19677 };
19678
19679 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
19680 if let ActiveDiagnostic::Group(group) = prev {
19681 self.display_map.update(cx, |display_map, cx| {
19682 display_map.remove_blocks(group.blocks, cx);
19683 });
19684 cx.notify();
19685 }
19686 }
19687
19688 /// Disable inline diagnostics rendering for this editor.
19689 pub fn disable_inline_diagnostics(&mut self) {
19690 self.inline_diagnostics_enabled = false;
19691 self.inline_diagnostics_update = Task::ready(());
19692 self.inline_diagnostics.clear();
19693 }
19694
19695 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
19696 self.diagnostics_enabled = false;
19697 self.dismiss_diagnostics(cx);
19698 self.inline_diagnostics_update = Task::ready(());
19699 self.inline_diagnostics.clear();
19700 }
19701
19702 pub fn disable_word_completions(&mut self) {
19703 self.word_completions_enabled = false;
19704 }
19705
19706 pub fn diagnostics_enabled(&self) -> bool {
19707 self.diagnostics_enabled && self.lsp_data_enabled()
19708 }
19709
19710 pub fn inline_diagnostics_enabled(&self) -> bool {
19711 self.inline_diagnostics_enabled && self.diagnostics_enabled()
19712 }
19713
19714 pub fn show_inline_diagnostics(&self) -> bool {
19715 self.show_inline_diagnostics
19716 }
19717
19718 pub fn toggle_inline_diagnostics(
19719 &mut self,
19720 _: &ToggleInlineDiagnostics,
19721 window: &mut Window,
19722 cx: &mut Context<Editor>,
19723 ) {
19724 self.show_inline_diagnostics = !self.show_inline_diagnostics;
19725 self.refresh_inline_diagnostics(false, window, cx);
19726 }
19727
19728 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
19729 self.diagnostics_max_severity = severity;
19730 self.display_map.update(cx, |display_map, _| {
19731 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
19732 });
19733 }
19734
19735 pub fn toggle_diagnostics(
19736 &mut self,
19737 _: &ToggleDiagnostics,
19738 window: &mut Window,
19739 cx: &mut Context<Editor>,
19740 ) {
19741 if !self.diagnostics_enabled() {
19742 return;
19743 }
19744
19745 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19746 EditorSettings::get_global(cx)
19747 .diagnostics_max_severity
19748 .filter(|severity| severity != &DiagnosticSeverity::Off)
19749 .unwrap_or(DiagnosticSeverity::Hint)
19750 } else {
19751 DiagnosticSeverity::Off
19752 };
19753 self.set_max_diagnostics_severity(new_severity, cx);
19754 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
19755 self.active_diagnostics = ActiveDiagnostic::None;
19756 self.inline_diagnostics_update = Task::ready(());
19757 self.inline_diagnostics.clear();
19758 } else {
19759 self.refresh_inline_diagnostics(false, window, cx);
19760 }
19761
19762 cx.notify();
19763 }
19764
19765 pub fn toggle_minimap(
19766 &mut self,
19767 _: &ToggleMinimap,
19768 window: &mut Window,
19769 cx: &mut Context<Editor>,
19770 ) {
19771 if self.supports_minimap(cx) {
19772 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
19773 }
19774 }
19775
19776 fn refresh_inline_diagnostics(
19777 &mut self,
19778 debounce: bool,
19779 window: &mut Window,
19780 cx: &mut Context<Self>,
19781 ) {
19782 let max_severity = ProjectSettings::get_global(cx)
19783 .diagnostics
19784 .inline
19785 .max_severity
19786 .unwrap_or(self.diagnostics_max_severity);
19787
19788 if !self.inline_diagnostics_enabled()
19789 || !self.diagnostics_enabled()
19790 || !self.show_inline_diagnostics
19791 || max_severity == DiagnosticSeverity::Off
19792 {
19793 self.inline_diagnostics_update = Task::ready(());
19794 self.inline_diagnostics.clear();
19795 return;
19796 }
19797
19798 let debounce_ms = ProjectSettings::get_global(cx)
19799 .diagnostics
19800 .inline
19801 .update_debounce_ms;
19802 let debounce = if debounce && debounce_ms > 0 {
19803 Some(Duration::from_millis(debounce_ms))
19804 } else {
19805 None
19806 };
19807 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
19808 if let Some(debounce) = debounce {
19809 cx.background_executor().timer(debounce).await;
19810 }
19811 let Some(snapshot) = editor.upgrade().map(|editor| {
19812 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
19813 }) else {
19814 return;
19815 };
19816
19817 let new_inline_diagnostics = cx
19818 .background_spawn(async move {
19819 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
19820 for diagnostic_entry in
19821 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
19822 {
19823 let message = diagnostic_entry
19824 .diagnostic
19825 .message
19826 .split_once('\n')
19827 .map(|(line, _)| line)
19828 .map(SharedString::new)
19829 .unwrap_or_else(|| {
19830 SharedString::new(&*diagnostic_entry.diagnostic.message)
19831 });
19832 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
19833 let (Ok(i) | Err(i)) = inline_diagnostics
19834 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
19835 inline_diagnostics.insert(
19836 i,
19837 (
19838 start_anchor,
19839 InlineDiagnostic {
19840 message,
19841 group_id: diagnostic_entry.diagnostic.group_id,
19842 start: diagnostic_entry.range.start.to_point(&snapshot),
19843 is_primary: diagnostic_entry.diagnostic.is_primary,
19844 severity: diagnostic_entry.diagnostic.severity,
19845 },
19846 ),
19847 );
19848 }
19849 inline_diagnostics
19850 })
19851 .await;
19852
19853 editor
19854 .update(cx, |editor, cx| {
19855 editor.inline_diagnostics = new_inline_diagnostics;
19856 cx.notify();
19857 })
19858 .ok();
19859 });
19860 }
19861
19862 fn pull_diagnostics(
19863 &mut self,
19864 buffer_id: BufferId,
19865 _window: &Window,
19866 cx: &mut Context<Self>,
19867 ) -> Option<()> {
19868 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
19869 // skip any LSP updates for it.
19870
19871 if self.active_diagnostics == ActiveDiagnostic::All || !self.diagnostics_enabled() {
19872 return None;
19873 }
19874 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
19875 .diagnostics
19876 .lsp_pull_diagnostics;
19877 if !pull_diagnostics_settings.enabled {
19878 return None;
19879 }
19880 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
19881 let project = self.project()?.downgrade();
19882 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
19883
19884 self.pull_diagnostics_task = cx.spawn(async move |_, cx| {
19885 cx.background_executor().timer(debounce).await;
19886 if let Ok(task) = project.update(cx, |project, cx| {
19887 project.lsp_store().update(cx, |lsp_store, cx| {
19888 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19889 })
19890 }) {
19891 task.await.log_err();
19892 }
19893 project
19894 .update(cx, |project, cx| {
19895 project.lsp_store().update(cx, |lsp_store, cx| {
19896 lsp_store.pull_document_diagnostics_for_buffer_edit(buffer_id, cx);
19897 })
19898 })
19899 .log_err();
19900 });
19901
19902 Some(())
19903 }
19904
19905 pub fn set_selections_from_remote(
19906 &mut self,
19907 selections: Vec<Selection<Anchor>>,
19908 pending_selection: Option<Selection<Anchor>>,
19909 window: &mut Window,
19910 cx: &mut Context<Self>,
19911 ) {
19912 let old_cursor_position = self.selections.newest_anchor().head();
19913 self.selections
19914 .change_with(&self.display_snapshot(cx), |s| {
19915 s.select_anchors(selections);
19916 if let Some(pending_selection) = pending_selection {
19917 s.set_pending(pending_selection, SelectMode::Character);
19918 } else {
19919 s.clear_pending();
19920 }
19921 });
19922 self.selections_did_change(
19923 false,
19924 &old_cursor_position,
19925 SelectionEffects::default(),
19926 window,
19927 cx,
19928 );
19929 }
19930
19931 pub fn transact(
19932 &mut self,
19933 window: &mut Window,
19934 cx: &mut Context<Self>,
19935 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19936 ) -> Option<TransactionId> {
19937 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19938 this.start_transaction_at(Instant::now(), window, cx);
19939 update(this, window, cx);
19940 this.end_transaction_at(Instant::now(), cx)
19941 })
19942 }
19943
19944 pub fn start_transaction_at(
19945 &mut self,
19946 now: Instant,
19947 window: &mut Window,
19948 cx: &mut Context<Self>,
19949 ) -> Option<TransactionId> {
19950 self.end_selection(window, cx);
19951 if let Some(tx_id) = self
19952 .buffer
19953 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19954 {
19955 self.selection_history
19956 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19957 cx.emit(EditorEvent::TransactionBegun {
19958 transaction_id: tx_id,
19959 });
19960 Some(tx_id)
19961 } else {
19962 None
19963 }
19964 }
19965
19966 pub fn end_transaction_at(
19967 &mut self,
19968 now: Instant,
19969 cx: &mut Context<Self>,
19970 ) -> Option<TransactionId> {
19971 if let Some(transaction_id) = self
19972 .buffer
19973 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19974 {
19975 if let Some((_, end_selections)) =
19976 self.selection_history.transaction_mut(transaction_id)
19977 {
19978 *end_selections = Some(self.selections.disjoint_anchors_arc());
19979 } else {
19980 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19981 }
19982
19983 cx.emit(EditorEvent::Edited { transaction_id });
19984 Some(transaction_id)
19985 } else {
19986 None
19987 }
19988 }
19989
19990 pub fn modify_transaction_selection_history(
19991 &mut self,
19992 transaction_id: TransactionId,
19993 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19994 ) -> bool {
19995 self.selection_history
19996 .transaction_mut(transaction_id)
19997 .map(modify)
19998 .is_some()
19999 }
20000
20001 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
20002 if self.selection_mark_mode {
20003 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20004 s.move_with(&mut |_, sel| {
20005 sel.collapse_to(sel.head(), SelectionGoal::None);
20006 });
20007 })
20008 }
20009 self.selection_mark_mode = true;
20010 cx.notify();
20011 }
20012
20013 pub fn swap_selection_ends(
20014 &mut self,
20015 _: &actions::SwapSelectionEnds,
20016 window: &mut Window,
20017 cx: &mut Context<Self>,
20018 ) {
20019 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20020 s.move_with(&mut |_, sel| {
20021 if sel.start != sel.end {
20022 sel.reversed = !sel.reversed
20023 }
20024 });
20025 });
20026 self.request_autoscroll(Autoscroll::newest(), cx);
20027 cx.notify();
20028 }
20029
20030 pub fn toggle_focus(
20031 workspace: &mut Workspace,
20032 _: &actions::ToggleFocus,
20033 window: &mut Window,
20034 cx: &mut Context<Workspace>,
20035 ) {
20036 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
20037 return;
20038 };
20039 workspace.activate_item(&item, true, true, window, cx);
20040 }
20041
20042 pub fn toggle_fold(
20043 &mut self,
20044 _: &actions::ToggleFold,
20045 window: &mut Window,
20046 cx: &mut Context<Self>,
20047 ) {
20048 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20049 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20050 let selection = self.selections.newest::<Point>(&display_map);
20051
20052 let range = if selection.is_empty() {
20053 let point = selection.head().to_display_point(&display_map);
20054 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20055 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20056 .to_point(&display_map);
20057 start..end
20058 } else {
20059 selection.range()
20060 };
20061 if display_map.folds_in_range(range).next().is_some() {
20062 self.unfold_lines(&Default::default(), window, cx)
20063 } else {
20064 self.fold(&Default::default(), window, cx)
20065 }
20066 } else {
20067 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20068 let buffer_ids: HashSet<_> = self
20069 .selections
20070 .disjoint_anchor_ranges()
20071 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20072 .collect();
20073
20074 let should_unfold = buffer_ids
20075 .iter()
20076 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20077
20078 for buffer_id in buffer_ids {
20079 if should_unfold {
20080 self.unfold_buffer(buffer_id, cx);
20081 } else {
20082 self.fold_buffer(buffer_id, cx);
20083 }
20084 }
20085 }
20086 }
20087
20088 pub fn toggle_fold_recursive(
20089 &mut self,
20090 _: &actions::ToggleFoldRecursive,
20091 window: &mut Window,
20092 cx: &mut Context<Self>,
20093 ) {
20094 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20095
20096 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20097 let range = if selection.is_empty() {
20098 let point = selection.head().to_display_point(&display_map);
20099 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
20100 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
20101 .to_point(&display_map);
20102 start..end
20103 } else {
20104 selection.range()
20105 };
20106 if display_map.folds_in_range(range).next().is_some() {
20107 self.unfold_recursive(&Default::default(), window, cx)
20108 } else {
20109 self.fold_recursive(&Default::default(), window, cx)
20110 }
20111 }
20112
20113 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
20114 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20115 let mut to_fold = Vec::new();
20116 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20117 let selections = self.selections.all_adjusted(&display_map);
20118
20119 for selection in selections {
20120 let range = selection.range().sorted();
20121 let buffer_start_row = range.start.row;
20122
20123 if range.start.row != range.end.row {
20124 let mut found = false;
20125 let mut row = range.start.row;
20126 while row <= range.end.row {
20127 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20128 {
20129 found = true;
20130 row = crease.range().end.row + 1;
20131 to_fold.push(crease);
20132 } else {
20133 row += 1
20134 }
20135 }
20136 if found {
20137 continue;
20138 }
20139 }
20140
20141 for row in (0..=range.start.row).rev() {
20142 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
20143 && crease.range().end.row >= buffer_start_row
20144 {
20145 to_fold.push(crease);
20146 if row <= range.start.row {
20147 break;
20148 }
20149 }
20150 }
20151 }
20152
20153 self.fold_creases(to_fold, true, window, cx);
20154 } else {
20155 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20156 let buffer_ids = self
20157 .selections
20158 .disjoint_anchor_ranges()
20159 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20160 .collect::<HashSet<_>>();
20161 for buffer_id in buffer_ids {
20162 self.fold_buffer(buffer_id, cx);
20163 }
20164 }
20165 }
20166
20167 pub fn toggle_fold_all(
20168 &mut self,
20169 _: &actions::ToggleFoldAll,
20170 window: &mut Window,
20171 cx: &mut Context<Self>,
20172 ) {
20173 let has_folds = if self.buffer.read(cx).is_singleton() {
20174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20175 let has_folds = display_map
20176 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
20177 .next()
20178 .is_some();
20179 has_folds
20180 } else {
20181 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
20182 let has_folds = buffer_ids
20183 .iter()
20184 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
20185 has_folds
20186 };
20187
20188 if has_folds {
20189 self.unfold_all(&actions::UnfoldAll, window, cx);
20190 } else {
20191 self.fold_all(&actions::FoldAll, window, cx);
20192 }
20193 }
20194
20195 fn fold_at_level(
20196 &mut self,
20197 fold_at: &FoldAtLevel,
20198 window: &mut Window,
20199 cx: &mut Context<Self>,
20200 ) {
20201 if !self.buffer.read(cx).is_singleton() {
20202 return;
20203 }
20204
20205 let fold_at_level = fold_at.0;
20206 let snapshot = self.buffer.read(cx).snapshot(cx);
20207 let mut to_fold = Vec::new();
20208 let mut stack = vec![(0, snapshot.max_row().0, 1)];
20209
20210 let row_ranges_to_keep: Vec<Range<u32>> = self
20211 .selections
20212 .all::<Point>(&self.display_snapshot(cx))
20213 .into_iter()
20214 .map(|sel| sel.start.row..sel.end.row)
20215 .collect();
20216
20217 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
20218 while start_row < end_row {
20219 match self
20220 .snapshot(window, cx)
20221 .crease_for_buffer_row(MultiBufferRow(start_row))
20222 {
20223 Some(crease) => {
20224 let nested_start_row = crease.range().start.row + 1;
20225 let nested_end_row = crease.range().end.row;
20226
20227 if current_level < fold_at_level {
20228 stack.push((nested_start_row, nested_end_row, current_level + 1));
20229 } else if current_level == fold_at_level {
20230 // Fold iff there is no selection completely contained within the fold region
20231 if !row_ranges_to_keep.iter().any(|selection| {
20232 selection.end >= nested_start_row
20233 && selection.start <= nested_end_row
20234 }) {
20235 to_fold.push(crease);
20236 }
20237 }
20238
20239 start_row = nested_end_row + 1;
20240 }
20241 None => start_row += 1,
20242 }
20243 }
20244 }
20245
20246 self.fold_creases(to_fold, true, window, cx);
20247 }
20248
20249 pub fn fold_at_level_1(
20250 &mut self,
20251 _: &actions::FoldAtLevel1,
20252 window: &mut Window,
20253 cx: &mut Context<Self>,
20254 ) {
20255 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
20256 }
20257
20258 pub fn fold_at_level_2(
20259 &mut self,
20260 _: &actions::FoldAtLevel2,
20261 window: &mut Window,
20262 cx: &mut Context<Self>,
20263 ) {
20264 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
20265 }
20266
20267 pub fn fold_at_level_3(
20268 &mut self,
20269 _: &actions::FoldAtLevel3,
20270 window: &mut Window,
20271 cx: &mut Context<Self>,
20272 ) {
20273 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
20274 }
20275
20276 pub fn fold_at_level_4(
20277 &mut self,
20278 _: &actions::FoldAtLevel4,
20279 window: &mut Window,
20280 cx: &mut Context<Self>,
20281 ) {
20282 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
20283 }
20284
20285 pub fn fold_at_level_5(
20286 &mut self,
20287 _: &actions::FoldAtLevel5,
20288 window: &mut Window,
20289 cx: &mut Context<Self>,
20290 ) {
20291 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
20292 }
20293
20294 pub fn fold_at_level_6(
20295 &mut self,
20296 _: &actions::FoldAtLevel6,
20297 window: &mut Window,
20298 cx: &mut Context<Self>,
20299 ) {
20300 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
20301 }
20302
20303 pub fn fold_at_level_7(
20304 &mut self,
20305 _: &actions::FoldAtLevel7,
20306 window: &mut Window,
20307 cx: &mut Context<Self>,
20308 ) {
20309 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
20310 }
20311
20312 pub fn fold_at_level_8(
20313 &mut self,
20314 _: &actions::FoldAtLevel8,
20315 window: &mut Window,
20316 cx: &mut Context<Self>,
20317 ) {
20318 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
20319 }
20320
20321 pub fn fold_at_level_9(
20322 &mut self,
20323 _: &actions::FoldAtLevel9,
20324 window: &mut Window,
20325 cx: &mut Context<Self>,
20326 ) {
20327 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
20328 }
20329
20330 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
20331 if self.buffer.read(cx).is_singleton() {
20332 let mut fold_ranges = Vec::new();
20333 let snapshot = self.buffer.read(cx).snapshot(cx);
20334
20335 for row in 0..snapshot.max_row().0 {
20336 if let Some(foldable_range) = self
20337 .snapshot(window, cx)
20338 .crease_for_buffer_row(MultiBufferRow(row))
20339 {
20340 fold_ranges.push(foldable_range);
20341 }
20342 }
20343
20344 self.fold_creases(fold_ranges, true, window, cx);
20345 } else {
20346 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
20347 editor
20348 .update_in(cx, |editor, _, cx| {
20349 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20350 editor.fold_buffer(buffer_id, cx);
20351 }
20352 })
20353 .ok();
20354 });
20355 }
20356 }
20357
20358 pub fn fold_function_bodies(
20359 &mut self,
20360 _: &actions::FoldFunctionBodies,
20361 window: &mut Window,
20362 cx: &mut Context<Self>,
20363 ) {
20364 let snapshot = self.buffer.read(cx).snapshot(cx);
20365
20366 let ranges = snapshot
20367 .text_object_ranges(
20368 MultiBufferOffset(0)..snapshot.len(),
20369 TreeSitterOptions::default(),
20370 )
20371 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
20372 .collect::<Vec<_>>();
20373
20374 let creases = ranges
20375 .into_iter()
20376 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
20377 .collect();
20378
20379 self.fold_creases(creases, true, window, cx);
20380 }
20381
20382 pub fn fold_recursive(
20383 &mut self,
20384 _: &actions::FoldRecursive,
20385 window: &mut Window,
20386 cx: &mut Context<Self>,
20387 ) {
20388 let mut to_fold = Vec::new();
20389 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20390 let selections = self.selections.all_adjusted(&display_map);
20391
20392 for selection in selections {
20393 let range = selection.range().sorted();
20394 let buffer_start_row = range.start.row;
20395
20396 if range.start.row != range.end.row {
20397 let mut found = false;
20398 for row in range.start.row..=range.end.row {
20399 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20400 found = true;
20401 to_fold.push(crease);
20402 }
20403 }
20404 if found {
20405 continue;
20406 }
20407 }
20408
20409 for row in (0..=range.start.row).rev() {
20410 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
20411 if crease.range().end.row >= buffer_start_row {
20412 to_fold.push(crease);
20413 } else {
20414 break;
20415 }
20416 }
20417 }
20418 }
20419
20420 self.fold_creases(to_fold, true, window, cx);
20421 }
20422
20423 pub fn fold_at(
20424 &mut self,
20425 buffer_row: MultiBufferRow,
20426 window: &mut Window,
20427 cx: &mut Context<Self>,
20428 ) {
20429 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20430
20431 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
20432 let autoscroll = self
20433 .selections
20434 .all::<Point>(&display_map)
20435 .iter()
20436 .any(|selection| crease.range().overlaps(&selection.range()));
20437
20438 self.fold_creases(vec![crease], autoscroll, window, cx);
20439 }
20440 }
20441
20442 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
20443 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
20444 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20445 let buffer = display_map.buffer_snapshot();
20446 let selections = self.selections.all::<Point>(&display_map);
20447 let ranges = selections
20448 .iter()
20449 .map(|s| {
20450 let range = s.display_range(&display_map).sorted();
20451 let mut start = range.start.to_point(&display_map);
20452 let mut end = range.end.to_point(&display_map);
20453 start.column = 0;
20454 end.column = buffer.line_len(MultiBufferRow(end.row));
20455 start..end
20456 })
20457 .collect::<Vec<_>>();
20458
20459 self.unfold_ranges(&ranges, true, true, cx);
20460 } else {
20461 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20462 let buffer_ids = self
20463 .selections
20464 .disjoint_anchor_ranges()
20465 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
20466 .collect::<HashSet<_>>();
20467 for buffer_id in buffer_ids {
20468 self.unfold_buffer(buffer_id, cx);
20469 }
20470 }
20471 }
20472
20473 pub fn unfold_recursive(
20474 &mut self,
20475 _: &UnfoldRecursive,
20476 _window: &mut Window,
20477 cx: &mut Context<Self>,
20478 ) {
20479 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20480 let selections = self.selections.all::<Point>(&display_map);
20481 let ranges = selections
20482 .iter()
20483 .map(|s| {
20484 let mut range = s.display_range(&display_map).sorted();
20485 *range.start.column_mut() = 0;
20486 *range.end.column_mut() = display_map.line_len(range.end.row());
20487 let start = range.start.to_point(&display_map);
20488 let end = range.end.to_point(&display_map);
20489 start..end
20490 })
20491 .collect::<Vec<_>>();
20492
20493 self.unfold_ranges(&ranges, true, true, cx);
20494 }
20495
20496 pub fn unfold_at(
20497 &mut self,
20498 buffer_row: MultiBufferRow,
20499 _window: &mut Window,
20500 cx: &mut Context<Self>,
20501 ) {
20502 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20503
20504 let intersection_range = Point::new(buffer_row.0, 0)
20505 ..Point::new(
20506 buffer_row.0,
20507 display_map.buffer_snapshot().line_len(buffer_row),
20508 );
20509
20510 let autoscroll = self
20511 .selections
20512 .all::<Point>(&display_map)
20513 .iter()
20514 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
20515
20516 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
20517 }
20518
20519 pub fn unfold_all(
20520 &mut self,
20521 _: &actions::UnfoldAll,
20522 _window: &mut Window,
20523 cx: &mut Context<Self>,
20524 ) {
20525 if self.buffer.read(cx).is_singleton() {
20526 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20527 self.unfold_ranges(
20528 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
20529 true,
20530 true,
20531 cx,
20532 );
20533 } else {
20534 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
20535 editor
20536 .update(cx, |editor, cx| {
20537 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
20538 editor.unfold_buffer(buffer_id, cx);
20539 }
20540 })
20541 .ok();
20542 });
20543 }
20544 }
20545
20546 pub fn fold_selected_ranges(
20547 &mut self,
20548 _: &FoldSelectedRanges,
20549 window: &mut Window,
20550 cx: &mut Context<Self>,
20551 ) {
20552 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20553 let selections = self.selections.all_adjusted(&display_map);
20554 let ranges = selections
20555 .into_iter()
20556 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
20557 .collect::<Vec<_>>();
20558 self.fold_creases(ranges, true, window, cx);
20559 }
20560
20561 pub fn fold_ranges<T: ToOffset + Clone>(
20562 &mut self,
20563 ranges: Vec<Range<T>>,
20564 auto_scroll: bool,
20565 window: &mut Window,
20566 cx: &mut Context<Self>,
20567 ) {
20568 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
20569 let ranges = ranges
20570 .into_iter()
20571 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
20572 .collect::<Vec<_>>();
20573 self.fold_creases(ranges, auto_scroll, window, cx);
20574 }
20575
20576 pub fn fold_creases<T: ToOffset + Clone>(
20577 &mut self,
20578 creases: Vec<Crease<T>>,
20579 auto_scroll: bool,
20580 window: &mut Window,
20581 cx: &mut Context<Self>,
20582 ) {
20583 if creases.is_empty() {
20584 return;
20585 }
20586
20587 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
20588
20589 if auto_scroll {
20590 self.request_autoscroll(Autoscroll::fit(), cx);
20591 }
20592
20593 cx.notify();
20594
20595 self.scrollbar_marker_state.dirty = true;
20596 self.update_data_on_scroll(window, cx);
20597 self.folds_did_change(cx);
20598 }
20599
20600 /// Removes any folds whose ranges intersect any of the given ranges.
20601 pub fn unfold_ranges<T: ToOffset + Clone>(
20602 &mut self,
20603 ranges: &[Range<T>],
20604 inclusive: bool,
20605 auto_scroll: bool,
20606 cx: &mut Context<Self>,
20607 ) {
20608 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20609 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx);
20610 });
20611 self.folds_did_change(cx);
20612 }
20613
20614 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20615 self.fold_buffers([buffer_id], cx);
20616 }
20617
20618 pub fn fold_buffers(
20619 &mut self,
20620 buffer_ids: impl IntoIterator<Item = BufferId>,
20621 cx: &mut Context<Self>,
20622 ) {
20623 if self.buffer().read(cx).is_singleton() {
20624 return;
20625 }
20626
20627 let ids_to_fold: Vec<BufferId> = buffer_ids
20628 .into_iter()
20629 .filter(|id| !self.is_buffer_folded(*id, cx))
20630 .collect();
20631
20632 if ids_to_fold.is_empty() {
20633 return;
20634 }
20635
20636 let mut all_folded_excerpt_ids = Vec::new();
20637 for buffer_id in &ids_to_fold {
20638 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
20639 all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id));
20640 }
20641
20642 self.display_map.update(cx, |display_map, cx| {
20643 display_map.fold_buffers(ids_to_fold.clone(), cx)
20644 });
20645
20646 let snapshot = self.display_snapshot(cx);
20647 self.selections.change_with(&snapshot, |selections| {
20648 for buffer_id in ids_to_fold {
20649 selections.remove_selections_from_buffer(buffer_id);
20650 }
20651 });
20652
20653 cx.emit(EditorEvent::BufferFoldToggled {
20654 ids: all_folded_excerpt_ids,
20655 folded: true,
20656 });
20657 cx.notify();
20658 }
20659
20660 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20661 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
20662 return;
20663 }
20664 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
20665 self.display_map.update(cx, |display_map, cx| {
20666 display_map.unfold_buffers([buffer_id], cx);
20667 });
20668 cx.emit(EditorEvent::BufferFoldToggled {
20669 ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(),
20670 folded: false,
20671 });
20672 cx.notify();
20673 }
20674
20675 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
20676 self.display_map.read(cx).is_buffer_folded(buffer)
20677 }
20678
20679 pub fn has_any_buffer_folded(&self, cx: &App) -> bool {
20680 if self.buffer().read(cx).is_singleton() {
20681 return false;
20682 }
20683 !self.folded_buffers(cx).is_empty()
20684 }
20685
20686 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
20687 self.display_map.read(cx).folded_buffers()
20688 }
20689
20690 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
20691 self.display_map.update(cx, |display_map, cx| {
20692 display_map.disable_header_for_buffer(buffer_id, cx);
20693 });
20694 cx.notify();
20695 }
20696
20697 /// Removes any folds with the given ranges.
20698 pub fn remove_folds_with_type<T: ToOffset + Clone>(
20699 &mut self,
20700 ranges: &[Range<T>],
20701 type_id: TypeId,
20702 auto_scroll: bool,
20703 cx: &mut Context<Self>,
20704 ) {
20705 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
20706 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
20707 });
20708 self.folds_did_change(cx);
20709 }
20710
20711 fn remove_folds_with<T: ToOffset + Clone>(
20712 &mut self,
20713 ranges: &[Range<T>],
20714 auto_scroll: bool,
20715 cx: &mut Context<Self>,
20716 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
20717 ) {
20718 if ranges.is_empty() {
20719 return;
20720 }
20721
20722 let mut buffers_affected = HashSet::default();
20723 let multi_buffer = self.buffer().read(cx);
20724 for range in ranges {
20725 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
20726 buffers_affected.insert(buffer.read(cx).remote_id());
20727 };
20728 }
20729
20730 self.display_map.update(cx, update);
20731
20732 if auto_scroll {
20733 self.request_autoscroll(Autoscroll::fit(), cx);
20734 }
20735
20736 cx.notify();
20737 self.scrollbar_marker_state.dirty = true;
20738 self.active_indent_guides_state.dirty = true;
20739 }
20740
20741 pub fn update_renderer_widths(
20742 &mut self,
20743 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
20744 cx: &mut Context<Self>,
20745 ) -> bool {
20746 self.display_map
20747 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
20748 }
20749
20750 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
20751 self.display_map.read(cx).fold_placeholder.clone()
20752 }
20753
20754 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
20755 self.buffer.update(cx, |buffer, cx| {
20756 buffer.set_all_diff_hunks_expanded(cx);
20757 });
20758 }
20759
20760 pub fn expand_all_diff_hunks(
20761 &mut self,
20762 _: &ExpandAllDiffHunks,
20763 _window: &mut Window,
20764 cx: &mut Context<Self>,
20765 ) {
20766 self.buffer.update(cx, |buffer, cx| {
20767 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20768 });
20769 }
20770
20771 pub fn collapse_all_diff_hunks(
20772 &mut self,
20773 _: &CollapseAllDiffHunks,
20774 _window: &mut Window,
20775 cx: &mut Context<Self>,
20776 ) {
20777 self.buffer.update(cx, |buffer, cx| {
20778 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
20779 });
20780 }
20781
20782 pub fn toggle_selected_diff_hunks(
20783 &mut self,
20784 _: &ToggleSelectedDiffHunks,
20785 _window: &mut Window,
20786 cx: &mut Context<Self>,
20787 ) {
20788 let ranges: Vec<_> = self
20789 .selections
20790 .disjoint_anchors()
20791 .iter()
20792 .map(|s| s.range())
20793 .collect();
20794 self.toggle_diff_hunks_in_ranges(ranges, cx);
20795 }
20796
20797 pub fn diff_hunks_in_ranges<'a>(
20798 &'a self,
20799 ranges: &'a [Range<Anchor>],
20800 buffer: &'a MultiBufferSnapshot,
20801 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
20802 ranges.iter().flat_map(move |range| {
20803 let end_excerpt_id = range.end.excerpt_id;
20804 let range = range.to_point(buffer);
20805 let mut peek_end = range.end;
20806 if range.end.row < buffer.max_row().0 {
20807 peek_end = Point::new(range.end.row + 1, 0);
20808 }
20809 buffer
20810 .diff_hunks_in_range(range.start..peek_end)
20811 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
20812 })
20813 }
20814
20815 pub fn has_stageable_diff_hunks_in_ranges(
20816 &self,
20817 ranges: &[Range<Anchor>],
20818 snapshot: &MultiBufferSnapshot,
20819 ) -> bool {
20820 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
20821 hunks.any(|hunk| hunk.status().has_secondary_hunk())
20822 }
20823
20824 pub fn toggle_staged_selected_diff_hunks(
20825 &mut self,
20826 _: &::git::ToggleStaged,
20827 _: &mut Window,
20828 cx: &mut Context<Self>,
20829 ) {
20830 let snapshot = self.buffer.read(cx).snapshot(cx);
20831 let ranges: Vec<_> = self
20832 .selections
20833 .disjoint_anchors()
20834 .iter()
20835 .map(|s| s.range())
20836 .collect();
20837 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
20838 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20839 }
20840
20841 pub fn set_render_diff_hunk_controls(
20842 &mut self,
20843 render_diff_hunk_controls: RenderDiffHunkControlsFn,
20844 cx: &mut Context<Self>,
20845 ) {
20846 self.render_diff_hunk_controls = render_diff_hunk_controls;
20847 cx.notify();
20848 }
20849
20850 pub fn stage_and_next(
20851 &mut self,
20852 _: &::git::StageAndNext,
20853 window: &mut Window,
20854 cx: &mut Context<Self>,
20855 ) {
20856 self.do_stage_or_unstage_and_next(true, window, cx);
20857 }
20858
20859 pub fn unstage_and_next(
20860 &mut self,
20861 _: &::git::UnstageAndNext,
20862 window: &mut Window,
20863 cx: &mut Context<Self>,
20864 ) {
20865 self.do_stage_or_unstage_and_next(false, window, cx);
20866 }
20867
20868 pub fn stage_or_unstage_diff_hunks(
20869 &mut self,
20870 stage: bool,
20871 ranges: Vec<Range<Anchor>>,
20872 cx: &mut Context<Self>,
20873 ) {
20874 if self.delegate_stage_and_restore {
20875 let snapshot = self.buffer.read(cx).snapshot(cx);
20876 let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
20877 if !hunks.is_empty() {
20878 cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
20879 }
20880 return;
20881 }
20882 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
20883 cx.spawn(async move |this, cx| {
20884 task.await?;
20885 this.update(cx, |this, cx| {
20886 let snapshot = this.buffer.read(cx).snapshot(cx);
20887 let chunk_by = this
20888 .diff_hunks_in_ranges(&ranges, &snapshot)
20889 .chunk_by(|hunk| hunk.buffer_id);
20890 for (buffer_id, hunks) in &chunk_by {
20891 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
20892 }
20893 })
20894 })
20895 .detach_and_log_err(cx);
20896 }
20897
20898 fn save_buffers_for_ranges_if_needed(
20899 &mut self,
20900 ranges: &[Range<Anchor>],
20901 cx: &mut Context<Editor>,
20902 ) -> Task<Result<()>> {
20903 let multibuffer = self.buffer.read(cx);
20904 let snapshot = multibuffer.read(cx);
20905 let buffer_ids: HashSet<_> = ranges
20906 .iter()
20907 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20908 .collect();
20909 drop(snapshot);
20910
20911 let mut buffers = HashSet::default();
20912 for buffer_id in buffer_ids {
20913 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20914 let buffer = buffer_entity.read(cx);
20915 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20916 {
20917 buffers.insert(buffer_entity);
20918 }
20919 }
20920 }
20921
20922 if let Some(project) = &self.project {
20923 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20924 } else {
20925 Task::ready(Ok(()))
20926 }
20927 }
20928
20929 fn do_stage_or_unstage_and_next(
20930 &mut self,
20931 stage: bool,
20932 window: &mut Window,
20933 cx: &mut Context<Self>,
20934 ) {
20935 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20936
20937 if ranges.iter().any(|range| range.start != range.end) {
20938 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20939 return;
20940 }
20941
20942 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20943
20944 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20945 let wrap_around = !all_diff_hunks_expanded;
20946 let snapshot = self.snapshot(window, cx);
20947 let position = self
20948 .selections
20949 .newest::<Point>(&snapshot.display_snapshot)
20950 .head();
20951
20952 self.go_to_hunk_before_or_after_position(
20953 &snapshot,
20954 position,
20955 Direction::Next,
20956 wrap_around,
20957 window,
20958 cx,
20959 );
20960 }
20961
20962 pub(crate) fn do_stage_or_unstage(
20963 &self,
20964 stage: bool,
20965 buffer_id: BufferId,
20966 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20967 cx: &mut App,
20968 ) -> Option<()> {
20969 let project = self.project()?;
20970 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20971 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20972 let buffer_snapshot = buffer.read(cx).snapshot();
20973 let file_exists = buffer_snapshot
20974 .file()
20975 .is_some_and(|file| file.disk_state().exists());
20976 diff.update(cx, |diff, cx| {
20977 diff.stage_or_unstage_hunks(
20978 stage,
20979 &hunks
20980 .map(|hunk| buffer_diff::DiffHunk {
20981 buffer_range: hunk.buffer_range,
20982 // We don't need to pass in word diffs here because they're only used for rendering and
20983 // this function changes internal state
20984 base_word_diffs: Vec::default(),
20985 buffer_word_diffs: Vec::default(),
20986 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20987 ..hunk.diff_base_byte_range.end.0,
20988 secondary_status: hunk.status.secondary,
20989 range: Point::zero()..Point::zero(), // unused
20990 })
20991 .collect::<Vec<_>>(),
20992 &buffer_snapshot,
20993 file_exists,
20994 cx,
20995 )
20996 });
20997 None
20998 }
20999
21000 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
21001 let ranges: Vec<_> = self
21002 .selections
21003 .disjoint_anchors()
21004 .iter()
21005 .map(|s| s.range())
21006 .collect();
21007 self.buffer
21008 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
21009 }
21010
21011 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
21012 self.buffer.update(cx, |buffer, cx| {
21013 let ranges = vec![Anchor::min()..Anchor::max()];
21014 if !buffer.all_diff_hunks_expanded()
21015 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
21016 {
21017 buffer.collapse_diff_hunks(ranges, cx);
21018 true
21019 } else {
21020 false
21021 }
21022 })
21023 }
21024
21025 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
21026 if self.buffer.read(cx).all_diff_hunks_expanded() {
21027 return true;
21028 }
21029 let ranges = vec![Anchor::min()..Anchor::max()];
21030 self.buffer
21031 .read(cx)
21032 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
21033 }
21034
21035 fn toggle_diff_hunks_in_ranges(
21036 &mut self,
21037 ranges: Vec<Range<Anchor>>,
21038 cx: &mut Context<Editor>,
21039 ) {
21040 self.buffer.update(cx, |buffer, cx| {
21041 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
21042 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
21043 })
21044 }
21045
21046 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
21047 self.buffer.update(cx, |buffer, cx| {
21048 buffer.toggle_single_diff_hunk(range, cx);
21049 })
21050 }
21051
21052 pub(crate) fn apply_all_diff_hunks(
21053 &mut self,
21054 _: &ApplyAllDiffHunks,
21055 window: &mut Window,
21056 cx: &mut Context<Self>,
21057 ) {
21058 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21059
21060 let buffers = self.buffer.read(cx).all_buffers();
21061 for branch_buffer in buffers {
21062 branch_buffer.update(cx, |branch_buffer, cx| {
21063 branch_buffer.merge_into_base(Vec::new(), cx);
21064 });
21065 }
21066
21067 if let Some(project) = self.project.clone() {
21068 self.save(
21069 SaveOptions {
21070 format: true,
21071 autosave: false,
21072 },
21073 project,
21074 window,
21075 cx,
21076 )
21077 .detach_and_log_err(cx);
21078 }
21079 }
21080
21081 pub(crate) fn apply_selected_diff_hunks(
21082 &mut self,
21083 _: &ApplyDiffHunk,
21084 window: &mut Window,
21085 cx: &mut Context<Self>,
21086 ) {
21087 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21088 let snapshot = self.snapshot(window, cx);
21089 let hunks = snapshot.hunks_for_ranges(
21090 self.selections
21091 .all(&snapshot.display_snapshot)
21092 .into_iter()
21093 .map(|selection| selection.range()),
21094 );
21095 let mut ranges_by_buffer = HashMap::default();
21096 self.transact(window, cx, |editor, _window, cx| {
21097 for hunk in hunks {
21098 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
21099 ranges_by_buffer
21100 .entry(buffer.clone())
21101 .or_insert_with(Vec::new)
21102 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
21103 }
21104 }
21105
21106 for (buffer, ranges) in ranges_by_buffer {
21107 buffer.update(cx, |buffer, cx| {
21108 buffer.merge_into_base(ranges, cx);
21109 });
21110 }
21111 });
21112
21113 if let Some(project) = self.project.clone() {
21114 self.save(
21115 SaveOptions {
21116 format: true,
21117 autosave: false,
21118 },
21119 project,
21120 window,
21121 cx,
21122 )
21123 .detach_and_log_err(cx);
21124 }
21125 }
21126
21127 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
21128 if hovered != self.gutter_hovered {
21129 self.gutter_hovered = hovered;
21130 cx.notify();
21131 }
21132 }
21133
21134 pub fn insert_blocks(
21135 &mut self,
21136 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
21137 autoscroll: Option<Autoscroll>,
21138 cx: &mut Context<Self>,
21139 ) -> Vec<CustomBlockId> {
21140 let blocks = self
21141 .display_map
21142 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
21143 if let Some(autoscroll) = autoscroll {
21144 self.request_autoscroll(autoscroll, cx);
21145 }
21146 cx.notify();
21147 blocks
21148 }
21149
21150 pub fn resize_blocks(
21151 &mut self,
21152 heights: HashMap<CustomBlockId, u32>,
21153 autoscroll: Option<Autoscroll>,
21154 cx: &mut Context<Self>,
21155 ) {
21156 self.display_map
21157 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
21158 if let Some(autoscroll) = autoscroll {
21159 self.request_autoscroll(autoscroll, cx);
21160 }
21161 cx.notify();
21162 }
21163
21164 pub fn replace_blocks(
21165 &mut self,
21166 renderers: HashMap<CustomBlockId, RenderBlock>,
21167 autoscroll: Option<Autoscroll>,
21168 cx: &mut Context<Self>,
21169 ) {
21170 self.display_map
21171 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
21172 if let Some(autoscroll) = autoscroll {
21173 self.request_autoscroll(autoscroll, cx);
21174 }
21175 cx.notify();
21176 }
21177
21178 pub fn remove_blocks(
21179 &mut self,
21180 block_ids: HashSet<CustomBlockId>,
21181 autoscroll: Option<Autoscroll>,
21182 cx: &mut Context<Self>,
21183 ) {
21184 self.display_map.update(cx, |display_map, cx| {
21185 display_map.remove_blocks(block_ids, cx)
21186 });
21187 if let Some(autoscroll) = autoscroll {
21188 self.request_autoscroll(autoscroll, cx);
21189 }
21190 cx.notify();
21191 }
21192
21193 pub fn row_for_block(
21194 &self,
21195 block_id: CustomBlockId,
21196 cx: &mut Context<Self>,
21197 ) -> Option<DisplayRow> {
21198 self.display_map
21199 .update(cx, |map, cx| map.row_for_block(block_id, cx))
21200 }
21201
21202 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
21203 self.focused_block = Some(focused_block);
21204 }
21205
21206 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
21207 self.focused_block.take()
21208 }
21209
21210 pub fn insert_creases(
21211 &mut self,
21212 creases: impl IntoIterator<Item = Crease<Anchor>>,
21213 cx: &mut Context<Self>,
21214 ) -> Vec<CreaseId> {
21215 self.display_map
21216 .update(cx, |map, cx| map.insert_creases(creases, cx))
21217 }
21218
21219 pub fn remove_creases(
21220 &mut self,
21221 ids: impl IntoIterator<Item = CreaseId>,
21222 cx: &mut Context<Self>,
21223 ) -> Vec<(CreaseId, Range<Anchor>)> {
21224 self.display_map
21225 .update(cx, |map, cx| map.remove_creases(ids, cx))
21226 }
21227
21228 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
21229 self.display_map
21230 .update(cx, |map, cx| map.snapshot(cx))
21231 .longest_row()
21232 }
21233
21234 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
21235 self.display_map
21236 .update(cx, |map, cx| map.snapshot(cx))
21237 .max_point()
21238 }
21239
21240 pub fn text(&self, cx: &App) -> String {
21241 self.buffer.read(cx).read(cx).text()
21242 }
21243
21244 pub fn is_empty(&self, cx: &App) -> bool {
21245 self.buffer.read(cx).read(cx).is_empty()
21246 }
21247
21248 pub fn text_option(&self, cx: &App) -> Option<String> {
21249 let text = self.text(cx);
21250 let text = text.trim();
21251
21252 if text.is_empty() {
21253 return None;
21254 }
21255
21256 Some(text.to_string())
21257 }
21258
21259 pub fn set_text(
21260 &mut self,
21261 text: impl Into<Arc<str>>,
21262 window: &mut Window,
21263 cx: &mut Context<Self>,
21264 ) {
21265 self.transact(window, cx, |this, _, cx| {
21266 this.buffer
21267 .read(cx)
21268 .as_singleton()
21269 .expect("you can only call set_text on editors for singleton buffers")
21270 .update(cx, |buffer, cx| buffer.set_text(text, cx));
21271 });
21272 }
21273
21274 pub fn display_text(&self, cx: &mut App) -> String {
21275 self.display_map
21276 .update(cx, |map, cx| map.snapshot(cx))
21277 .text()
21278 }
21279
21280 fn create_minimap(
21281 &self,
21282 minimap_settings: MinimapSettings,
21283 window: &mut Window,
21284 cx: &mut Context<Self>,
21285 ) -> Option<Entity<Self>> {
21286 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
21287 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
21288 }
21289
21290 fn initialize_new_minimap(
21291 &self,
21292 minimap_settings: MinimapSettings,
21293 window: &mut Window,
21294 cx: &mut Context<Self>,
21295 ) -> Entity<Self> {
21296 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
21297 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
21298
21299 let mut minimap = Editor::new_internal(
21300 EditorMode::Minimap {
21301 parent: cx.weak_entity(),
21302 },
21303 self.buffer.clone(),
21304 None,
21305 Some(self.display_map.clone()),
21306 window,
21307 cx,
21308 );
21309 let my_snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
21310 let minimap_snapshot = minimap.display_map.update(cx, |map, cx| map.snapshot(cx));
21311 minimap.scroll_manager.clone_state(
21312 &self.scroll_manager,
21313 &my_snapshot,
21314 &minimap_snapshot,
21315 cx,
21316 );
21317 minimap.set_text_style_refinement(TextStyleRefinement {
21318 font_size: Some(MINIMAP_FONT_SIZE),
21319 font_weight: Some(MINIMAP_FONT_WEIGHT),
21320 font_family: Some(MINIMAP_FONT_FAMILY),
21321 ..Default::default()
21322 });
21323 minimap.update_minimap_configuration(minimap_settings, cx);
21324 cx.new(|_| minimap)
21325 }
21326
21327 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
21328 let current_line_highlight = minimap_settings
21329 .current_line_highlight
21330 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
21331 self.set_current_line_highlight(Some(current_line_highlight));
21332 }
21333
21334 pub fn minimap(&self) -> Option<&Entity<Self>> {
21335 self.minimap
21336 .as_ref()
21337 .filter(|_| self.minimap_visibility.visible())
21338 }
21339
21340 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
21341 let mut wrap_guides = smallvec![];
21342
21343 if self.show_wrap_guides == Some(false) {
21344 return wrap_guides;
21345 }
21346
21347 let settings = self.buffer.read(cx).language_settings(cx);
21348 if settings.show_wrap_guides {
21349 match self.soft_wrap_mode(cx) {
21350 SoftWrap::Column(soft_wrap) => {
21351 wrap_guides.push((soft_wrap as usize, true));
21352 }
21353 SoftWrap::Bounded(soft_wrap) => {
21354 wrap_guides.push((soft_wrap as usize, true));
21355 }
21356 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
21357 }
21358 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
21359 }
21360
21361 wrap_guides
21362 }
21363
21364 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
21365 let settings = self.buffer.read(cx).language_settings(cx);
21366 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
21367 match mode {
21368 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
21369 SoftWrap::None
21370 }
21371 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
21372 language_settings::SoftWrap::PreferredLineLength => {
21373 SoftWrap::Column(settings.preferred_line_length)
21374 }
21375 language_settings::SoftWrap::Bounded => {
21376 SoftWrap::Bounded(settings.preferred_line_length)
21377 }
21378 }
21379 }
21380
21381 pub fn set_soft_wrap_mode(
21382 &mut self,
21383 mode: language_settings::SoftWrap,
21384 cx: &mut Context<Self>,
21385 ) {
21386 self.soft_wrap_mode_override = Some(mode);
21387 cx.notify();
21388 }
21389
21390 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
21391 self.hard_wrap = hard_wrap;
21392 cx.notify();
21393 }
21394
21395 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
21396 self.text_style_refinement = Some(style);
21397 }
21398
21399 /// called by the Element so we know what style we were most recently rendered with.
21400 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
21401 // We intentionally do not inform the display map about the minimap style
21402 // so that wrapping is not recalculated and stays consistent for the editor
21403 // and its linked minimap.
21404 if !self.mode.is_minimap() {
21405 let font = style.text.font();
21406 let font_size = style.text.font_size.to_pixels(window.rem_size());
21407 let display_map = self
21408 .placeholder_display_map
21409 .as_ref()
21410 .filter(|_| self.is_empty(cx))
21411 .unwrap_or(&self.display_map);
21412
21413 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
21414 }
21415 self.style = Some(style);
21416 }
21417
21418 pub fn style(&mut self, cx: &App) -> &EditorStyle {
21419 if self.style.is_none() {
21420 self.style = Some(self.create_style(cx));
21421 }
21422 self.style.as_ref().unwrap()
21423 }
21424
21425 // Called by the element. This method is not designed to be called outside of the editor
21426 // element's layout code because it does not notify when rewrapping is computed synchronously.
21427 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
21428 if self.is_empty(cx) {
21429 self.placeholder_display_map
21430 .as_ref()
21431 .map_or(false, |display_map| {
21432 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
21433 })
21434 } else {
21435 self.display_map
21436 .update(cx, |map, cx| map.set_wrap_width(width, cx))
21437 }
21438 }
21439
21440 pub fn set_soft_wrap(&mut self) {
21441 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
21442 }
21443
21444 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
21445 if self.soft_wrap_mode_override.is_some() {
21446 self.soft_wrap_mode_override.take();
21447 } else {
21448 let soft_wrap = match self.soft_wrap_mode(cx) {
21449 SoftWrap::GitDiff => return,
21450 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
21451 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
21452 language_settings::SoftWrap::None
21453 }
21454 };
21455 self.soft_wrap_mode_override = Some(soft_wrap);
21456 }
21457 cx.notify();
21458 }
21459
21460 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
21461 let Some(workspace) = self.workspace() else {
21462 return;
21463 };
21464 let fs = workspace.read(cx).app_state().fs.clone();
21465 let current_show = TabBarSettings::get_global(cx).show;
21466 update_settings_file(fs, cx, move |setting, _| {
21467 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
21468 });
21469 }
21470
21471 pub fn toggle_indent_guides(
21472 &mut self,
21473 _: &ToggleIndentGuides,
21474 _: &mut Window,
21475 cx: &mut Context<Self>,
21476 ) {
21477 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
21478 self.buffer
21479 .read(cx)
21480 .language_settings(cx)
21481 .indent_guides
21482 .enabled
21483 });
21484 self.show_indent_guides = Some(!currently_enabled);
21485 cx.notify();
21486 }
21487
21488 fn should_show_indent_guides(&self) -> Option<bool> {
21489 self.show_indent_guides
21490 }
21491
21492 pub fn disable_indent_guides_for_buffer(
21493 &mut self,
21494 buffer_id: BufferId,
21495 cx: &mut Context<Self>,
21496 ) {
21497 self.buffers_with_disabled_indent_guides.insert(buffer_id);
21498 cx.notify();
21499 }
21500
21501 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
21502 self.buffers_with_disabled_indent_guides
21503 .contains(&buffer_id)
21504 }
21505
21506 pub fn toggle_line_numbers(
21507 &mut self,
21508 _: &ToggleLineNumbers,
21509 _: &mut Window,
21510 cx: &mut Context<Self>,
21511 ) {
21512 let mut editor_settings = EditorSettings::get_global(cx).clone();
21513 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
21514 EditorSettings::override_global(editor_settings, cx);
21515 }
21516
21517 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
21518 if let Some(show_line_numbers) = self.show_line_numbers {
21519 return show_line_numbers;
21520 }
21521 EditorSettings::get_global(cx).gutter.line_numbers
21522 }
21523
21524 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
21525 match (
21526 self.use_relative_line_numbers,
21527 EditorSettings::get_global(cx).relative_line_numbers,
21528 ) {
21529 (None, setting) => setting,
21530 (Some(false), _) => RelativeLineNumbers::Disabled,
21531 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
21532 (Some(true), _) => RelativeLineNumbers::Enabled,
21533 }
21534 }
21535
21536 pub fn toggle_relative_line_numbers(
21537 &mut self,
21538 _: &ToggleRelativeLineNumbers,
21539 _: &mut Window,
21540 cx: &mut Context<Self>,
21541 ) {
21542 let is_relative = self.relative_line_numbers(cx);
21543 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
21544 }
21545
21546 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
21547 self.use_relative_line_numbers = is_relative;
21548 cx.notify();
21549 }
21550
21551 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
21552 self.show_gutter = show_gutter;
21553 cx.notify();
21554 }
21555
21556 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
21557 self.show_scrollbars = ScrollbarAxes {
21558 horizontal: show,
21559 vertical: show,
21560 };
21561 cx.notify();
21562 }
21563
21564 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21565 self.show_scrollbars.vertical = show;
21566 cx.notify();
21567 }
21568
21569 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
21570 self.show_scrollbars.horizontal = show;
21571 cx.notify();
21572 }
21573
21574 pub fn set_minimap_visibility(
21575 &mut self,
21576 minimap_visibility: MinimapVisibility,
21577 window: &mut Window,
21578 cx: &mut Context<Self>,
21579 ) {
21580 if self.minimap_visibility != minimap_visibility {
21581 if minimap_visibility.visible() && self.minimap.is_none() {
21582 let minimap_settings = EditorSettings::get_global(cx).minimap;
21583 self.minimap =
21584 self.create_minimap(minimap_settings.with_show_override(), window, cx);
21585 }
21586 self.minimap_visibility = minimap_visibility;
21587 cx.notify();
21588 }
21589 }
21590
21591 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21592 self.set_show_scrollbars(false, cx);
21593 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
21594 }
21595
21596 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21597 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
21598 }
21599
21600 /// Normally the text in full mode and auto height editors is padded on the
21601 /// left side by roughly half a character width for improved hit testing.
21602 ///
21603 /// Use this method to disable this for cases where this is not wanted (e.g.
21604 /// if you want to align the editor text with some other text above or below)
21605 /// or if you want to add this padding to single-line editors.
21606 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
21607 self.offset_content = offset_content;
21608 cx.notify();
21609 }
21610
21611 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
21612 self.show_line_numbers = Some(show_line_numbers);
21613 cx.notify();
21614 }
21615
21616 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
21617 self.disable_expand_excerpt_buttons = true;
21618 cx.notify();
21619 }
21620
21621 pub fn set_number_deleted_lines(&mut self, number: bool, cx: &mut Context<Self>) {
21622 self.number_deleted_lines = number;
21623 cx.notify();
21624 }
21625
21626 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
21627 self.delegate_expand_excerpts = delegate;
21628 }
21629
21630 pub fn set_delegate_stage_and_restore(&mut self, delegate: bool) {
21631 self.delegate_stage_and_restore = delegate;
21632 }
21633
21634 pub fn set_delegate_open_excerpts(&mut self, delegate: bool) {
21635 self.delegate_open_excerpts = delegate;
21636 }
21637
21638 pub fn set_on_local_selections_changed(
21639 &mut self,
21640 callback: Option<Box<dyn Fn(Point, &mut Window, &mut Context<Self>) + 'static>>,
21641 ) {
21642 self.on_local_selections_changed = callback;
21643 }
21644
21645 pub fn set_suppress_selection_callback(&mut self, suppress: bool) {
21646 self.suppress_selection_callback = suppress;
21647 }
21648
21649 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
21650 self.show_git_diff_gutter = Some(show_git_diff_gutter);
21651 cx.notify();
21652 }
21653
21654 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
21655 self.show_code_actions = Some(show_code_actions);
21656 cx.notify();
21657 }
21658
21659 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
21660 self.show_runnables = Some(show_runnables);
21661 cx.notify();
21662 }
21663
21664 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
21665 self.show_breakpoints = Some(show_breakpoints);
21666 cx.notify();
21667 }
21668
21669 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
21670 self.show_diff_review_button = show;
21671 cx.notify();
21672 }
21673
21674 pub fn show_diff_review_button(&self) -> bool {
21675 self.show_diff_review_button
21676 }
21677
21678 pub fn render_diff_review_button(
21679 &self,
21680 display_row: DisplayRow,
21681 width: Pixels,
21682 cx: &mut Context<Self>,
21683 ) -> impl IntoElement {
21684 let text_color = cx.theme().colors().text;
21685 let icon_color = cx.theme().colors().icon_accent;
21686
21687 h_flex()
21688 .id("diff_review_button")
21689 .cursor_pointer()
21690 .w(width - px(1.))
21691 .h(relative(0.9))
21692 .justify_center()
21693 .rounded_sm()
21694 .border_1()
21695 .border_color(text_color.opacity(0.1))
21696 .bg(text_color.opacity(0.15))
21697 .hover(|s| {
21698 s.bg(icon_color.opacity(0.4))
21699 .border_color(icon_color.opacity(0.5))
21700 })
21701 .child(Icon::new(IconName::Plus).size(IconSize::Small))
21702 .tooltip(Tooltip::text("Add Review (drag to select multiple lines)"))
21703 .on_mouse_down(
21704 gpui::MouseButton::Left,
21705 cx.listener(move |editor, _event: &gpui::MouseDownEvent, window, cx| {
21706 editor.start_diff_review_drag(display_row, window, cx);
21707 }),
21708 )
21709 }
21710
21711 pub fn start_diff_review_drag(
21712 &mut self,
21713 display_row: DisplayRow,
21714 window: &mut Window,
21715 cx: &mut Context<Self>,
21716 ) {
21717 let snapshot = self.snapshot(window, cx);
21718 let point = snapshot
21719 .display_snapshot
21720 .display_point_to_point(DisplayPoint::new(display_row, 0), Bias::Left);
21721 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21722 self.diff_review_drag_state = Some(DiffReviewDragState {
21723 start_anchor: anchor,
21724 current_anchor: anchor,
21725 });
21726 cx.notify();
21727 }
21728
21729 pub fn update_diff_review_drag(
21730 &mut self,
21731 display_row: DisplayRow,
21732 window: &mut Window,
21733 cx: &mut Context<Self>,
21734 ) {
21735 if self.diff_review_drag_state.is_none() {
21736 return;
21737 }
21738 let snapshot = self.snapshot(window, cx);
21739 let point = snapshot
21740 .display_snapshot
21741 .display_point_to_point(display_row.as_display_point(), Bias::Left);
21742 let anchor = snapshot.buffer_snapshot().anchor_before(point);
21743 if let Some(drag_state) = &mut self.diff_review_drag_state {
21744 drag_state.current_anchor = anchor;
21745 cx.notify();
21746 }
21747 }
21748
21749 pub fn end_diff_review_drag(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21750 if let Some(drag_state) = self.diff_review_drag_state.take() {
21751 let snapshot = self.snapshot(window, cx);
21752 let range = drag_state.row_range(&snapshot.display_snapshot);
21753 self.show_diff_review_overlay(*range.start()..*range.end(), window, cx);
21754 }
21755 cx.notify();
21756 }
21757
21758 pub fn cancel_diff_review_drag(&mut self, cx: &mut Context<Self>) {
21759 self.diff_review_drag_state = None;
21760 cx.notify();
21761 }
21762
21763 /// Calculates the appropriate block height for the diff review overlay.
21764 /// Height is in lines: 2 for input row, 1 for header when comments exist,
21765 /// and 2 lines per comment when expanded.
21766 fn calculate_overlay_height(
21767 &self,
21768 hunk_key: &DiffHunkKey,
21769 comments_expanded: bool,
21770 snapshot: &MultiBufferSnapshot,
21771 ) -> u32 {
21772 let comment_count = self.hunk_comment_count(hunk_key, snapshot);
21773 let base_height: u32 = 2; // Input row with avatar and buttons
21774
21775 if comment_count == 0 {
21776 base_height
21777 } else if comments_expanded {
21778 // Header (1 line) + 2 lines per comment
21779 base_height + 1 + (comment_count as u32 * 2)
21780 } else {
21781 // Just header when collapsed
21782 base_height + 1
21783 }
21784 }
21785
21786 pub fn show_diff_review_overlay(
21787 &mut self,
21788 display_range: Range<DisplayRow>,
21789 window: &mut Window,
21790 cx: &mut Context<Self>,
21791 ) {
21792 let Range { start, end } = display_range.sorted();
21793
21794 let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21795 let editor_snapshot = self.snapshot(window, cx);
21796
21797 // Convert display rows to multibuffer points
21798 let start_point = editor_snapshot
21799 .display_snapshot
21800 .display_point_to_point(start.as_display_point(), Bias::Left);
21801 let end_point = editor_snapshot
21802 .display_snapshot
21803 .display_point_to_point(end.as_display_point(), Bias::Left);
21804 let end_multi_buffer_row = MultiBufferRow(end_point.row);
21805
21806 // Create anchor range for the selected lines (start of first line to end of last line)
21807 let line_end = Point::new(
21808 end_point.row,
21809 buffer_snapshot.line_len(end_multi_buffer_row),
21810 );
21811 let anchor_range =
21812 buffer_snapshot.anchor_after(start_point)..buffer_snapshot.anchor_before(line_end);
21813
21814 // Compute the hunk key for this display row
21815 let file_path = buffer_snapshot
21816 .file_at(start_point)
21817 .map(|file: &Arc<dyn language::File>| file.path().clone())
21818 .unwrap_or_else(|| Arc::from(util::rel_path::RelPath::empty()));
21819 let hunk_start_anchor = buffer_snapshot.anchor_before(start_point);
21820 let new_hunk_key = DiffHunkKey {
21821 file_path,
21822 hunk_start_anchor,
21823 };
21824
21825 // Check if we already have an overlay for this hunk
21826 if let Some(existing_overlay) = self.diff_review_overlays.iter().find(|overlay| {
21827 Self::hunk_keys_match(&overlay.hunk_key, &new_hunk_key, &buffer_snapshot)
21828 }) {
21829 // Just focus the existing overlay's prompt editor
21830 let focus_handle = existing_overlay.prompt_editor.focus_handle(cx);
21831 window.focus(&focus_handle, cx);
21832 return;
21833 }
21834
21835 // Dismiss overlays that have no comments for their hunks
21836 self.dismiss_overlays_without_comments(cx);
21837
21838 // Get the current user's avatar URI from the project's user_store
21839 let user_avatar_uri = self.project.as_ref().and_then(|project| {
21840 let user_store = project.read(cx).user_store();
21841 user_store
21842 .read(cx)
21843 .current_user()
21844 .map(|user| user.avatar_uri.clone())
21845 });
21846
21847 // Create anchor at the end of the last row so the block appears immediately below it
21848 // Use multibuffer coordinates for anchor creation
21849 let line_len = buffer_snapshot.line_len(end_multi_buffer_row);
21850 let anchor = buffer_snapshot.anchor_after(Point::new(end_multi_buffer_row.0, line_len));
21851
21852 // Use the hunk key we already computed
21853 let hunk_key = new_hunk_key;
21854
21855 // Create the prompt editor for the review input
21856 let prompt_editor = cx.new(|cx| {
21857 let mut editor = Editor::single_line(window, cx);
21858 editor.set_placeholder_text("Add a review comment...", window, cx);
21859 editor
21860 });
21861
21862 // Register the Newline action on the prompt editor to submit the review
21863 let parent_editor = cx.entity().downgrade();
21864 let subscription = prompt_editor.update(cx, |prompt_editor, _cx| {
21865 prompt_editor.register_action({
21866 let parent_editor = parent_editor.clone();
21867 move |_: &crate::actions::Newline, window, cx| {
21868 if let Some(editor) = parent_editor.upgrade() {
21869 editor.update(cx, |editor, cx| {
21870 editor.submit_diff_review_comment(window, cx);
21871 });
21872 }
21873 }
21874 })
21875 });
21876
21877 // Calculate initial height based on existing comments for this hunk
21878 let initial_height = self.calculate_overlay_height(&hunk_key, true, &buffer_snapshot);
21879
21880 // Create the overlay block
21881 let prompt_editor_for_render = prompt_editor.clone();
21882 let hunk_key_for_render = hunk_key.clone();
21883 let editor_handle = cx.entity().downgrade();
21884 let block = BlockProperties {
21885 style: BlockStyle::Sticky,
21886 placement: BlockPlacement::Below(anchor),
21887 height: Some(initial_height),
21888 render: Arc::new(move |cx| {
21889 Self::render_diff_review_overlay(
21890 &prompt_editor_for_render,
21891 &hunk_key_for_render,
21892 &editor_handle,
21893 cx,
21894 )
21895 }),
21896 priority: 0,
21897 };
21898
21899 let block_ids = self.insert_blocks([block], None, cx);
21900 let Some(block_id) = block_ids.into_iter().next() else {
21901 log::error!("Failed to insert diff review overlay block");
21902 return;
21903 };
21904
21905 self.diff_review_overlays.push(DiffReviewOverlay {
21906 anchor_range,
21907 block_id,
21908 prompt_editor: prompt_editor.clone(),
21909 hunk_key,
21910 comments_expanded: true,
21911 inline_edit_editors: HashMap::default(),
21912 inline_edit_subscriptions: HashMap::default(),
21913 user_avatar_uri,
21914 _subscription: subscription,
21915 });
21916
21917 // Focus the prompt editor
21918 let focus_handle = prompt_editor.focus_handle(cx);
21919 window.focus(&focus_handle, cx);
21920
21921 cx.notify();
21922 }
21923
21924 /// Dismisses all diff review overlays.
21925 pub fn dismiss_all_diff_review_overlays(&mut self, cx: &mut Context<Self>) {
21926 if self.diff_review_overlays.is_empty() {
21927 return;
21928 }
21929 let block_ids: HashSet<_> = self
21930 .diff_review_overlays
21931 .drain(..)
21932 .map(|overlay| overlay.block_id)
21933 .collect();
21934 self.remove_blocks(block_ids, None, cx);
21935 cx.notify();
21936 }
21937
21938 /// Dismisses overlays that have no comments stored for their hunks.
21939 /// Keeps overlays that have at least one comment.
21940 fn dismiss_overlays_without_comments(&mut self, cx: &mut Context<Self>) {
21941 let snapshot = self.buffer.read(cx).snapshot(cx);
21942
21943 // First, compute which overlays have comments (to avoid borrow issues with retain)
21944 let overlays_with_comments: Vec<bool> = self
21945 .diff_review_overlays
21946 .iter()
21947 .map(|overlay| self.hunk_comment_count(&overlay.hunk_key, &snapshot) > 0)
21948 .collect();
21949
21950 // Now collect block IDs to remove and retain overlays
21951 let mut block_ids_to_remove = HashSet::default();
21952 let mut index = 0;
21953 self.diff_review_overlays.retain(|overlay| {
21954 let has_comments = overlays_with_comments[index];
21955 index += 1;
21956 if !has_comments {
21957 block_ids_to_remove.insert(overlay.block_id);
21958 }
21959 has_comments
21960 });
21961
21962 if !block_ids_to_remove.is_empty() {
21963 self.remove_blocks(block_ids_to_remove, None, cx);
21964 cx.notify();
21965 }
21966 }
21967
21968 /// Refreshes the diff review overlay block to update its height and render function.
21969 /// Uses resize_blocks and replace_blocks to avoid visual flicker from remove+insert.
21970 fn refresh_diff_review_overlay_height(
21971 &mut self,
21972 hunk_key: &DiffHunkKey,
21973 _window: &mut Window,
21974 cx: &mut Context<Self>,
21975 ) {
21976 // Extract all needed data from overlay first to avoid borrow conflicts
21977 let snapshot = self.buffer.read(cx).snapshot(cx);
21978 let (comments_expanded, block_id, prompt_editor) = {
21979 let Some(overlay) = self
21980 .diff_review_overlays
21981 .iter()
21982 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
21983 else {
21984 return;
21985 };
21986
21987 (
21988 overlay.comments_expanded,
21989 overlay.block_id,
21990 overlay.prompt_editor.clone(),
21991 )
21992 };
21993
21994 // Calculate new height
21995 let snapshot = self.buffer.read(cx).snapshot(cx);
21996 let new_height = self.calculate_overlay_height(hunk_key, comments_expanded, &snapshot);
21997
21998 // Update the block height using resize_blocks (avoids flicker)
21999 let mut heights = HashMap::default();
22000 heights.insert(block_id, new_height);
22001 self.resize_blocks(heights, None, cx);
22002
22003 // Update the render function using replace_blocks (avoids flicker)
22004 let hunk_key_for_render = hunk_key.clone();
22005 let editor_handle = cx.entity().downgrade();
22006 let render: Arc<dyn Fn(&mut BlockContext) -> AnyElement + Send + Sync> =
22007 Arc::new(move |cx| {
22008 Self::render_diff_review_overlay(
22009 &prompt_editor,
22010 &hunk_key_for_render,
22011 &editor_handle,
22012 cx,
22013 )
22014 });
22015
22016 let mut renderers = HashMap::default();
22017 renderers.insert(block_id, render);
22018 self.replace_blocks(renderers, None, cx);
22019 }
22020
22021 /// Action handler for SubmitDiffReviewComment.
22022 pub fn submit_diff_review_comment_action(
22023 &mut self,
22024 _: &SubmitDiffReviewComment,
22025 window: &mut Window,
22026 cx: &mut Context<Self>,
22027 ) {
22028 self.submit_diff_review_comment(window, cx);
22029 }
22030
22031 /// Stores the diff review comment locally.
22032 /// Comments are stored per-hunk and can later be batch-submitted to the Agent panel.
22033 pub fn submit_diff_review_comment(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22034 // Find the overlay that currently has focus
22035 let overlay_index = self
22036 .diff_review_overlays
22037 .iter()
22038 .position(|overlay| overlay.prompt_editor.focus_handle(cx).is_focused(window));
22039 let Some(overlay_index) = overlay_index else {
22040 return;
22041 };
22042 let overlay = &self.diff_review_overlays[overlay_index];
22043
22044 let comment_text = overlay.prompt_editor.read(cx).text(cx).trim().to_string();
22045 if comment_text.is_empty() {
22046 return;
22047 }
22048
22049 let anchor_range = overlay.anchor_range.clone();
22050 let hunk_key = overlay.hunk_key.clone();
22051
22052 self.add_review_comment(hunk_key.clone(), comment_text, anchor_range, cx);
22053
22054 // Clear the prompt editor but keep the overlay open
22055 if let Some(overlay) = self.diff_review_overlays.get(overlay_index) {
22056 overlay.prompt_editor.update(cx, |editor, cx| {
22057 editor.clear(window, cx);
22058 });
22059 }
22060
22061 // Refresh the overlay to update the block height for the new comment
22062 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22063
22064 cx.notify();
22065 }
22066
22067 /// Returns the prompt editor for the diff review overlay, if one is active.
22068 /// This is primarily used for testing.
22069 pub fn diff_review_prompt_editor(&self) -> Option<&Entity<Editor>> {
22070 self.diff_review_overlays
22071 .first()
22072 .map(|overlay| &overlay.prompt_editor)
22073 }
22074
22075 /// Returns the line range for the first diff review overlay, if one is active.
22076 /// Returns (start_row, end_row) as physical line numbers in the underlying file.
22077 pub fn diff_review_line_range(&self, cx: &App) -> Option<(u32, u32)> {
22078 let overlay = self.diff_review_overlays.first()?;
22079 let snapshot = self.buffer.read(cx).snapshot(cx);
22080 let start_point = overlay.anchor_range.start.to_point(&snapshot);
22081 let end_point = overlay.anchor_range.end.to_point(&snapshot);
22082 let start_row = snapshot
22083 .point_to_buffer_point(start_point)
22084 .map(|(_, p, _)| p.row)
22085 .unwrap_or(start_point.row);
22086 let end_row = snapshot
22087 .point_to_buffer_point(end_point)
22088 .map(|(_, p, _)| p.row)
22089 .unwrap_or(end_point.row);
22090 Some((start_row, end_row))
22091 }
22092
22093 /// Sets whether the comments section is expanded in the diff review overlay.
22094 /// This is primarily used for testing.
22095 pub fn set_diff_review_comments_expanded(&mut self, expanded: bool, cx: &mut Context<Self>) {
22096 for overlay in &mut self.diff_review_overlays {
22097 overlay.comments_expanded = expanded;
22098 }
22099 cx.notify();
22100 }
22101
22102 /// Compares two DiffHunkKeys for equality by resolving their anchors.
22103 fn hunk_keys_match(a: &DiffHunkKey, b: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> bool {
22104 a.file_path == b.file_path
22105 && a.hunk_start_anchor.to_point(snapshot) == b.hunk_start_anchor.to_point(snapshot)
22106 }
22107
22108 /// Returns comments for a specific hunk, ordered by creation time.
22109 pub fn comments_for_hunk<'a>(
22110 &'a self,
22111 key: &DiffHunkKey,
22112 snapshot: &MultiBufferSnapshot,
22113 ) -> &'a [StoredReviewComment] {
22114 let key_point = key.hunk_start_anchor.to_point(snapshot);
22115 self.stored_review_comments
22116 .iter()
22117 .find(|(k, _)| {
22118 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22119 })
22120 .map(|(_, comments)| comments.as_slice())
22121 .unwrap_or(&[])
22122 }
22123
22124 /// Returns the total count of stored review comments across all hunks.
22125 pub fn total_review_comment_count(&self) -> usize {
22126 self.stored_review_comments
22127 .iter()
22128 .map(|(_, v)| v.len())
22129 .sum()
22130 }
22131
22132 /// Returns the count of comments for a specific hunk.
22133 pub fn hunk_comment_count(&self, key: &DiffHunkKey, snapshot: &MultiBufferSnapshot) -> usize {
22134 let key_point = key.hunk_start_anchor.to_point(snapshot);
22135 self.stored_review_comments
22136 .iter()
22137 .find(|(k, _)| {
22138 k.file_path == key.file_path && k.hunk_start_anchor.to_point(snapshot) == key_point
22139 })
22140 .map(|(_, v)| v.len())
22141 .unwrap_or(0)
22142 }
22143
22144 /// Adds a new review comment to a specific hunk.
22145 pub fn add_review_comment(
22146 &mut self,
22147 hunk_key: DiffHunkKey,
22148 comment: String,
22149 anchor_range: Range<Anchor>,
22150 cx: &mut Context<Self>,
22151 ) -> usize {
22152 let id = self.next_review_comment_id;
22153 self.next_review_comment_id += 1;
22154
22155 let stored_comment = StoredReviewComment::new(id, comment, anchor_range);
22156
22157 let snapshot = self.buffer.read(cx).snapshot(cx);
22158 let key_point = hunk_key.hunk_start_anchor.to_point(&snapshot);
22159
22160 // Find existing entry for this hunk or add a new one
22161 if let Some((_, comments)) = self.stored_review_comments.iter_mut().find(|(k, _)| {
22162 k.file_path == hunk_key.file_path
22163 && k.hunk_start_anchor.to_point(&snapshot) == key_point
22164 }) {
22165 comments.push(stored_comment);
22166 } else {
22167 self.stored_review_comments
22168 .push((hunk_key, vec![stored_comment]));
22169 }
22170
22171 cx.emit(EditorEvent::ReviewCommentsChanged {
22172 total_count: self.total_review_comment_count(),
22173 });
22174 cx.notify();
22175 id
22176 }
22177
22178 /// Removes a review comment by ID from any hunk.
22179 pub fn remove_review_comment(&mut self, id: usize, cx: &mut Context<Self>) -> bool {
22180 for (_, comments) in self.stored_review_comments.iter_mut() {
22181 if let Some(index) = comments.iter().position(|c| c.id == id) {
22182 comments.remove(index);
22183 cx.emit(EditorEvent::ReviewCommentsChanged {
22184 total_count: self.total_review_comment_count(),
22185 });
22186 cx.notify();
22187 return true;
22188 }
22189 }
22190 false
22191 }
22192
22193 /// Updates a review comment's text by ID.
22194 pub fn update_review_comment(
22195 &mut self,
22196 id: usize,
22197 new_comment: String,
22198 cx: &mut Context<Self>,
22199 ) -> bool {
22200 for (_, comments) in self.stored_review_comments.iter_mut() {
22201 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22202 comment.comment = new_comment;
22203 comment.is_editing = false;
22204 cx.emit(EditorEvent::ReviewCommentsChanged {
22205 total_count: self.total_review_comment_count(),
22206 });
22207 cx.notify();
22208 return true;
22209 }
22210 }
22211 false
22212 }
22213
22214 /// Sets a comment's editing state.
22215 pub fn set_comment_editing(&mut self, id: usize, is_editing: bool, cx: &mut Context<Self>) {
22216 for (_, comments) in self.stored_review_comments.iter_mut() {
22217 if let Some(comment) = comments.iter_mut().find(|c| c.id == id) {
22218 comment.is_editing = is_editing;
22219 cx.notify();
22220 return;
22221 }
22222 }
22223 }
22224
22225 /// Takes all stored comments from all hunks, clearing the storage.
22226 /// Returns a Vec of (hunk_key, comments) pairs.
22227 pub fn take_all_review_comments(
22228 &mut self,
22229 cx: &mut Context<Self>,
22230 ) -> Vec<(DiffHunkKey, Vec<StoredReviewComment>)> {
22231 // Dismiss all overlays when taking comments (e.g., when sending to agent)
22232 self.dismiss_all_diff_review_overlays(cx);
22233 let comments = std::mem::take(&mut self.stored_review_comments);
22234 // Reset the ID counter since all comments have been taken
22235 self.next_review_comment_id = 0;
22236 cx.emit(EditorEvent::ReviewCommentsChanged { total_count: 0 });
22237 cx.notify();
22238 comments
22239 }
22240
22241 /// Removes review comments whose anchors are no longer valid or whose
22242 /// associated diff hunks no longer exist.
22243 ///
22244 /// This should be called when the buffer changes to prevent orphaned comments
22245 /// from accumulating.
22246 pub fn cleanup_orphaned_review_comments(&mut self, cx: &mut Context<Self>) {
22247 let snapshot = self.buffer.read(cx).snapshot(cx);
22248 let original_count = self.total_review_comment_count();
22249
22250 // Remove comments with invalid hunk anchors
22251 self.stored_review_comments
22252 .retain(|(hunk_key, _)| hunk_key.hunk_start_anchor.is_valid(&snapshot));
22253
22254 // Also clean up individual comments with invalid anchor ranges
22255 for (_, comments) in &mut self.stored_review_comments {
22256 comments.retain(|comment| {
22257 comment.range.start.is_valid(&snapshot) && comment.range.end.is_valid(&snapshot)
22258 });
22259 }
22260
22261 // Remove empty hunk entries
22262 self.stored_review_comments
22263 .retain(|(_, comments)| !comments.is_empty());
22264
22265 let new_count = self.total_review_comment_count();
22266 if new_count != original_count {
22267 cx.emit(EditorEvent::ReviewCommentsChanged {
22268 total_count: new_count,
22269 });
22270 cx.notify();
22271 }
22272 }
22273
22274 /// Toggles the expanded state of the comments section in the overlay.
22275 pub fn toggle_review_comments_expanded(
22276 &mut self,
22277 _: &ToggleReviewCommentsExpanded,
22278 window: &mut Window,
22279 cx: &mut Context<Self>,
22280 ) {
22281 // Find the overlay that currently has focus, or use the first one
22282 let overlay_info = self.diff_review_overlays.iter_mut().find_map(|overlay| {
22283 if overlay.prompt_editor.focus_handle(cx).is_focused(window) {
22284 overlay.comments_expanded = !overlay.comments_expanded;
22285 Some(overlay.hunk_key.clone())
22286 } else {
22287 None
22288 }
22289 });
22290
22291 // If no focused overlay found, toggle the first one
22292 let hunk_key = overlay_info.or_else(|| {
22293 self.diff_review_overlays.first_mut().map(|overlay| {
22294 overlay.comments_expanded = !overlay.comments_expanded;
22295 overlay.hunk_key.clone()
22296 })
22297 });
22298
22299 if let Some(hunk_key) = hunk_key {
22300 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22301 cx.notify();
22302 }
22303 }
22304
22305 /// Handles the EditReviewComment action - sets a comment into editing mode.
22306 pub fn edit_review_comment(
22307 &mut self,
22308 action: &EditReviewComment,
22309 window: &mut Window,
22310 cx: &mut Context<Self>,
22311 ) {
22312 let comment_id = action.id;
22313
22314 // Set the comment to editing mode
22315 self.set_comment_editing(comment_id, true, cx);
22316
22317 // Find the overlay that contains this comment and create an inline editor if needed
22318 // First, find which hunk this comment belongs to
22319 let hunk_key = self
22320 .stored_review_comments
22321 .iter()
22322 .find_map(|(key, comments)| {
22323 if comments.iter().any(|c| c.id == comment_id) {
22324 Some(key.clone())
22325 } else {
22326 None
22327 }
22328 });
22329
22330 let snapshot = self.buffer.read(cx).snapshot(cx);
22331 if let Some(hunk_key) = hunk_key {
22332 if let Some(overlay) = self
22333 .diff_review_overlays
22334 .iter_mut()
22335 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22336 {
22337 if let std::collections::hash_map::Entry::Vacant(entry) =
22338 overlay.inline_edit_editors.entry(comment_id)
22339 {
22340 // Find the comment text
22341 let comment_text = self
22342 .stored_review_comments
22343 .iter()
22344 .flat_map(|(_, comments)| comments)
22345 .find(|c| c.id == comment_id)
22346 .map(|c| c.comment.clone())
22347 .unwrap_or_default();
22348
22349 // Create inline editor
22350 let parent_editor = cx.entity().downgrade();
22351 let inline_editor = cx.new(|cx| {
22352 let mut editor = Editor::single_line(window, cx);
22353 editor.set_text(&*comment_text, window, cx);
22354 // Select all text for easy replacement
22355 editor.select_all(&crate::actions::SelectAll, window, cx);
22356 editor
22357 });
22358
22359 // Register the Newline action to confirm the edit
22360 let subscription = inline_editor.update(cx, |inline_editor, _cx| {
22361 inline_editor.register_action({
22362 let parent_editor = parent_editor.clone();
22363 move |_: &crate::actions::Newline, window, cx| {
22364 if let Some(editor) = parent_editor.upgrade() {
22365 editor.update(cx, |editor, cx| {
22366 editor.confirm_edit_review_comment(comment_id, window, cx);
22367 });
22368 }
22369 }
22370 })
22371 });
22372
22373 // Store the subscription to keep the action handler alive
22374 overlay
22375 .inline_edit_subscriptions
22376 .insert(comment_id, subscription);
22377
22378 // Focus the inline editor
22379 let focus_handle = inline_editor.focus_handle(cx);
22380 window.focus(&focus_handle, cx);
22381
22382 entry.insert(inline_editor);
22383 }
22384 }
22385 }
22386
22387 cx.notify();
22388 }
22389
22390 /// Confirms an inline edit of a review comment.
22391 pub fn confirm_edit_review_comment(
22392 &mut self,
22393 comment_id: usize,
22394 _window: &mut Window,
22395 cx: &mut Context<Self>,
22396 ) {
22397 // Get the new text from the inline editor
22398 // Find the overlay containing this comment's inline editor
22399 let snapshot = self.buffer.read(cx).snapshot(cx);
22400 let hunk_key = self
22401 .stored_review_comments
22402 .iter()
22403 .find_map(|(key, comments)| {
22404 if comments.iter().any(|c| c.id == comment_id) {
22405 Some(key.clone())
22406 } else {
22407 None
22408 }
22409 });
22410
22411 let new_text = hunk_key
22412 .as_ref()
22413 .and_then(|hunk_key| {
22414 self.diff_review_overlays
22415 .iter()
22416 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot))
22417 })
22418 .as_ref()
22419 .and_then(|overlay| overlay.inline_edit_editors.get(&comment_id))
22420 .map(|editor| editor.read(cx).text(cx).trim().to_string());
22421
22422 if let Some(new_text) = new_text {
22423 if !new_text.is_empty() {
22424 self.update_review_comment(comment_id, new_text, cx);
22425 }
22426 }
22427
22428 // Remove the inline editor and its subscription
22429 if let Some(hunk_key) = hunk_key {
22430 if let Some(overlay) = self
22431 .diff_review_overlays
22432 .iter_mut()
22433 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22434 {
22435 overlay.inline_edit_editors.remove(&comment_id);
22436 overlay.inline_edit_subscriptions.remove(&comment_id);
22437 }
22438 }
22439
22440 // Clear editing state
22441 self.set_comment_editing(comment_id, false, cx);
22442 }
22443
22444 /// Cancels an inline edit of a review comment.
22445 pub fn cancel_edit_review_comment(
22446 &mut self,
22447 comment_id: usize,
22448 _window: &mut Window,
22449 cx: &mut Context<Self>,
22450 ) {
22451 // Find which hunk this comment belongs to
22452 let hunk_key = self
22453 .stored_review_comments
22454 .iter()
22455 .find_map(|(key, comments)| {
22456 if comments.iter().any(|c| c.id == comment_id) {
22457 Some(key.clone())
22458 } else {
22459 None
22460 }
22461 });
22462
22463 // Remove the inline editor and its subscription
22464 if let Some(hunk_key) = hunk_key {
22465 let snapshot = self.buffer.read(cx).snapshot(cx);
22466 if let Some(overlay) = self
22467 .diff_review_overlays
22468 .iter_mut()
22469 .find(|overlay| Self::hunk_keys_match(&overlay.hunk_key, &hunk_key, &snapshot))
22470 {
22471 overlay.inline_edit_editors.remove(&comment_id);
22472 overlay.inline_edit_subscriptions.remove(&comment_id);
22473 }
22474 }
22475
22476 // Clear editing state
22477 self.set_comment_editing(comment_id, false, cx);
22478 }
22479
22480 /// Action handler for ConfirmEditReviewComment.
22481 pub fn confirm_edit_review_comment_action(
22482 &mut self,
22483 action: &ConfirmEditReviewComment,
22484 window: &mut Window,
22485 cx: &mut Context<Self>,
22486 ) {
22487 self.confirm_edit_review_comment(action.id, window, cx);
22488 }
22489
22490 /// Action handler for CancelEditReviewComment.
22491 pub fn cancel_edit_review_comment_action(
22492 &mut self,
22493 action: &CancelEditReviewComment,
22494 window: &mut Window,
22495 cx: &mut Context<Self>,
22496 ) {
22497 self.cancel_edit_review_comment(action.id, window, cx);
22498 }
22499
22500 /// Handles the DeleteReviewComment action - removes a comment.
22501 pub fn delete_review_comment(
22502 &mut self,
22503 action: &DeleteReviewComment,
22504 window: &mut Window,
22505 cx: &mut Context<Self>,
22506 ) {
22507 // Get the hunk key before removing the comment
22508 // Find the hunk key from the comment itself
22509 let comment_id = action.id;
22510 let hunk_key = self
22511 .stored_review_comments
22512 .iter()
22513 .find_map(|(key, comments)| {
22514 if comments.iter().any(|c| c.id == comment_id) {
22515 Some(key.clone())
22516 } else {
22517 None
22518 }
22519 });
22520
22521 // Also get it from the overlay for refresh purposes
22522 let overlay_hunk_key = self
22523 .diff_review_overlays
22524 .first()
22525 .map(|o| o.hunk_key.clone());
22526
22527 self.remove_review_comment(action.id, cx);
22528
22529 // Refresh the overlay height after removing a comment
22530 if let Some(hunk_key) = hunk_key.or(overlay_hunk_key) {
22531 self.refresh_diff_review_overlay_height(&hunk_key, window, cx);
22532 }
22533 }
22534
22535 fn render_diff_review_overlay(
22536 prompt_editor: &Entity<Editor>,
22537 hunk_key: &DiffHunkKey,
22538 editor_handle: &WeakEntity<Editor>,
22539 cx: &mut BlockContext,
22540 ) -> AnyElement {
22541 fn format_line_ranges(ranges: &[(u32, u32)]) -> Option<String> {
22542 if ranges.is_empty() {
22543 return None;
22544 }
22545 let formatted: Vec<String> = ranges
22546 .iter()
22547 .map(|(start, end)| {
22548 let start_line = start + 1;
22549 let end_line = end + 1;
22550 if start_line == end_line {
22551 format!("Line {start_line}")
22552 } else {
22553 format!("Lines {start_line}-{end_line}")
22554 }
22555 })
22556 .collect();
22557 // Don't show label for single line in single excerpt
22558 if ranges.len() == 1 && ranges[0].0 == ranges[0].1 {
22559 return None;
22560 }
22561 Some(formatted.join(" ⋯ "))
22562 }
22563
22564 let theme = cx.theme();
22565 let colors = theme.colors();
22566
22567 let (comments, comments_expanded, inline_editors, user_avatar_uri, line_ranges) =
22568 editor_handle
22569 .upgrade()
22570 .map(|editor| {
22571 let editor = editor.read(cx);
22572 let snapshot = editor.buffer().read(cx).snapshot(cx);
22573 let comments = editor.comments_for_hunk(hunk_key, &snapshot).to_vec();
22574 let (expanded, editors, avatar_uri, line_ranges) = editor
22575 .diff_review_overlays
22576 .iter()
22577 .find(|overlay| {
22578 Editor::hunk_keys_match(&overlay.hunk_key, hunk_key, &snapshot)
22579 })
22580 .map(|o| {
22581 let start_point = o.anchor_range.start.to_point(&snapshot);
22582 let end_point = o.anchor_range.end.to_point(&snapshot);
22583 // Get line ranges per excerpt to detect discontinuities
22584 let buffer_ranges =
22585 snapshot.range_to_buffer_ranges(start_point..end_point);
22586 let ranges: Vec<(u32, u32)> = buffer_ranges
22587 .iter()
22588 .map(|(buffer, range, _)| {
22589 let start = buffer.offset_to_point(range.start.0).row;
22590 let end = buffer.offset_to_point(range.end.0).row;
22591 (start, end)
22592 })
22593 .collect();
22594 (
22595 o.comments_expanded,
22596 o.inline_edit_editors.clone(),
22597 o.user_avatar_uri.clone(),
22598 if ranges.is_empty() {
22599 None
22600 } else {
22601 Some(ranges)
22602 },
22603 )
22604 })
22605 .unwrap_or((true, HashMap::default(), None, None));
22606 (comments, expanded, editors, avatar_uri, line_ranges)
22607 })
22608 .unwrap_or((Vec::new(), true, HashMap::default(), None, None));
22609
22610 let comment_count = comments.len();
22611 let avatar_size = px(20.);
22612 let action_icon_size = IconSize::XSmall;
22613
22614 v_flex()
22615 .w_full()
22616 .bg(colors.editor_background)
22617 .border_b_1()
22618 .border_color(colors.border)
22619 .px_2()
22620 .pb_2()
22621 .gap_2()
22622 // Line range indicator (only shown for multi-line selections or multiple excerpts)
22623 .when_some(line_ranges, |el, ranges| {
22624 let label = format_line_ranges(&ranges);
22625 if let Some(label) = label {
22626 el.child(
22627 h_flex()
22628 .w_full()
22629 .px_2()
22630 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)),
22631 )
22632 } else {
22633 el
22634 }
22635 })
22636 // Top row: editable input with user's avatar
22637 .child(
22638 h_flex()
22639 .w_full()
22640 .items_center()
22641 .gap_2()
22642 .px_2()
22643 .py_1p5()
22644 .rounded_md()
22645 .bg(colors.surface_background)
22646 .child(
22647 div()
22648 .size(avatar_size)
22649 .flex_shrink_0()
22650 .rounded_full()
22651 .overflow_hidden()
22652 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22653 Avatar::new(avatar_uri.clone())
22654 .size(avatar_size)
22655 .into_any_element()
22656 } else {
22657 Icon::new(IconName::Person)
22658 .size(IconSize::Small)
22659 .color(ui::Color::Muted)
22660 .into_any_element()
22661 }),
22662 )
22663 .child(
22664 div()
22665 .flex_1()
22666 .border_1()
22667 .border_color(colors.border)
22668 .rounded_md()
22669 .bg(colors.editor_background)
22670 .px_2()
22671 .py_1()
22672 .child(prompt_editor.clone()),
22673 )
22674 .child(
22675 h_flex()
22676 .flex_shrink_0()
22677 .gap_1()
22678 .child(
22679 IconButton::new("diff-review-close", IconName::Close)
22680 .icon_color(ui::Color::Muted)
22681 .icon_size(action_icon_size)
22682 .tooltip(Tooltip::text("Close"))
22683 .on_click(|_, window, cx| {
22684 window
22685 .dispatch_action(Box::new(crate::actions::Cancel), cx);
22686 }),
22687 )
22688 .child(
22689 IconButton::new("diff-review-add", IconName::Return)
22690 .icon_color(ui::Color::Muted)
22691 .icon_size(action_icon_size)
22692 .tooltip(Tooltip::text("Add comment"))
22693 .on_click(|_, window, cx| {
22694 window.dispatch_action(
22695 Box::new(crate::actions::SubmitDiffReviewComment),
22696 cx,
22697 );
22698 }),
22699 ),
22700 ),
22701 )
22702 // Expandable comments section (only shown when there are comments)
22703 .when(comment_count > 0, |el| {
22704 el.child(Self::render_comments_section(
22705 comments,
22706 comments_expanded,
22707 inline_editors,
22708 user_avatar_uri,
22709 avatar_size,
22710 action_icon_size,
22711 colors,
22712 ))
22713 })
22714 .into_any_element()
22715 }
22716
22717 fn render_comments_section(
22718 comments: Vec<StoredReviewComment>,
22719 expanded: bool,
22720 inline_editors: HashMap<usize, Entity<Editor>>,
22721 user_avatar_uri: Option<SharedUri>,
22722 avatar_size: Pixels,
22723 action_icon_size: IconSize,
22724 colors: &theme::ThemeColors,
22725 ) -> impl IntoElement {
22726 let comment_count = comments.len();
22727
22728 v_flex()
22729 .w_full()
22730 .gap_1()
22731 // Header with expand/collapse toggle
22732 .child(
22733 h_flex()
22734 .id("review-comments-header")
22735 .w_full()
22736 .items_center()
22737 .gap_1()
22738 .px_2()
22739 .py_1()
22740 .cursor_pointer()
22741 .rounded_md()
22742 .hover(|style| style.bg(colors.ghost_element_hover))
22743 .on_click(|_, window: &mut Window, cx| {
22744 window.dispatch_action(
22745 Box::new(crate::actions::ToggleReviewCommentsExpanded),
22746 cx,
22747 );
22748 })
22749 .child(
22750 Icon::new(if expanded {
22751 IconName::ChevronDown
22752 } else {
22753 IconName::ChevronRight
22754 })
22755 .size(IconSize::Small)
22756 .color(ui::Color::Muted),
22757 )
22758 .child(
22759 Label::new(format!(
22760 "{} Comment{}",
22761 comment_count,
22762 if comment_count == 1 { "" } else { "s" }
22763 ))
22764 .size(LabelSize::Small)
22765 .color(Color::Muted),
22766 ),
22767 )
22768 // Comments list (when expanded)
22769 .when(expanded, |el| {
22770 el.children(comments.into_iter().map(|comment| {
22771 let inline_editor = inline_editors.get(&comment.id).cloned();
22772 Self::render_comment_row(
22773 comment,
22774 inline_editor,
22775 user_avatar_uri.clone(),
22776 avatar_size,
22777 action_icon_size,
22778 colors,
22779 )
22780 }))
22781 })
22782 }
22783
22784 fn render_comment_row(
22785 comment: StoredReviewComment,
22786 inline_editor: Option<Entity<Editor>>,
22787 user_avatar_uri: Option<SharedUri>,
22788 avatar_size: Pixels,
22789 action_icon_size: IconSize,
22790 colors: &theme::ThemeColors,
22791 ) -> impl IntoElement {
22792 let comment_id = comment.id;
22793 let is_editing = inline_editor.is_some();
22794
22795 h_flex()
22796 .w_full()
22797 .items_center()
22798 .gap_2()
22799 .px_2()
22800 .py_1p5()
22801 .rounded_md()
22802 .bg(colors.surface_background)
22803 .child(
22804 div()
22805 .size(avatar_size)
22806 .flex_shrink_0()
22807 .rounded_full()
22808 .overflow_hidden()
22809 .child(if let Some(ref avatar_uri) = user_avatar_uri {
22810 Avatar::new(avatar_uri.clone())
22811 .size(avatar_size)
22812 .into_any_element()
22813 } else {
22814 Icon::new(IconName::Person)
22815 .size(IconSize::Small)
22816 .color(ui::Color::Muted)
22817 .into_any_element()
22818 }),
22819 )
22820 .child(if let Some(editor) = inline_editor {
22821 // Inline edit mode: show an editable text field
22822 div()
22823 .flex_1()
22824 .border_1()
22825 .border_color(colors.border)
22826 .rounded_md()
22827 .bg(colors.editor_background)
22828 .px_2()
22829 .py_1()
22830 .child(editor)
22831 .into_any_element()
22832 } else {
22833 // Display mode: show the comment text
22834 div()
22835 .flex_1()
22836 .text_sm()
22837 .text_color(colors.text)
22838 .child(comment.comment)
22839 .into_any_element()
22840 })
22841 .child(if is_editing {
22842 // Editing mode: show close and confirm buttons
22843 h_flex()
22844 .gap_1()
22845 .child(
22846 IconButton::new(
22847 format!("diff-review-cancel-edit-{comment_id}"),
22848 IconName::Close,
22849 )
22850 .icon_color(ui::Color::Muted)
22851 .icon_size(action_icon_size)
22852 .tooltip(Tooltip::text("Cancel"))
22853 .on_click(move |_, window, cx| {
22854 window.dispatch_action(
22855 Box::new(crate::actions::CancelEditReviewComment {
22856 id: comment_id,
22857 }),
22858 cx,
22859 );
22860 }),
22861 )
22862 .child(
22863 IconButton::new(
22864 format!("diff-review-confirm-edit-{comment_id}"),
22865 IconName::Return,
22866 )
22867 .icon_color(ui::Color::Muted)
22868 .icon_size(action_icon_size)
22869 .tooltip(Tooltip::text("Confirm"))
22870 .on_click(move |_, window, cx| {
22871 window.dispatch_action(
22872 Box::new(crate::actions::ConfirmEditReviewComment {
22873 id: comment_id,
22874 }),
22875 cx,
22876 );
22877 }),
22878 )
22879 .into_any_element()
22880 } else {
22881 // Display mode: no action buttons for now (edit/delete not yet implemented)
22882 gpui::Empty.into_any_element()
22883 })
22884 }
22885
22886 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
22887 if self.display_map.read(cx).masked != masked {
22888 self.display_map.update(cx, |map, _| map.masked = masked);
22889 }
22890 cx.notify()
22891 }
22892
22893 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
22894 self.show_wrap_guides = Some(show_wrap_guides);
22895 cx.notify();
22896 }
22897
22898 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
22899 self.show_indent_guides = Some(show_indent_guides);
22900 cx.notify();
22901 }
22902
22903 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
22904 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
22905 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
22906 && let Some(dir) = file.abs_path(cx).parent()
22907 {
22908 return Some(dir.to_owned());
22909 }
22910 }
22911
22912 None
22913 }
22914
22915 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
22916 self.active_excerpt(cx)?
22917 .1
22918 .read(cx)
22919 .file()
22920 .and_then(|f| f.as_local())
22921 }
22922
22923 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
22924 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22925 let buffer = buffer.read(cx);
22926 if let Some(project_path) = buffer.project_path(cx) {
22927 let project = self.project()?.read(cx);
22928 project.absolute_path(&project_path, cx)
22929 } else {
22930 buffer
22931 .file()
22932 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
22933 }
22934 })
22935 }
22936
22937 pub fn reveal_in_finder(
22938 &mut self,
22939 _: &RevealInFileManager,
22940 _window: &mut Window,
22941 cx: &mut Context<Self>,
22942 ) {
22943 if let Some(path) = self.target_file_abs_path(cx) {
22944 if let Some(project) = self.project() {
22945 project.update(cx, |project, cx| project.reveal_path(&path, cx));
22946 } else {
22947 cx.reveal_path(&path);
22948 }
22949 }
22950 }
22951
22952 pub fn copy_path(
22953 &mut self,
22954 _: &zed_actions::workspace::CopyPath,
22955 _window: &mut Window,
22956 cx: &mut Context<Self>,
22957 ) {
22958 if let Some(path) = self.target_file_abs_path(cx)
22959 && let Some(path) = path.to_str()
22960 {
22961 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22962 } else {
22963 cx.propagate();
22964 }
22965 }
22966
22967 pub fn copy_relative_path(
22968 &mut self,
22969 _: &zed_actions::workspace::CopyRelativePath,
22970 _window: &mut Window,
22971 cx: &mut Context<Self>,
22972 ) {
22973 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
22974 let project = self.project()?.read(cx);
22975 let path = buffer.read(cx).file()?.path();
22976 let path = path.display(project.path_style(cx));
22977 Some(path)
22978 }) {
22979 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
22980 } else {
22981 cx.propagate();
22982 }
22983 }
22984
22985 /// Returns the project path for the editor's buffer, if any buffer is
22986 /// opened in the editor.
22987 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
22988 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
22989 buffer.read(cx).project_path(cx)
22990 } else {
22991 None
22992 }
22993 }
22994
22995 // Returns true if the editor handled a go-to-line request
22996 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
22997 maybe!({
22998 let breakpoint_store = self.breakpoint_store.as_ref()?;
22999
23000 let (active_stack_frame, debug_line_pane_id) = {
23001 let store = breakpoint_store.read(cx);
23002 let active_stack_frame = store.active_position().cloned();
23003 let debug_line_pane_id = store.active_debug_line_pane_id();
23004 (active_stack_frame, debug_line_pane_id)
23005 };
23006
23007 let Some(active_stack_frame) = active_stack_frame else {
23008 self.clear_row_highlights::<ActiveDebugLine>();
23009 return None;
23010 };
23011
23012 if let Some(debug_line_pane_id) = debug_line_pane_id {
23013 if let Some(workspace) = self
23014 .workspace
23015 .as_ref()
23016 .and_then(|(workspace, _)| workspace.upgrade())
23017 {
23018 let editor_pane_id = workspace
23019 .read(cx)
23020 .pane_for_item_id(cx.entity_id())
23021 .map(|pane| pane.entity_id());
23022
23023 if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
23024 self.clear_row_highlights::<ActiveDebugLine>();
23025 return None;
23026 }
23027 }
23028 }
23029
23030 let position = active_stack_frame.position;
23031 let buffer_id = position.buffer_id?;
23032 let snapshot = self
23033 .project
23034 .as_ref()?
23035 .read(cx)
23036 .buffer_for_id(buffer_id, cx)?
23037 .read(cx)
23038 .snapshot();
23039
23040 let mut handled = false;
23041 for (id, _, ExcerptRange { context, .. }) in
23042 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
23043 {
23044 if context.start.cmp(&position, &snapshot).is_ge()
23045 || context.end.cmp(&position, &snapshot).is_lt()
23046 {
23047 continue;
23048 }
23049 let snapshot = self.buffer.read(cx).snapshot(cx);
23050 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
23051
23052 handled = true;
23053 self.clear_row_highlights::<ActiveDebugLine>();
23054
23055 self.go_to_line::<ActiveDebugLine>(
23056 multibuffer_anchor,
23057 Some(cx.theme().colors().editor_debugger_active_line_background),
23058 window,
23059 cx,
23060 );
23061
23062 cx.notify();
23063 }
23064
23065 handled.then_some(())
23066 })
23067 .is_some()
23068 }
23069
23070 pub fn copy_file_name_without_extension(
23071 &mut self,
23072 _: &CopyFileNameWithoutExtension,
23073 _: &mut Window,
23074 cx: &mut Context<Self>,
23075 ) {
23076 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23077 let file = buffer.read(cx).file()?;
23078 file.path().file_stem()
23079 }) {
23080 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
23081 }
23082 }
23083
23084 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
23085 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23086 let file = buffer.read(cx).file()?;
23087 Some(file.file_name(cx))
23088 }) {
23089 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
23090 }
23091 }
23092
23093 pub fn toggle_git_blame(
23094 &mut self,
23095 _: &::git::Blame,
23096 window: &mut Window,
23097 cx: &mut Context<Self>,
23098 ) {
23099 self.show_git_blame_gutter = !self.show_git_blame_gutter;
23100
23101 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
23102 self.start_git_blame(true, window, cx);
23103 }
23104
23105 cx.notify();
23106 }
23107
23108 pub fn toggle_git_blame_inline(
23109 &mut self,
23110 _: &ToggleGitBlameInline,
23111 window: &mut Window,
23112 cx: &mut Context<Self>,
23113 ) {
23114 self.toggle_git_blame_inline_internal(true, window, cx);
23115 cx.notify();
23116 }
23117
23118 pub fn open_git_blame_commit(
23119 &mut self,
23120 _: &OpenGitBlameCommit,
23121 window: &mut Window,
23122 cx: &mut Context<Self>,
23123 ) {
23124 self.open_git_blame_commit_internal(window, cx);
23125 }
23126
23127 fn open_git_blame_commit_internal(
23128 &mut self,
23129 window: &mut Window,
23130 cx: &mut Context<Self>,
23131 ) -> Option<()> {
23132 let blame = self.blame.as_ref()?;
23133 let snapshot = self.snapshot(window, cx);
23134 let cursor = self
23135 .selections
23136 .newest::<Point>(&snapshot.display_snapshot)
23137 .head();
23138 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
23139 let (_, blame_entry) = blame
23140 .update(cx, |blame, cx| {
23141 blame
23142 .blame_for_rows(
23143 &[RowInfo {
23144 buffer_id: Some(buffer.remote_id()),
23145 buffer_row: Some(point.row),
23146 ..Default::default()
23147 }],
23148 cx,
23149 )
23150 .next()
23151 })
23152 .flatten()?;
23153 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23154 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
23155 let workspace = self.workspace()?.downgrade();
23156 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
23157 None
23158 }
23159
23160 pub fn git_blame_inline_enabled(&self) -> bool {
23161 self.git_blame_inline_enabled
23162 }
23163
23164 pub fn toggle_selection_menu(
23165 &mut self,
23166 _: &ToggleSelectionMenu,
23167 _: &mut Window,
23168 cx: &mut Context<Self>,
23169 ) {
23170 self.show_selection_menu = self
23171 .show_selection_menu
23172 .map(|show_selections_menu| !show_selections_menu)
23173 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
23174
23175 cx.notify();
23176 }
23177
23178 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
23179 self.show_selection_menu
23180 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
23181 }
23182
23183 fn start_git_blame(
23184 &mut self,
23185 user_triggered: bool,
23186 window: &mut Window,
23187 cx: &mut Context<Self>,
23188 ) {
23189 if let Some(project) = self.project() {
23190 if let Some(buffer) = self.buffer().read(cx).as_singleton()
23191 && buffer.read(cx).file().is_none()
23192 {
23193 return;
23194 }
23195
23196 let focused = self.focus_handle(cx).contains_focused(window, cx);
23197
23198 let project = project.clone();
23199 let blame = cx
23200 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
23201 self.blame_subscription =
23202 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
23203 self.blame = Some(blame);
23204 }
23205 }
23206
23207 fn toggle_git_blame_inline_internal(
23208 &mut self,
23209 user_triggered: bool,
23210 window: &mut Window,
23211 cx: &mut Context<Self>,
23212 ) {
23213 if self.git_blame_inline_enabled {
23214 self.git_blame_inline_enabled = false;
23215 self.show_git_blame_inline = false;
23216 self.show_git_blame_inline_delay_task.take();
23217 } else {
23218 self.git_blame_inline_enabled = true;
23219 self.start_git_blame_inline(user_triggered, window, cx);
23220 }
23221
23222 cx.notify();
23223 }
23224
23225 fn start_git_blame_inline(
23226 &mut self,
23227 user_triggered: bool,
23228 window: &mut Window,
23229 cx: &mut Context<Self>,
23230 ) {
23231 self.start_git_blame(user_triggered, window, cx);
23232
23233 if ProjectSettings::get_global(cx)
23234 .git
23235 .inline_blame_delay()
23236 .is_some()
23237 {
23238 self.start_inline_blame_timer(window, cx);
23239 } else {
23240 self.show_git_blame_inline = true
23241 }
23242 }
23243
23244 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
23245 self.blame.as_ref()
23246 }
23247
23248 pub fn show_git_blame_gutter(&self) -> bool {
23249 self.show_git_blame_gutter
23250 }
23251
23252 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
23253 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
23254 }
23255
23256 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
23257 self.show_git_blame_inline
23258 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
23259 && !self.newest_selection_head_on_empty_line(cx)
23260 && self.has_blame_entries(cx)
23261 }
23262
23263 fn has_blame_entries(&self, cx: &App) -> bool {
23264 self.blame()
23265 .is_some_and(|blame| blame.read(cx).has_generated_entries())
23266 }
23267
23268 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
23269 let cursor_anchor = self.selections.newest_anchor().head();
23270
23271 let snapshot = self.buffer.read(cx).snapshot(cx);
23272 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
23273
23274 snapshot.line_len(buffer_row) == 0
23275 }
23276
23277 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
23278 let buffer_and_selection = maybe!({
23279 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23280 let selection_range = selection.range();
23281
23282 let multi_buffer = self.buffer().read(cx);
23283 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
23284 let buffer_ranges = multi_buffer_snapshot
23285 .range_to_buffer_ranges(selection_range.start..=selection_range.end);
23286
23287 let (buffer, range, _) = if selection.reversed {
23288 buffer_ranges.first()
23289 } else {
23290 buffer_ranges.last()
23291 }?;
23292
23293 let buffer_range = range.to_point(buffer);
23294
23295 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
23296 return Some((
23297 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23298 buffer_range.start.row..buffer_range.end.row,
23299 ));
23300 };
23301
23302 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
23303 let start =
23304 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.start, buffer);
23305 let end =
23306 buffer_diff_snapshot.buffer_point_to_base_text_point(buffer_range.end, buffer);
23307
23308 Some((
23309 multi_buffer.buffer(buffer.remote_id()).unwrap(),
23310 start.row..end.row,
23311 ))
23312 });
23313
23314 let Some((buffer, selection)) = buffer_and_selection else {
23315 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
23316 };
23317
23318 let Some(project) = self.project() else {
23319 return Task::ready(Err(anyhow!("editor does not have project")));
23320 };
23321
23322 project.update(cx, |project, cx| {
23323 project.get_permalink_to_line(&buffer, selection, cx)
23324 })
23325 }
23326
23327 pub fn copy_permalink_to_line(
23328 &mut self,
23329 _: &CopyPermalinkToLine,
23330 window: &mut Window,
23331 cx: &mut Context<Self>,
23332 ) {
23333 let permalink_task = self.get_permalink_to_line(cx);
23334 let workspace = self.workspace();
23335
23336 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23337 Ok(permalink) => {
23338 cx.update(|_, cx| {
23339 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
23340 })
23341 .ok();
23342 }
23343 Err(err) => {
23344 let message = format!("Failed to copy permalink: {err}");
23345
23346 anyhow::Result::<()>::Err(err).log_err();
23347
23348 if let Some(workspace) = workspace {
23349 workspace
23350 .update_in(cx, |workspace, _, cx| {
23351 struct CopyPermalinkToLine;
23352
23353 workspace.show_toast(
23354 Toast::new(
23355 NotificationId::unique::<CopyPermalinkToLine>(),
23356 message,
23357 ),
23358 cx,
23359 )
23360 })
23361 .ok();
23362 }
23363 }
23364 })
23365 .detach();
23366 }
23367
23368 pub fn copy_file_location(
23369 &mut self,
23370 _: &CopyFileLocation,
23371 _: &mut Window,
23372 cx: &mut Context<Self>,
23373 ) {
23374 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
23375
23376 let start_line = selection.start.row + 1;
23377 let end_line = selection.end.row + 1;
23378
23379 let end_line = if selection.end.column == 0 && end_line > start_line {
23380 end_line - 1
23381 } else {
23382 end_line
23383 };
23384
23385 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
23386 let project = self.project()?.read(cx);
23387 let file = buffer.read(cx).file()?;
23388 let path = file.path().display(project.path_style(cx));
23389
23390 let location = if start_line == end_line {
23391 format!("{path}:{start_line}")
23392 } else {
23393 format!("{path}:{start_line}-{end_line}")
23394 };
23395 Some(location)
23396 }) {
23397 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
23398 }
23399 }
23400
23401 pub fn open_permalink_to_line(
23402 &mut self,
23403 _: &OpenPermalinkToLine,
23404 window: &mut Window,
23405 cx: &mut Context<Self>,
23406 ) {
23407 let permalink_task = self.get_permalink_to_line(cx);
23408 let workspace = self.workspace();
23409
23410 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
23411 Ok(permalink) => {
23412 cx.update(|_, cx| {
23413 cx.open_url(permalink.as_ref());
23414 })
23415 .ok();
23416 }
23417 Err(err) => {
23418 let message = format!("Failed to open permalink: {err}");
23419
23420 anyhow::Result::<()>::Err(err).log_err();
23421
23422 if let Some(workspace) = workspace {
23423 workspace.update(cx, |workspace, cx| {
23424 struct OpenPermalinkToLine;
23425
23426 workspace.show_toast(
23427 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
23428 cx,
23429 )
23430 });
23431 }
23432 }
23433 })
23434 .detach();
23435 }
23436
23437 pub fn insert_uuid_v4(
23438 &mut self,
23439 _: &InsertUuidV4,
23440 window: &mut Window,
23441 cx: &mut Context<Self>,
23442 ) {
23443 self.insert_uuid(UuidVersion::V4, window, cx);
23444 }
23445
23446 pub fn insert_uuid_v7(
23447 &mut self,
23448 _: &InsertUuidV7,
23449 window: &mut Window,
23450 cx: &mut Context<Self>,
23451 ) {
23452 self.insert_uuid(UuidVersion::V7, window, cx);
23453 }
23454
23455 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
23456 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
23457 self.transact(window, cx, |this, window, cx| {
23458 let edits = this
23459 .selections
23460 .all::<Point>(&this.display_snapshot(cx))
23461 .into_iter()
23462 .map(|selection| {
23463 let uuid = match version {
23464 UuidVersion::V4 => uuid::Uuid::new_v4(),
23465 UuidVersion::V7 => uuid::Uuid::now_v7(),
23466 };
23467
23468 (selection.range(), uuid.to_string())
23469 });
23470 this.edit(edits, cx);
23471 this.refresh_edit_prediction(true, false, window, cx);
23472 });
23473 }
23474
23475 pub fn open_selections_in_multibuffer(
23476 &mut self,
23477 _: &OpenSelectionsInMultibuffer,
23478 window: &mut Window,
23479 cx: &mut Context<Self>,
23480 ) {
23481 let multibuffer = self.buffer.read(cx);
23482
23483 let Some(buffer) = multibuffer.as_singleton() else {
23484 return;
23485 };
23486
23487 let Some(workspace) = self.workspace() else {
23488 return;
23489 };
23490
23491 let title = multibuffer.title(cx).to_string();
23492
23493 let locations = self
23494 .selections
23495 .all_anchors(&self.display_snapshot(cx))
23496 .iter()
23497 .map(|selection| {
23498 (
23499 buffer.clone(),
23500 (selection.start.text_anchor..selection.end.text_anchor)
23501 .to_point(buffer.read(cx)),
23502 )
23503 })
23504 .into_group_map();
23505
23506 cx.spawn_in(window, async move |_, cx| {
23507 workspace.update_in(cx, |workspace, window, cx| {
23508 Self::open_locations_in_multibuffer(
23509 workspace,
23510 locations,
23511 format!("Selections for '{title}'"),
23512 false,
23513 false,
23514 MultibufferSelectionMode::All,
23515 window,
23516 cx,
23517 );
23518 })
23519 })
23520 .detach();
23521 }
23522
23523 /// Adds a row highlight for the given range. If a row has multiple highlights, the
23524 /// last highlight added will be used.
23525 ///
23526 /// If the range ends at the beginning of a line, then that line will not be highlighted.
23527 pub fn highlight_rows<T: 'static>(
23528 &mut self,
23529 range: Range<Anchor>,
23530 color: Hsla,
23531 options: RowHighlightOptions,
23532 cx: &mut Context<Self>,
23533 ) {
23534 let snapshot = self.buffer().read(cx).snapshot(cx);
23535 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23536 let ix = row_highlights.binary_search_by(|highlight| {
23537 Ordering::Equal
23538 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
23539 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
23540 });
23541
23542 if let Err(mut ix) = ix {
23543 let index = post_inc(&mut self.highlight_order);
23544
23545 // If this range intersects with the preceding highlight, then merge it with
23546 // the preceding highlight. Otherwise insert a new highlight.
23547 let mut merged = false;
23548 if ix > 0 {
23549 let prev_highlight = &mut row_highlights[ix - 1];
23550 if prev_highlight
23551 .range
23552 .end
23553 .cmp(&range.start, &snapshot)
23554 .is_ge()
23555 {
23556 ix -= 1;
23557 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
23558 prev_highlight.range.end = range.end;
23559 }
23560 merged = true;
23561 prev_highlight.index = index;
23562 prev_highlight.color = color;
23563 prev_highlight.options = options;
23564 }
23565 }
23566
23567 if !merged {
23568 row_highlights.insert(
23569 ix,
23570 RowHighlight {
23571 range,
23572 index,
23573 color,
23574 options,
23575 type_id: TypeId::of::<T>(),
23576 },
23577 );
23578 }
23579
23580 // If any of the following highlights intersect with this one, merge them.
23581 while let Some(next_highlight) = row_highlights.get(ix + 1) {
23582 let highlight = &row_highlights[ix];
23583 if next_highlight
23584 .range
23585 .start
23586 .cmp(&highlight.range.end, &snapshot)
23587 .is_le()
23588 {
23589 if next_highlight
23590 .range
23591 .end
23592 .cmp(&highlight.range.end, &snapshot)
23593 .is_gt()
23594 {
23595 row_highlights[ix].range.end = next_highlight.range.end;
23596 }
23597 row_highlights.remove(ix + 1);
23598 } else {
23599 break;
23600 }
23601 }
23602 }
23603 }
23604
23605 /// Remove any highlighted row ranges of the given type that intersect the
23606 /// given ranges.
23607 pub fn remove_highlighted_rows<T: 'static>(
23608 &mut self,
23609 ranges_to_remove: Vec<Range<Anchor>>,
23610 cx: &mut Context<Self>,
23611 ) {
23612 let snapshot = self.buffer().read(cx).snapshot(cx);
23613 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
23614 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23615 row_highlights.retain(|highlight| {
23616 while let Some(range_to_remove) = ranges_to_remove.peek() {
23617 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
23618 Ordering::Less | Ordering::Equal => {
23619 ranges_to_remove.next();
23620 }
23621 Ordering::Greater => {
23622 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
23623 Ordering::Less | Ordering::Equal => {
23624 return false;
23625 }
23626 Ordering::Greater => break,
23627 }
23628 }
23629 }
23630 }
23631
23632 true
23633 })
23634 }
23635
23636 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
23637 pub fn clear_row_highlights<T: 'static>(&mut self) {
23638 self.highlighted_rows.remove(&TypeId::of::<T>());
23639 }
23640
23641 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
23642 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
23643 self.highlighted_rows
23644 .get(&TypeId::of::<T>())
23645 .map_or(&[] as &[_], |vec| vec.as_slice())
23646 .iter()
23647 .map(|highlight| (highlight.range.clone(), highlight.color))
23648 }
23649
23650 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
23651 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
23652 /// Allows to ignore certain kinds of highlights.
23653 pub fn highlighted_display_rows(
23654 &self,
23655 window: &mut Window,
23656 cx: &mut App,
23657 ) -> BTreeMap<DisplayRow, LineHighlight> {
23658 let snapshot = self.snapshot(window, cx);
23659 let mut used_highlight_orders = HashMap::default();
23660 self.highlighted_rows
23661 .iter()
23662 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
23663 .fold(
23664 BTreeMap::<DisplayRow, LineHighlight>::new(),
23665 |mut unique_rows, highlight| {
23666 let start = highlight.range.start.to_display_point(&snapshot);
23667 let end = highlight.range.end.to_display_point(&snapshot);
23668 let start_row = start.row().0;
23669 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
23670 {
23671 end.row().0.saturating_sub(1)
23672 } else {
23673 end.row().0
23674 };
23675 for row in start_row..=end_row {
23676 let used_index =
23677 used_highlight_orders.entry(row).or_insert(highlight.index);
23678 if highlight.index >= *used_index {
23679 *used_index = highlight.index;
23680 unique_rows.insert(
23681 DisplayRow(row),
23682 LineHighlight {
23683 include_gutter: highlight.options.include_gutter,
23684 border: None,
23685 background: highlight.color.into(),
23686 type_id: Some(highlight.type_id),
23687 },
23688 );
23689 }
23690 }
23691 unique_rows
23692 },
23693 )
23694 }
23695
23696 pub fn highlighted_display_row_for_autoscroll(
23697 &self,
23698 snapshot: &DisplaySnapshot,
23699 ) -> Option<DisplayRow> {
23700 self.highlighted_rows
23701 .values()
23702 .flat_map(|highlighted_rows| highlighted_rows.iter())
23703 .filter_map(|highlight| {
23704 if highlight.options.autoscroll {
23705 Some(highlight.range.start.to_display_point(snapshot).row())
23706 } else {
23707 None
23708 }
23709 })
23710 .min()
23711 }
23712
23713 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
23714 self.highlight_background(
23715 HighlightKey::SearchWithinRange,
23716 ranges,
23717 |_, colors| colors.colors().editor_document_highlight_read_background,
23718 cx,
23719 )
23720 }
23721
23722 pub fn set_breadcrumb_header(&mut self, new_header: String) {
23723 self.breadcrumb_header = Some(new_header);
23724 }
23725
23726 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
23727 self.clear_background_highlights(HighlightKey::SearchWithinRange, cx);
23728 }
23729
23730 pub fn highlight_background(
23731 &mut self,
23732 key: HighlightKey,
23733 ranges: &[Range<Anchor>],
23734 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
23735 cx: &mut Context<Self>,
23736 ) {
23737 self.background_highlights
23738 .insert(key, (Arc::new(color_fetcher), Arc::from(ranges)));
23739 self.scrollbar_marker_state.dirty = true;
23740 cx.notify();
23741 }
23742
23743 pub fn clear_background_highlights(
23744 &mut self,
23745 key: HighlightKey,
23746 cx: &mut Context<Self>,
23747 ) -> Option<BackgroundHighlight> {
23748 let text_highlights = self.background_highlights.remove(&key)?;
23749 if !text_highlights.1.is_empty() {
23750 self.scrollbar_marker_state.dirty = true;
23751 cx.notify();
23752 }
23753 Some(text_highlights)
23754 }
23755
23756 pub fn highlight_gutter<T: 'static>(
23757 &mut self,
23758 ranges: impl Into<Vec<Range<Anchor>>>,
23759 color_fetcher: fn(&App) -> Hsla,
23760 cx: &mut Context<Self>,
23761 ) {
23762 self.gutter_highlights
23763 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
23764 cx.notify();
23765 }
23766
23767 pub fn clear_gutter_highlights<T: 'static>(
23768 &mut self,
23769 cx: &mut Context<Self>,
23770 ) -> Option<GutterHighlight> {
23771 cx.notify();
23772 self.gutter_highlights.remove(&TypeId::of::<T>())
23773 }
23774
23775 pub fn insert_gutter_highlight<T: 'static>(
23776 &mut self,
23777 range: Range<Anchor>,
23778 color_fetcher: fn(&App) -> Hsla,
23779 cx: &mut Context<Self>,
23780 ) {
23781 let snapshot = self.buffer().read(cx).snapshot(cx);
23782 let mut highlights = self
23783 .gutter_highlights
23784 .remove(&TypeId::of::<T>())
23785 .map(|(_, highlights)| highlights)
23786 .unwrap_or_default();
23787 let ix = highlights.binary_search_by(|highlight| {
23788 Ordering::Equal
23789 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
23790 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
23791 });
23792 if let Err(ix) = ix {
23793 highlights.insert(ix, range);
23794 }
23795 self.gutter_highlights
23796 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
23797 }
23798
23799 pub fn remove_gutter_highlights<T: 'static>(
23800 &mut self,
23801 ranges_to_remove: Vec<Range<Anchor>>,
23802 cx: &mut Context<Self>,
23803 ) {
23804 let snapshot = self.buffer().read(cx).snapshot(cx);
23805 let Some((color_fetcher, mut gutter_highlights)) =
23806 self.gutter_highlights.remove(&TypeId::of::<T>())
23807 else {
23808 return;
23809 };
23810 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
23811 gutter_highlights.retain(|highlight| {
23812 while let Some(range_to_remove) = ranges_to_remove.peek() {
23813 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
23814 Ordering::Less | Ordering::Equal => {
23815 ranges_to_remove.next();
23816 }
23817 Ordering::Greater => {
23818 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
23819 Ordering::Less | Ordering::Equal => {
23820 return false;
23821 }
23822 Ordering::Greater => break,
23823 }
23824 }
23825 }
23826 }
23827
23828 true
23829 });
23830 self.gutter_highlights
23831 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
23832 }
23833
23834 #[cfg(any(test, feature = "test-support"))]
23835 pub fn all_text_highlights(
23836 &self,
23837 window: &mut Window,
23838 cx: &mut Context<Self>,
23839 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
23840 let snapshot = self.snapshot(window, cx);
23841 self.display_map.update(cx, |display_map, _| {
23842 display_map
23843 .all_text_highlights()
23844 .map(|(_, highlight)| {
23845 let (style, ranges) = highlight.as_ref();
23846 (
23847 *style,
23848 ranges
23849 .iter()
23850 .map(|range| range.clone().to_display_points(&snapshot))
23851 .collect(),
23852 )
23853 })
23854 .collect()
23855 })
23856 }
23857
23858 #[cfg(any(test, feature = "test-support"))]
23859 pub fn all_text_background_highlights(
23860 &self,
23861 window: &mut Window,
23862 cx: &mut Context<Self>,
23863 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23864 let snapshot = self.snapshot(window, cx);
23865 let buffer = &snapshot.buffer_snapshot();
23866 let start = buffer.anchor_before(MultiBufferOffset(0));
23867 let end = buffer.anchor_after(buffer.len());
23868 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
23869 }
23870
23871 #[cfg(any(test, feature = "test-support"))]
23872 pub fn sorted_background_highlights_in_range(
23873 &self,
23874 search_range: Range<Anchor>,
23875 display_snapshot: &DisplaySnapshot,
23876 theme: &Theme,
23877 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23878 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
23879 res.sort_by(|a, b| {
23880 a.0.start
23881 .cmp(&b.0.start)
23882 .then_with(|| a.0.end.cmp(&b.0.end))
23883 .then_with(|| a.1.cmp(&b.1))
23884 });
23885 res
23886 }
23887
23888 #[cfg(any(test, feature = "test-support"))]
23889 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
23890 let snapshot = self.buffer().read(cx).snapshot(cx);
23891
23892 let highlights = self
23893 .background_highlights
23894 .get(&HighlightKey::BufferSearchHighlights);
23895
23896 if let Some((_color, ranges)) = highlights {
23897 ranges
23898 .iter()
23899 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
23900 .collect_vec()
23901 } else {
23902 vec![]
23903 }
23904 }
23905
23906 fn document_highlights_for_position<'a>(
23907 &'a self,
23908 position: Anchor,
23909 buffer: &'a MultiBufferSnapshot,
23910 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
23911 let read_highlights = self
23912 .background_highlights
23913 .get(&HighlightKey::DocumentHighlightRead)
23914 .map(|h| &h.1);
23915 let write_highlights = self
23916 .background_highlights
23917 .get(&HighlightKey::DocumentHighlightWrite)
23918 .map(|h| &h.1);
23919 let left_position = position.bias_left(buffer);
23920 let right_position = position.bias_right(buffer);
23921 read_highlights
23922 .into_iter()
23923 .chain(write_highlights)
23924 .flat_map(move |ranges| {
23925 let start_ix = match ranges.binary_search_by(|probe| {
23926 let cmp = probe.end.cmp(&left_position, buffer);
23927 if cmp.is_ge() {
23928 Ordering::Greater
23929 } else {
23930 Ordering::Less
23931 }
23932 }) {
23933 Ok(i) | Err(i) => i,
23934 };
23935
23936 ranges[start_ix..]
23937 .iter()
23938 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
23939 })
23940 }
23941
23942 pub fn has_background_highlights(&self, key: HighlightKey) -> bool {
23943 self.background_highlights
23944 .get(&key)
23945 .is_some_and(|(_, highlights)| !highlights.is_empty())
23946 }
23947
23948 /// Returns all background highlights for a given range.
23949 ///
23950 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
23951 pub fn background_highlights_in_range(
23952 &self,
23953 search_range: Range<Anchor>,
23954 display_snapshot: &DisplaySnapshot,
23955 theme: &Theme,
23956 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23957 let mut results = Vec::new();
23958 for (color_fetcher, ranges) in self.background_highlights.values() {
23959 let start_ix = match ranges.binary_search_by(|probe| {
23960 let cmp = probe
23961 .end
23962 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
23963 if cmp.is_gt() {
23964 Ordering::Greater
23965 } else {
23966 Ordering::Less
23967 }
23968 }) {
23969 Ok(i) | Err(i) => i,
23970 };
23971 for (index, range) in ranges[start_ix..].iter().enumerate() {
23972 if range
23973 .start
23974 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
23975 .is_ge()
23976 {
23977 break;
23978 }
23979
23980 let color = color_fetcher(&(start_ix + index), theme);
23981 let start = range.start.to_display_point(display_snapshot);
23982 let end = range.end.to_display_point(display_snapshot);
23983 results.push((start..end, color))
23984 }
23985 }
23986 results
23987 }
23988
23989 pub fn gutter_highlights_in_range(
23990 &self,
23991 search_range: Range<Anchor>,
23992 display_snapshot: &DisplaySnapshot,
23993 cx: &App,
23994 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
23995 let mut results = Vec::new();
23996 for (color_fetcher, ranges) in self.gutter_highlights.values() {
23997 let color = color_fetcher(cx);
23998 let start_ix = match ranges.binary_search_by(|probe| {
23999 let cmp = probe
24000 .end
24001 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
24002 if cmp.is_gt() {
24003 Ordering::Greater
24004 } else {
24005 Ordering::Less
24006 }
24007 }) {
24008 Ok(i) | Err(i) => i,
24009 };
24010 for range in &ranges[start_ix..] {
24011 if range
24012 .start
24013 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
24014 .is_ge()
24015 {
24016 break;
24017 }
24018
24019 let start = range.start.to_display_point(display_snapshot);
24020 let end = range.end.to_display_point(display_snapshot);
24021 results.push((start..end, color))
24022 }
24023 }
24024 results
24025 }
24026
24027 /// Get the text ranges corresponding to the redaction query
24028 pub fn redacted_ranges(
24029 &self,
24030 search_range: Range<Anchor>,
24031 display_snapshot: &DisplaySnapshot,
24032 cx: &App,
24033 ) -> Vec<Range<DisplayPoint>> {
24034 display_snapshot
24035 .buffer_snapshot()
24036 .redacted_ranges(search_range, |file| {
24037 if let Some(file) = file {
24038 file.is_private()
24039 && EditorSettings::get(
24040 Some(SettingsLocation {
24041 worktree_id: file.worktree_id(cx),
24042 path: file.path().as_ref(),
24043 }),
24044 cx,
24045 )
24046 .redact_private_values
24047 } else {
24048 false
24049 }
24050 })
24051 .map(|range| {
24052 range.start.to_display_point(display_snapshot)
24053 ..range.end.to_display_point(display_snapshot)
24054 })
24055 .collect()
24056 }
24057
24058 pub fn highlight_text_key(
24059 &mut self,
24060 key: HighlightKey,
24061 ranges: Vec<Range<Anchor>>,
24062 style: HighlightStyle,
24063 merge: bool,
24064 cx: &mut Context<Self>,
24065 ) {
24066 self.display_map.update(cx, |map, cx| {
24067 map.highlight_text(key, ranges, style, merge, cx);
24068 });
24069 cx.notify();
24070 }
24071
24072 pub fn highlight_text(
24073 &mut self,
24074 key: HighlightKey,
24075 ranges: Vec<Range<Anchor>>,
24076 style: HighlightStyle,
24077 cx: &mut Context<Self>,
24078 ) {
24079 self.display_map.update(cx, |map, cx| {
24080 map.highlight_text(key, ranges, style, false, cx)
24081 });
24082 cx.notify();
24083 }
24084
24085 pub fn text_highlights<'a>(
24086 &'a self,
24087 key: HighlightKey,
24088 cx: &'a App,
24089 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
24090 self.display_map.read(cx).text_highlights(key)
24091 }
24092
24093 pub fn clear_highlights(&mut self, key: HighlightKey, cx: &mut Context<Self>) {
24094 let cleared = self
24095 .display_map
24096 .update(cx, |map, _| map.clear_highlights(key));
24097 if cleared {
24098 cx.notify();
24099 }
24100 }
24101
24102 pub fn clear_highlights_with(
24103 &mut self,
24104 f: &mut dyn FnMut(&HighlightKey) -> bool,
24105 cx: &mut Context<Self>,
24106 ) {
24107 let cleared = self
24108 .display_map
24109 .update(cx, |map, _| map.clear_highlights_with(f));
24110 if cleared {
24111 cx.notify();
24112 }
24113 }
24114
24115 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
24116 (self.read_only(cx) || self.blink_manager.read(cx).visible())
24117 && self.focus_handle.is_focused(window)
24118 }
24119
24120 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
24121 self.show_cursor_when_unfocused = is_enabled;
24122 cx.notify();
24123 }
24124
24125 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
24126 cx.notify();
24127 }
24128
24129 fn on_debug_session_event(
24130 &mut self,
24131 _session: Entity<Session>,
24132 event: &SessionEvent,
24133 cx: &mut Context<Self>,
24134 ) {
24135 if let SessionEvent::InvalidateInlineValue = event {
24136 self.refresh_inline_values(cx);
24137 }
24138 }
24139
24140 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
24141 let Some(semantics) = self.semantics_provider.clone() else {
24142 return;
24143 };
24144
24145 if !self.inline_value_cache.enabled {
24146 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
24147 self.splice_inlays(&inlays, Vec::new(), cx);
24148 return;
24149 }
24150
24151 let current_execution_position = self
24152 .highlighted_rows
24153 .get(&TypeId::of::<ActiveDebugLine>())
24154 .and_then(|lines| lines.last().map(|line| line.range.end));
24155
24156 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
24157 let inline_values = editor
24158 .update(cx, |editor, cx| {
24159 let Some(current_execution_position) = current_execution_position else {
24160 return Some(Task::ready(Ok(Vec::new())));
24161 };
24162
24163 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
24164 let snapshot = buffer.snapshot(cx);
24165
24166 let excerpt = snapshot.excerpt_containing(
24167 current_execution_position..current_execution_position,
24168 )?;
24169
24170 editor.buffer.read(cx).buffer(excerpt.buffer_id())
24171 })?;
24172
24173 if current_execution_position
24174 .text_anchor
24175 .buffer_id
24176 .is_some_and(|id| id != buffer.read(cx).remote_id())
24177 {
24178 return Some(Task::ready(Ok(Vec::new())));
24179 }
24180
24181 let range =
24182 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
24183
24184 semantics.inline_values(buffer, range, cx)
24185 })
24186 .ok()
24187 .flatten()?
24188 .await
24189 .context("refreshing debugger inlays")
24190 .log_err()?;
24191
24192 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
24193
24194 for (buffer_id, inline_value) in inline_values
24195 .into_iter()
24196 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
24197 {
24198 buffer_inline_values
24199 .entry(buffer_id)
24200 .or_default()
24201 .push(inline_value);
24202 }
24203
24204 editor
24205 .update(cx, |editor, cx| {
24206 let snapshot = editor.buffer.read(cx).snapshot(cx);
24207 let mut new_inlays = Vec::default();
24208
24209 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
24210 let buffer_id = buffer_snapshot.remote_id();
24211 buffer_inline_values
24212 .get(&buffer_id)
24213 .into_iter()
24214 .flatten()
24215 .for_each(|hint| {
24216 let inlay = Inlay::debugger(
24217 post_inc(&mut editor.next_inlay_id),
24218 Anchor::in_buffer(excerpt_id, hint.position),
24219 hint.text(),
24220 );
24221 if !inlay.text().chars().contains(&'\n') {
24222 new_inlays.push(inlay);
24223 }
24224 });
24225 }
24226
24227 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
24228 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
24229
24230 editor.splice_inlays(&inlay_ids, new_inlays, cx);
24231 })
24232 .ok()?;
24233 Some(())
24234 });
24235 }
24236
24237 fn on_buffer_event(
24238 &mut self,
24239 multibuffer: &Entity<MultiBuffer>,
24240 event: &multi_buffer::Event,
24241 window: &mut Window,
24242 cx: &mut Context<Self>,
24243 ) {
24244 match event {
24245 multi_buffer::Event::Edited {
24246 edited_buffer,
24247 is_local,
24248 } => {
24249 self.scrollbar_marker_state.dirty = true;
24250 self.active_indent_guides_state.dirty = true;
24251 self.refresh_active_diagnostics(cx);
24252 self.refresh_code_actions(window, cx);
24253 self.refresh_single_line_folds(window, cx);
24254 let snapshot = self.snapshot(window, cx);
24255 self.refresh_matching_bracket_highlights(&snapshot, cx);
24256 self.refresh_outline_symbols_at_cursor(cx);
24257 self.refresh_sticky_headers(&snapshot, cx);
24258 if *is_local && self.has_active_edit_prediction() {
24259 self.update_visible_edit_prediction(window, cx);
24260 }
24261
24262 // Clean up orphaned review comments after edits
24263 self.cleanup_orphaned_review_comments(cx);
24264
24265 if let Some(buffer) = edited_buffer {
24266 if buffer.read(cx).file().is_none() {
24267 cx.emit(EditorEvent::TitleChanged);
24268 }
24269
24270 if self.project.is_some() {
24271 let buffer_id = buffer.read(cx).remote_id();
24272 self.register_buffer(buffer_id, cx);
24273 self.update_lsp_data(Some(buffer_id), window, cx);
24274 self.refresh_inlay_hints(
24275 InlayHintRefreshReason::BufferEdited(buffer_id),
24276 cx,
24277 );
24278 }
24279 }
24280
24281 cx.emit(EditorEvent::BufferEdited);
24282 cx.emit(SearchEvent::MatchesInvalidated);
24283
24284 let Some(project) = &self.project else { return };
24285 let (telemetry, is_via_ssh) = {
24286 let project = project.read(cx);
24287 let telemetry = project.client().telemetry().clone();
24288 let is_via_ssh = project.is_via_remote_server();
24289 (telemetry, is_via_ssh)
24290 };
24291 telemetry.log_edit_event("editor", is_via_ssh);
24292 }
24293 multi_buffer::Event::ExcerptsAdded {
24294 buffer,
24295 predecessor,
24296 excerpts,
24297 } => {
24298 let buffer_id = buffer.read(cx).remote_id();
24299 if self.buffer.read(cx).diff_for(buffer_id).is_none()
24300 && let Some(project) = &self.project
24301 {
24302 update_uncommitted_diff_for_buffer(
24303 cx.entity(),
24304 project,
24305 [buffer.clone()],
24306 self.buffer.clone(),
24307 cx,
24308 )
24309 .detach();
24310 }
24311 self.semantic_token_state
24312 .invalidate_buffer(&buffer.read(cx).remote_id());
24313 self.update_lsp_data(Some(buffer_id), window, cx);
24314 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24315 self.refresh_runnables(None, window, cx);
24316 self.colorize_brackets(false, cx);
24317 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24318 cx.emit(EditorEvent::ExcerptsAdded {
24319 buffer: buffer.clone(),
24320 predecessor: *predecessor,
24321 excerpts: excerpts.clone(),
24322 });
24323 }
24324 multi_buffer::Event::ExcerptsRemoved {
24325 ids,
24326 removed_buffer_ids,
24327 } => {
24328 if let Some(inlay_hints) = &mut self.inlay_hints {
24329 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
24330 }
24331 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
24332 for buffer_id in removed_buffer_ids {
24333 self.registered_buffers.remove(buffer_id);
24334 self.clear_runnables(Some(*buffer_id));
24335 self.semantic_token_state.invalidate_buffer(buffer_id);
24336 self.display_map.update(cx, |display_map, cx| {
24337 display_map.invalidate_semantic_highlights(*buffer_id);
24338 display_map.clear_lsp_folding_ranges(*buffer_id, cx);
24339 });
24340 }
24341
24342 self.display_map.update(cx, |display_map, cx| {
24343 display_map.unfold_buffers(removed_buffer_ids.iter().copied(), cx);
24344 });
24345
24346 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24347 cx.emit(EditorEvent::ExcerptsRemoved {
24348 ids: ids.clone(),
24349 removed_buffer_ids: removed_buffer_ids.clone(),
24350 });
24351 }
24352 multi_buffer::Event::ExcerptsEdited {
24353 excerpt_ids,
24354 buffer_ids,
24355 } => {
24356 self.display_map.update(cx, |map, cx| {
24357 map.unfold_buffers(buffer_ids.iter().copied(), cx)
24358 });
24359 cx.emit(EditorEvent::ExcerptsEdited {
24360 ids: excerpt_ids.clone(),
24361 });
24362 }
24363 multi_buffer::Event::ExcerptsExpanded { ids } => {
24364 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
24365 self.refresh_document_highlights(cx);
24366 let snapshot = multibuffer.read(cx).snapshot(cx);
24367 for id in ids {
24368 self.bracket_fetched_tree_sitter_chunks.remove(id);
24369 if let Some(buffer) = snapshot.buffer_for_excerpt(*id) {
24370 self.semantic_token_state
24371 .invalidate_buffer(&buffer.remote_id());
24372 }
24373 }
24374 self.colorize_brackets(false, cx);
24375 self.update_lsp_data(None, window, cx);
24376 self.refresh_runnables(None, window, cx);
24377 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
24378 }
24379 multi_buffer::Event::Reparsed(buffer_id) => {
24380 self.refresh_runnables(Some(*buffer_id), window, cx);
24381 self.refresh_selected_text_highlights(&self.display_snapshot(cx), true, window, cx);
24382 self.colorize_brackets(true, cx);
24383 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24384
24385 cx.emit(EditorEvent::Reparsed(*buffer_id));
24386 }
24387 multi_buffer::Event::DiffHunksToggled => {
24388 self.refresh_runnables(None, window, cx);
24389 }
24390 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
24391 if !is_fresh_language {
24392 self.registered_buffers.remove(&buffer_id);
24393 }
24394 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
24395 cx.emit(EditorEvent::Reparsed(*buffer_id));
24396 self.update_edit_prediction_settings(cx);
24397 cx.notify();
24398 }
24399 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
24400 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
24401 multi_buffer::Event::FileHandleChanged
24402 | multi_buffer::Event::Reloaded
24403 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
24404 multi_buffer::Event::DiagnosticsUpdated => {
24405 self.update_diagnostics_state(window, cx);
24406 }
24407 _ => {}
24408 };
24409 }
24410
24411 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
24412 if !self.diagnostics_enabled() {
24413 return;
24414 }
24415 self.refresh_active_diagnostics(cx);
24416 self.refresh_inline_diagnostics(true, window, cx);
24417 self.scrollbar_marker_state.dirty = true;
24418 cx.notify();
24419 }
24420
24421 pub fn start_temporary_diff_override(&mut self) {
24422 self.load_diff_task.take();
24423 self.temporary_diff_override = true;
24424 }
24425
24426 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
24427 self.temporary_diff_override = false;
24428 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
24429 self.buffer.update(cx, |buffer, cx| {
24430 buffer.set_all_diff_hunks_collapsed(cx);
24431 });
24432
24433 if let Some(project) = self.project.clone() {
24434 self.load_diff_task = Some(
24435 update_uncommitted_diff_for_buffer(
24436 cx.entity(),
24437 &project,
24438 self.buffer.read(cx).all_buffers(),
24439 self.buffer.clone(),
24440 cx,
24441 )
24442 .shared(),
24443 );
24444 }
24445 }
24446
24447 fn on_display_map_changed(
24448 &mut self,
24449 _: Entity<DisplayMap>,
24450 _: &mut Window,
24451 cx: &mut Context<Self>,
24452 ) {
24453 cx.notify();
24454 }
24455
24456 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
24457 if !self.mode.is_full() {
24458 return None;
24459 }
24460
24461 let theme_settings = theme_settings::ThemeSettings::get_global(cx);
24462 let theme = cx.theme();
24463 let accent_colors = theme.accents().clone();
24464
24465 let accent_overrides = theme_settings
24466 .theme_overrides
24467 .get(theme.name.as_ref())
24468 .map(|theme_style| &theme_style.accents)
24469 .into_iter()
24470 .flatten()
24471 .chain(
24472 theme_settings
24473 .experimental_theme_overrides
24474 .as_ref()
24475 .map(|overrides| &overrides.accents)
24476 .into_iter()
24477 .flatten(),
24478 )
24479 .flat_map(|accent| accent.0.clone().map(SharedString::from))
24480 .collect();
24481
24482 Some(AccentData {
24483 colors: accent_colors,
24484 overrides: accent_overrides,
24485 })
24486 }
24487
24488 fn fetch_applicable_language_settings(
24489 &self,
24490 cx: &App,
24491 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
24492 if !self.mode.is_full() {
24493 return HashMap::default();
24494 }
24495
24496 self.buffer().read(cx).all_buffers().into_iter().fold(
24497 HashMap::default(),
24498 |mut acc, buffer| {
24499 let buffer = buffer.read(cx);
24500 let language = buffer.language().map(|language| language.name());
24501 if let hash_map::Entry::Vacant(v) = acc.entry(language) {
24502 v.insert(LanguageSettings::for_buffer(&buffer, cx).into_owned());
24503 }
24504 acc
24505 },
24506 )
24507 }
24508
24509 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
24510 let new_language_settings = self.fetch_applicable_language_settings(cx);
24511 let language_settings_changed = new_language_settings != self.applicable_language_settings;
24512 self.applicable_language_settings = new_language_settings;
24513
24514 let new_accents = self.fetch_accent_data(cx);
24515 let accents_changed = new_accents != self.accent_data;
24516 self.accent_data = new_accents;
24517
24518 if self.diagnostics_enabled() {
24519 let new_severity = EditorSettings::get_global(cx)
24520 .diagnostics_max_severity
24521 .unwrap_or(DiagnosticSeverity::Hint);
24522 self.set_max_diagnostics_severity(new_severity, cx);
24523 }
24524 self.refresh_runnables(None, window, cx);
24525 self.update_edit_prediction_settings(cx);
24526 self.refresh_edit_prediction(true, false, window, cx);
24527 self.refresh_inline_values(cx);
24528
24529 let old_cursor_shape = self.cursor_shape;
24530 let old_show_breadcrumbs = self.show_breadcrumbs;
24531
24532 {
24533 let editor_settings = EditorSettings::get_global(cx);
24534 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
24535 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
24536 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
24537 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
24538 }
24539
24540 if old_cursor_shape != self.cursor_shape {
24541 cx.emit(EditorEvent::CursorShapeChanged);
24542 }
24543
24544 if old_show_breadcrumbs != self.show_breadcrumbs {
24545 cx.emit(EditorEvent::BreadcrumbsChanged);
24546 }
24547
24548 let (restore_unsaved_buffers, show_inline_diagnostics, inline_blame_enabled) = {
24549 let project_settings = ProjectSettings::get_global(cx);
24550 (
24551 project_settings.session.restore_unsaved_buffers,
24552 project_settings.diagnostics.inline.enabled,
24553 project_settings.git.inline_blame.enabled,
24554 )
24555 };
24556 self.buffer_serialization = self
24557 .should_serialize_buffer()
24558 .then(|| BufferSerialization::new(restore_unsaved_buffers));
24559
24560 if self.mode.is_full() {
24561 if self.show_inline_diagnostics != show_inline_diagnostics {
24562 self.show_inline_diagnostics = show_inline_diagnostics;
24563 self.refresh_inline_diagnostics(false, window, cx);
24564 }
24565
24566 if self.git_blame_inline_enabled != inline_blame_enabled {
24567 self.toggle_git_blame_inline_internal(false, window, cx);
24568 }
24569
24570 let minimap_settings = EditorSettings::get_global(cx).minimap;
24571 if self.minimap_visibility != MinimapVisibility::Disabled {
24572 if self.minimap_visibility.settings_visibility()
24573 != minimap_settings.minimap_enabled()
24574 {
24575 self.set_minimap_visibility(
24576 MinimapVisibility::for_mode(self.mode(), cx),
24577 window,
24578 cx,
24579 );
24580 } else if let Some(minimap_entity) = self.minimap.as_ref() {
24581 minimap_entity.update(cx, |minimap_editor, cx| {
24582 minimap_editor.update_minimap_configuration(minimap_settings, cx)
24583 })
24584 }
24585 }
24586
24587 if language_settings_changed || accents_changed {
24588 self.colorize_brackets(true, cx);
24589 }
24590
24591 if language_settings_changed {
24592 self.clear_disabled_lsp_folding_ranges(window, cx);
24593 self.refresh_document_symbols(None, cx);
24594 }
24595
24596 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
24597 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
24598 }) {
24599 if !inlay_splice.is_empty() {
24600 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
24601 }
24602 self.refresh_document_colors(None, window, cx);
24603 }
24604
24605 self.refresh_inlay_hints(
24606 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
24607 self.selections.newest_anchor().head(),
24608 &self.buffer.read(cx).snapshot(cx),
24609 cx,
24610 )),
24611 cx,
24612 );
24613
24614 let new_semantic_token_rules = ProjectSettings::get_global(cx)
24615 .global_lsp_settings
24616 .semantic_token_rules
24617 .clone();
24618 let semantic_token_rules_changed = self
24619 .semantic_token_state
24620 .update_rules(new_semantic_token_rules);
24621 if language_settings_changed || semantic_token_rules_changed {
24622 self.invalidate_semantic_tokens(None);
24623 self.refresh_semantic_tokens(None, None, cx);
24624 }
24625 }
24626
24627 cx.notify();
24628 }
24629
24630 fn theme_changed(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24631 if !self.mode.is_full() {
24632 return;
24633 }
24634
24635 let new_accents = self.fetch_accent_data(cx);
24636 if new_accents != self.accent_data {
24637 self.accent_data = new_accents;
24638 self.colorize_brackets(true, cx);
24639 }
24640
24641 self.invalidate_semantic_tokens(None);
24642 self.refresh_semantic_tokens(None, None, cx);
24643 }
24644
24645 pub fn set_searchable(&mut self, searchable: bool) {
24646 self.searchable = searchable;
24647 }
24648
24649 pub fn searchable(&self) -> bool {
24650 self.searchable
24651 }
24652
24653 pub fn open_excerpts_in_split(
24654 &mut self,
24655 _: &OpenExcerptsSplit,
24656 window: &mut Window,
24657 cx: &mut Context<Self>,
24658 ) {
24659 self.open_excerpts_common(None, true, window, cx)
24660 }
24661
24662 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
24663 self.open_excerpts_common(None, false, window, cx)
24664 }
24665
24666 pub(crate) fn open_excerpts_common(
24667 &mut self,
24668 jump_data: Option<JumpData>,
24669 split: bool,
24670 window: &mut Window,
24671 cx: &mut Context<Self>,
24672 ) {
24673 if self.buffer.read(cx).is_singleton() {
24674 cx.propagate();
24675 return;
24676 }
24677
24678 let mut new_selections_by_buffer = HashMap::default();
24679 match &jump_data {
24680 Some(JumpData::MultiBufferPoint {
24681 excerpt_id,
24682 position,
24683 anchor,
24684 line_offset_from_top,
24685 }) => {
24686 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
24687 if let Some(buffer) = multi_buffer_snapshot
24688 .buffer_id_for_excerpt(*excerpt_id)
24689 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
24690 {
24691 let buffer_snapshot = buffer.read(cx).snapshot();
24692 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
24693 language::ToPoint::to_point(anchor, &buffer_snapshot)
24694 } else {
24695 buffer_snapshot.clip_point(*position, Bias::Left)
24696 };
24697 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
24698 new_selections_by_buffer.insert(
24699 buffer,
24700 (
24701 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
24702 Some(*line_offset_from_top),
24703 ),
24704 );
24705 }
24706 }
24707 Some(JumpData::MultiBufferRow {
24708 row,
24709 line_offset_from_top,
24710 }) => {
24711 let point = MultiBufferPoint::new(row.0, 0);
24712 if let Some((buffer, buffer_point, _)) =
24713 self.buffer.read(cx).point_to_buffer_point(point, cx)
24714 {
24715 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
24716 new_selections_by_buffer
24717 .entry(buffer)
24718 .or_insert((Vec::new(), Some(*line_offset_from_top)))
24719 .0
24720 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
24721 }
24722 }
24723 None => {
24724 let selections = self
24725 .selections
24726 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
24727 let multi_buffer = self.buffer.read(cx);
24728 for selection in selections {
24729 for (snapshot, range, _, anchor) in multi_buffer
24730 .snapshot(cx)
24731 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
24732 {
24733 if let Some(anchor) = anchor {
24734 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
24735 else {
24736 continue;
24737 };
24738 let offset = text::ToOffset::to_offset(
24739 &anchor.text_anchor,
24740 &buffer_handle.read(cx).snapshot(),
24741 );
24742 let range = BufferOffset(offset)..BufferOffset(offset);
24743 new_selections_by_buffer
24744 .entry(buffer_handle)
24745 .or_insert((Vec::new(), None))
24746 .0
24747 .push(range)
24748 } else {
24749 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
24750 else {
24751 continue;
24752 };
24753 new_selections_by_buffer
24754 .entry(buffer_handle)
24755 .or_insert((Vec::new(), None))
24756 .0
24757 .push(range)
24758 }
24759 }
24760 }
24761 }
24762 }
24763
24764 if self.delegate_open_excerpts {
24765 let selections_by_buffer: HashMap<_, _> = new_selections_by_buffer
24766 .into_iter()
24767 .map(|(buffer, value)| (buffer.read(cx).remote_id(), value))
24768 .collect();
24769 if !selections_by_buffer.is_empty() {
24770 cx.emit(EditorEvent::OpenExcerptsRequested {
24771 selections_by_buffer,
24772 split,
24773 });
24774 }
24775 return;
24776 }
24777
24778 let Some(workspace) = self.workspace() else {
24779 cx.propagate();
24780 return;
24781 };
24782
24783 new_selections_by_buffer
24784 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
24785
24786 if new_selections_by_buffer.is_empty() {
24787 return;
24788 }
24789
24790 Self::open_buffers_in_workspace(
24791 workspace.downgrade(),
24792 new_selections_by_buffer,
24793 split,
24794 window,
24795 cx,
24796 );
24797 }
24798
24799 pub(crate) fn open_buffers_in_workspace(
24800 workspace: WeakEntity<Workspace>,
24801 new_selections_by_buffer: HashMap<
24802 Entity<language::Buffer>,
24803 (Vec<Range<BufferOffset>>, Option<u32>),
24804 >,
24805 split: bool,
24806 window: &mut Window,
24807 cx: &mut App,
24808 ) {
24809 // We defer the pane interaction because we ourselves are a workspace item
24810 // and activating a new item causes the pane to call a method on us reentrantly,
24811 // which panics if we're on the stack.
24812 window.defer(cx, move |window, cx| {
24813 workspace
24814 .update(cx, |workspace, cx| {
24815 let pane = if split {
24816 workspace.adjacent_pane(window, cx)
24817 } else {
24818 workspace.active_pane().clone()
24819 };
24820
24821 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
24822 let buffer_read = buffer.read(cx);
24823 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
24824 (true, project::File::from_dyn(Some(file)).is_some())
24825 } else {
24826 (false, false)
24827 };
24828
24829 // If project file is none workspace.open_project_item will fail to open the excerpt
24830 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
24831 // so we check if there's a tab match in that case first
24832 let editor = (!has_file || !is_project_file)
24833 .then(|| {
24834 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
24835 // so `workspace.open_project_item` will never find them, always opening a new editor.
24836 // Instead, we try to activate the existing editor in the pane first.
24837 let (editor, pane_item_index, pane_item_id) =
24838 pane.read(cx).items().enumerate().find_map(|(i, item)| {
24839 let editor = item.downcast::<Editor>()?;
24840 let singleton_buffer =
24841 editor.read(cx).buffer().read(cx).as_singleton()?;
24842 if singleton_buffer == buffer {
24843 Some((editor, i, item.item_id()))
24844 } else {
24845 None
24846 }
24847 })?;
24848 pane.update(cx, |pane, cx| {
24849 pane.activate_item(pane_item_index, true, true, window, cx);
24850 if !PreviewTabsSettings::get_global(cx)
24851 .enable_preview_from_multibuffer
24852 {
24853 pane.unpreview_item_if_preview(pane_item_id);
24854 }
24855 });
24856 Some(editor)
24857 })
24858 .flatten()
24859 .unwrap_or_else(|| {
24860 let keep_old_preview = PreviewTabsSettings::get_global(cx)
24861 .enable_keep_preview_on_code_navigation;
24862 let allow_new_preview = PreviewTabsSettings::get_global(cx)
24863 .enable_preview_from_multibuffer;
24864 workspace.open_project_item::<Self>(
24865 pane.clone(),
24866 buffer,
24867 true,
24868 true,
24869 keep_old_preview,
24870 allow_new_preview,
24871 window,
24872 cx,
24873 )
24874 });
24875
24876 editor.update(cx, |editor, cx| {
24877 if has_file && !is_project_file {
24878 editor.set_read_only(true);
24879 }
24880 let autoscroll = match scroll_offset {
24881 Some(scroll_offset) => {
24882 Autoscroll::top_relative(scroll_offset as usize)
24883 }
24884 None => Autoscroll::newest(),
24885 };
24886 let nav_history = editor.nav_history.take();
24887 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
24888 let Some((excerpt_id, _, buffer_snapshot)) =
24889 multibuffer_snapshot.as_singleton()
24890 else {
24891 return;
24892 };
24893 editor.change_selections(
24894 SelectionEffects::scroll(autoscroll),
24895 window,
24896 cx,
24897 |s| {
24898 s.select_ranges(ranges.into_iter().map(|range| {
24899 let range = buffer_snapshot.anchor_before(range.start)
24900 ..buffer_snapshot.anchor_after(range.end);
24901 multibuffer_snapshot
24902 .anchor_range_in_excerpt(excerpt_id, range)
24903 .unwrap()
24904 }));
24905 },
24906 );
24907 editor.nav_history = nav_history;
24908 });
24909 }
24910 })
24911 .ok();
24912 });
24913 }
24914
24915 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
24916 let snapshot = self.buffer.read(cx).read(cx);
24917 let (_, ranges) = self.text_highlights(HighlightKey::InputComposition, cx)?;
24918 Some(
24919 ranges
24920 .iter()
24921 .map(move |range| {
24922 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
24923 })
24924 .collect(),
24925 )
24926 }
24927
24928 fn selection_replacement_ranges(
24929 &self,
24930 range: Range<MultiBufferOffsetUtf16>,
24931 cx: &mut App,
24932 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
24933 let selections = self
24934 .selections
24935 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24936 let newest_selection = selections
24937 .iter()
24938 .max_by_key(|selection| selection.id)
24939 .unwrap();
24940 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
24941 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
24942 let snapshot = self.buffer.read(cx).read(cx);
24943 selections
24944 .into_iter()
24945 .map(|mut selection| {
24946 selection.start.0.0 =
24947 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
24948 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
24949 snapshot.clip_offset_utf16(selection.start, Bias::Left)
24950 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
24951 })
24952 .collect()
24953 }
24954
24955 fn report_editor_event(
24956 &self,
24957 reported_event: ReportEditorEvent,
24958 file_extension: Option<String>,
24959 cx: &App,
24960 ) {
24961 if cfg!(any(test, feature = "test-support")) {
24962 return;
24963 }
24964
24965 let Some(project) = &self.project else { return };
24966
24967 // If None, we are in a file without an extension
24968 let file = self
24969 .buffer
24970 .read(cx)
24971 .as_singleton()
24972 .and_then(|b| b.read(cx).file());
24973 let file_extension = file_extension.or(file
24974 .as_ref()
24975 .and_then(|file| Path::new(file.file_name(cx)).extension())
24976 .and_then(|e| e.to_str())
24977 .map(|a| a.to_string()));
24978
24979 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
24980 .map(|vim_mode| vim_mode.0)
24981 .unwrap_or(false);
24982
24983 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
24984 let copilot_enabled = edit_predictions_provider
24985 == language::language_settings::EditPredictionProvider::Copilot;
24986 let copilot_enabled_for_language = self
24987 .buffer
24988 .read(cx)
24989 .language_settings(cx)
24990 .show_edit_predictions;
24991
24992 let project = project.read(cx);
24993 let event_type = reported_event.event_type();
24994
24995 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
24996 telemetry::event!(
24997 event_type,
24998 type = if auto_saved {"autosave"} else {"manual"},
24999 file_extension,
25000 vim_mode,
25001 copilot_enabled,
25002 copilot_enabled_for_language,
25003 edit_predictions_provider,
25004 is_via_ssh = project.is_via_remote_server(),
25005 );
25006 } else {
25007 telemetry::event!(
25008 event_type,
25009 file_extension,
25010 vim_mode,
25011 copilot_enabled,
25012 copilot_enabled_for_language,
25013 edit_predictions_provider,
25014 is_via_ssh = project.is_via_remote_server(),
25015 );
25016 };
25017 }
25018
25019 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
25020 /// with each line being an array of {text, highlight} objects.
25021 fn copy_highlight_json(
25022 &mut self,
25023 _: &CopyHighlightJson,
25024 window: &mut Window,
25025 cx: &mut Context<Self>,
25026 ) {
25027 #[derive(Serialize)]
25028 struct Chunk<'a> {
25029 text: String,
25030 highlight: Option<&'a str>,
25031 }
25032
25033 let snapshot = self.buffer.read(cx).snapshot(cx);
25034 let range = self
25035 .selected_text_range(false, window, cx)
25036 .and_then(|selection| {
25037 if selection.range.is_empty() {
25038 None
25039 } else {
25040 Some(
25041 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
25042 selection.range.start,
25043 )))
25044 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
25045 selection.range.end,
25046 ))),
25047 )
25048 }
25049 })
25050 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
25051
25052 let chunks = snapshot.chunks(range, true);
25053 let mut lines = Vec::new();
25054 let mut line: VecDeque<Chunk> = VecDeque::new();
25055
25056 let Some(style) = self.style.as_ref() else {
25057 return;
25058 };
25059
25060 for chunk in chunks {
25061 let highlight = chunk
25062 .syntax_highlight_id
25063 .and_then(|id| style.syntax.get_capture_name(id));
25064
25065 let mut chunk_lines = chunk.text.split('\n').peekable();
25066 while let Some(text) = chunk_lines.next() {
25067 let mut merged_with_last_token = false;
25068 if let Some(last_token) = line.back_mut()
25069 && last_token.highlight == highlight
25070 {
25071 last_token.text.push_str(text);
25072 merged_with_last_token = true;
25073 }
25074
25075 if !merged_with_last_token {
25076 line.push_back(Chunk {
25077 text: text.into(),
25078 highlight,
25079 });
25080 }
25081
25082 if chunk_lines.peek().is_some() {
25083 if line.len() > 1 && line.front().unwrap().text.is_empty() {
25084 line.pop_front();
25085 }
25086 if line.len() > 1 && line.back().unwrap().text.is_empty() {
25087 line.pop_back();
25088 }
25089
25090 lines.push(mem::take(&mut line));
25091 }
25092 }
25093 }
25094
25095 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
25096 return;
25097 };
25098 cx.write_to_clipboard(ClipboardItem::new_string(lines));
25099 }
25100
25101 pub fn open_context_menu(
25102 &mut self,
25103 _: &OpenContextMenu,
25104 window: &mut Window,
25105 cx: &mut Context<Self>,
25106 ) {
25107 self.request_autoscroll(Autoscroll::newest(), cx);
25108 let position = self
25109 .selections
25110 .newest_display(&self.display_snapshot(cx))
25111 .start;
25112 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
25113 }
25114
25115 pub fn replay_insert_event(
25116 &mut self,
25117 text: &str,
25118 relative_utf16_range: Option<Range<isize>>,
25119 window: &mut Window,
25120 cx: &mut Context<Self>,
25121 ) {
25122 if !self.input_enabled {
25123 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25124 return;
25125 }
25126 if let Some(relative_utf16_range) = relative_utf16_range {
25127 let selections = self
25128 .selections
25129 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25130 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25131 let new_ranges = selections.into_iter().map(|range| {
25132 let start = MultiBufferOffsetUtf16(OffsetUtf16(
25133 range
25134 .head()
25135 .0
25136 .0
25137 .saturating_add_signed(relative_utf16_range.start),
25138 ));
25139 let end = MultiBufferOffsetUtf16(OffsetUtf16(
25140 range
25141 .head()
25142 .0
25143 .0
25144 .saturating_add_signed(relative_utf16_range.end),
25145 ));
25146 start..end
25147 });
25148 s.select_ranges(new_ranges);
25149 });
25150 }
25151
25152 self.handle_input(text, window, cx);
25153 }
25154
25155 pub fn is_focused(&self, window: &Window) -> bool {
25156 self.focus_handle.is_focused(window)
25157 }
25158
25159 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25160 cx.emit(EditorEvent::Focused);
25161
25162 if let Some(descendant) = self
25163 .last_focused_descendant
25164 .take()
25165 .and_then(|descendant| descendant.upgrade())
25166 {
25167 window.focus(&descendant, cx);
25168 } else {
25169 if let Some(blame) = self.blame.as_ref() {
25170 blame.update(cx, GitBlame::focus)
25171 }
25172
25173 self.blink_manager.update(cx, BlinkManager::enable);
25174 self.show_cursor_names(window, cx);
25175 self.buffer.update(cx, |buffer, cx| {
25176 buffer.finalize_last_transaction(cx);
25177 if self.leader_id.is_none() {
25178 buffer.set_active_selections(
25179 &self.selections.disjoint_anchors_arc(),
25180 self.selections.line_mode(),
25181 self.cursor_shape,
25182 cx,
25183 );
25184 }
25185 });
25186
25187 if let Some(position_map) = self.last_position_map.clone()
25188 && !self.mouse_cursor_hidden
25189 {
25190 EditorElement::mouse_moved(
25191 self,
25192 &MouseMoveEvent {
25193 position: window.mouse_position(),
25194 pressed_button: None,
25195 modifiers: window.modifiers(),
25196 },
25197 &position_map,
25198 None,
25199 window,
25200 cx,
25201 );
25202 }
25203 }
25204 }
25205
25206 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25207 cx.emit(EditorEvent::FocusedIn)
25208 }
25209
25210 fn handle_focus_out(
25211 &mut self,
25212 event: FocusOutEvent,
25213 _window: &mut Window,
25214 cx: &mut Context<Self>,
25215 ) {
25216 if event.blurred != self.focus_handle {
25217 self.last_focused_descendant = Some(event.blurred);
25218 }
25219 self.selection_drag_state = SelectionDragState::None;
25220 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
25221 }
25222
25223 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25224 self.blink_manager.update(cx, BlinkManager::disable);
25225 self.buffer
25226 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
25227
25228 if let Some(blame) = self.blame.as_ref() {
25229 blame.update(cx, GitBlame::blur)
25230 }
25231 if !self.hover_state.focused(window, cx) {
25232 hide_hover(self, cx);
25233 }
25234 if !self
25235 .context_menu
25236 .borrow()
25237 .as_ref()
25238 .is_some_and(|context_menu| context_menu.focused(window, cx))
25239 {
25240 self.hide_context_menu(window, cx);
25241 }
25242 self.take_active_edit_prediction(true, cx);
25243 cx.emit(EditorEvent::Blurred);
25244 cx.notify();
25245 }
25246
25247 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
25248 let mut pending: String = window
25249 .pending_input_keystrokes()
25250 .into_iter()
25251 .flatten()
25252 .filter_map(|keystroke| keystroke.key_char.clone())
25253 .collect();
25254
25255 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
25256 pending = "".to_string();
25257 }
25258
25259 let existing_pending = self
25260 .text_highlights(HighlightKey::PendingInput, cx)
25261 .map(|(_, ranges)| ranges.to_vec());
25262 if existing_pending.is_none() && pending.is_empty() {
25263 return;
25264 }
25265 let transaction =
25266 self.transact(window, cx, |this, window, cx| {
25267 let selections = this
25268 .selections
25269 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
25270 let edits = selections
25271 .iter()
25272 .map(|selection| (selection.end..selection.end, pending.clone()));
25273 this.edit(edits, cx);
25274 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25275 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
25276 sel.start + ix * pending.len()..sel.end + ix * pending.len()
25277 }));
25278 });
25279 if let Some(existing_ranges) = existing_pending {
25280 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
25281 this.edit(edits, cx);
25282 }
25283 });
25284
25285 let snapshot = self.snapshot(window, cx);
25286 let ranges = self
25287 .selections
25288 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
25289 .into_iter()
25290 .map(|selection| {
25291 snapshot.buffer_snapshot().anchor_after(selection.end)
25292 ..snapshot
25293 .buffer_snapshot()
25294 .anchor_before(selection.end + pending.len())
25295 })
25296 .collect();
25297
25298 if pending.is_empty() {
25299 self.clear_highlights(HighlightKey::PendingInput, cx);
25300 } else {
25301 self.highlight_text(
25302 HighlightKey::PendingInput,
25303 ranges,
25304 HighlightStyle {
25305 underline: Some(UnderlineStyle {
25306 thickness: px(1.),
25307 color: None,
25308 wavy: false,
25309 }),
25310 ..Default::default()
25311 },
25312 cx,
25313 );
25314 }
25315
25316 self.ime_transaction = self.ime_transaction.or(transaction);
25317 if let Some(transaction) = self.ime_transaction {
25318 self.buffer.update(cx, |buffer, cx| {
25319 buffer.group_until_transaction(transaction, cx);
25320 });
25321 }
25322
25323 if self
25324 .text_highlights(HighlightKey::PendingInput, cx)
25325 .is_none()
25326 {
25327 self.ime_transaction.take();
25328 }
25329 }
25330
25331 pub fn register_action_renderer(
25332 &mut self,
25333 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
25334 ) -> Subscription {
25335 let id = self.next_editor_action_id.post_inc();
25336 self.editor_actions
25337 .borrow_mut()
25338 .insert(id, Box::new(listener));
25339
25340 let editor_actions = self.editor_actions.clone();
25341 Subscription::new(move || {
25342 editor_actions.borrow_mut().remove(&id);
25343 })
25344 }
25345
25346 pub fn register_action<A: Action>(
25347 &mut self,
25348 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
25349 ) -> Subscription {
25350 let id = self.next_editor_action_id.post_inc();
25351 let listener = Arc::new(listener);
25352 self.editor_actions.borrow_mut().insert(
25353 id,
25354 Box::new(move |_, window, _| {
25355 let listener = listener.clone();
25356 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
25357 let action = action.downcast_ref().unwrap();
25358 if phase == DispatchPhase::Bubble {
25359 listener(action, window, cx)
25360 }
25361 })
25362 }),
25363 );
25364
25365 let editor_actions = self.editor_actions.clone();
25366 Subscription::new(move || {
25367 editor_actions.borrow_mut().remove(&id);
25368 })
25369 }
25370
25371 pub fn file_header_size(&self) -> u32 {
25372 FILE_HEADER_HEIGHT
25373 }
25374
25375 pub fn restore(
25376 &mut self,
25377 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
25378 window: &mut Window,
25379 cx: &mut Context<Self>,
25380 ) {
25381 self.buffer().update(cx, |multi_buffer, cx| {
25382 for (buffer_id, changes) in revert_changes {
25383 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
25384 buffer.update(cx, |buffer, cx| {
25385 buffer.edit(
25386 changes
25387 .into_iter()
25388 .map(|(range, text)| (range, text.to_string())),
25389 None,
25390 cx,
25391 );
25392 });
25393 }
25394 }
25395 });
25396 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25397 selections.refresh()
25398 });
25399 }
25400
25401 pub fn to_pixel_point(
25402 &mut self,
25403 source: Anchor,
25404 editor_snapshot: &EditorSnapshot,
25405 window: &mut Window,
25406 cx: &mut App,
25407 ) -> Option<gpui::Point<Pixels>> {
25408 let source_point = source.to_display_point(editor_snapshot);
25409 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
25410 }
25411
25412 pub fn display_to_pixel_point(
25413 &mut self,
25414 source: DisplayPoint,
25415 editor_snapshot: &EditorSnapshot,
25416 window: &mut Window,
25417 cx: &mut App,
25418 ) -> Option<gpui::Point<Pixels>> {
25419 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
25420 let text_layout_details = self.text_layout_details(window, cx);
25421 let scroll_top = text_layout_details
25422 .scroll_anchor
25423 .scroll_position(editor_snapshot)
25424 .y;
25425
25426 if source.row().as_f64() < scroll_top.floor() {
25427 return None;
25428 }
25429 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
25430 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
25431 Some(gpui::Point::new(source_x, source_y))
25432 }
25433
25434 pub fn has_visible_completions_menu(&self) -> bool {
25435 !self.edit_prediction_preview_is_active()
25436 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
25437 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
25438 })
25439 }
25440
25441 pub fn register_addon<T: Addon>(&mut self, instance: T) {
25442 if self.mode.is_minimap() {
25443 return;
25444 }
25445 self.addons
25446 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
25447 }
25448
25449 pub fn unregister_addon<T: Addon>(&mut self) {
25450 self.addons.remove(&std::any::TypeId::of::<T>());
25451 }
25452
25453 pub fn addon<T: Addon>(&self) -> Option<&T> {
25454 let type_id = std::any::TypeId::of::<T>();
25455 self.addons
25456 .get(&type_id)
25457 .and_then(|item| item.to_any().downcast_ref::<T>())
25458 }
25459
25460 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
25461 let type_id = std::any::TypeId::of::<T>();
25462 self.addons
25463 .get_mut(&type_id)
25464 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
25465 }
25466
25467 fn character_dimensions(&self, window: &mut Window, cx: &mut App) -> CharacterDimensions {
25468 let text_layout_details = self.text_layout_details(window, cx);
25469 let style = &text_layout_details.editor_style;
25470 let font_id = window.text_system().resolve_font(&style.text.font());
25471 let font_size = style.text.font_size.to_pixels(window.rem_size());
25472 let line_height = style.text.line_height_in_pixels(window.rem_size());
25473 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
25474 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
25475
25476 CharacterDimensions {
25477 em_width,
25478 em_advance,
25479 line_height,
25480 }
25481 }
25482
25483 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
25484 self.load_diff_task.clone()
25485 }
25486
25487 fn read_metadata_from_db(
25488 &mut self,
25489 item_id: u64,
25490 workspace_id: WorkspaceId,
25491 window: &mut Window,
25492 cx: &mut Context<Editor>,
25493 ) {
25494 if self.buffer_kind(cx) == ItemBufferKind::Singleton
25495 && !self.mode.is_minimap()
25496 && WorkspaceSettings::get(None, cx).restore_on_startup
25497 != RestoreOnStartupBehavior::EmptyTab
25498 {
25499 let buffer_snapshot = OnceCell::new();
25500
25501 // Get file path for path-based fold lookup
25502 let file_path: Option<Arc<Path>> =
25503 self.buffer().read(cx).as_singleton().and_then(|buffer| {
25504 project::File::from_dyn(buffer.read(cx).file())
25505 .map(|file| Arc::from(file.abs_path(cx)))
25506 });
25507
25508 // Try file_folds (path-based) first, fallback to editor_folds (migration)
25509 let db = EditorDb::global(cx);
25510 let (folds, needs_migration) = if let Some(ref path) = file_path {
25511 if let Some(folds) = db.get_file_folds(workspace_id, path).log_err()
25512 && !folds.is_empty()
25513 {
25514 (Some(folds), false)
25515 } else if let Some(folds) = db.get_editor_folds(item_id, workspace_id).log_err()
25516 && !folds.is_empty()
25517 {
25518 // Found old editor_folds data, will migrate to file_folds
25519 (Some(folds), true)
25520 } else {
25521 (None, false)
25522 }
25523 } else {
25524 // No file path, try editor_folds as fallback
25525 let folds = db.get_editor_folds(item_id, workspace_id).log_err();
25526 (folds.filter(|f| !f.is_empty()), false)
25527 };
25528
25529 if let Some(folds) = folds {
25530 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25531 let snapshot_len = snapshot.len().0;
25532
25533 // Helper: search for fingerprint in buffer, return offset if found
25534 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25535 // Ensure we start at a character boundary (defensive)
25536 let search_start = snapshot
25537 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25538 .0;
25539 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25540
25541 let mut byte_offset = search_start;
25542 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25543 if byte_offset > search_end {
25544 break;
25545 }
25546 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25547 return Some(byte_offset);
25548 }
25549 byte_offset += ch.len_utf8();
25550 }
25551 None
25552 };
25553
25554 // Track search position to handle duplicate fingerprints correctly.
25555 // Folds are stored in document order, so we advance after each match.
25556 let mut search_start = 0usize;
25557
25558 // Collect db_folds for migration (only folds with valid fingerprints)
25559 let mut db_folds_for_migration: Vec<(usize, usize, String, String)> = Vec::new();
25560
25561 let valid_folds: Vec<_> = folds
25562 .into_iter()
25563 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25564 // Skip folds without fingerprints (old data before migration)
25565 let sfp = start_fp?;
25566 let efp = end_fp?;
25567 let efp_len = efp.len();
25568
25569 // Fast path: check if fingerprints match at stored offsets
25570 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
25571 let start_matches = stored_start < snapshot_len
25572 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25573 let efp_check_pos = stored_end.saturating_sub(efp_len);
25574 let end_matches = efp_check_pos >= stored_start
25575 && stored_end <= snapshot_len
25576 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25577
25578 let (new_start, new_end) = if start_matches && end_matches {
25579 // Offsets unchanged, use stored values
25580 (stored_start, stored_end)
25581 } else if sfp == efp {
25582 // Short fold: identical fingerprints can only match once per search
25583 // Use stored fold length to compute new_end
25584 let new_start = find_fingerprint(&sfp, search_start)?;
25585 let fold_len = stored_end - stored_start;
25586 let new_end = new_start + fold_len;
25587 (new_start, new_end)
25588 } else {
25589 // Slow path: search for fingerprints in buffer
25590 let new_start = find_fingerprint(&sfp, search_start)?;
25591 // Search for end_fp after start, then add efp_len to get actual fold end
25592 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25593 let new_end = efp_pos + efp_len;
25594 (new_start, new_end)
25595 };
25596
25597 // Advance search position for next fold
25598 search_start = new_end;
25599
25600 // Validate fold makes sense (end must be after start)
25601 if new_end <= new_start {
25602 return None;
25603 }
25604
25605 // Collect for migration if needed
25606 if needs_migration {
25607 db_folds_for_migration.push((new_start, new_end, sfp, efp));
25608 }
25609
25610 Some(
25611 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25612 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25613 )
25614 })
25615 .collect();
25616
25617 if !valid_folds.is_empty() {
25618 self.fold_ranges(valid_folds, false, window, cx);
25619
25620 // Migrate from editor_folds to file_folds if we loaded from old table
25621 if needs_migration {
25622 if let Some(ref path) = file_path {
25623 let path = path.clone();
25624 let db = EditorDb::global(cx);
25625 cx.spawn(async move |_, _| {
25626 db.save_file_folds(workspace_id, path, db_folds_for_migration)
25627 .await
25628 .log_err();
25629 })
25630 .detach();
25631 }
25632 }
25633 }
25634 }
25635
25636 if let Some(selections) = db.get_editor_selections(item_id, workspace_id).log_err()
25637 && !selections.is_empty()
25638 {
25639 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
25640 // skip adding the initial selection to selection history
25641 self.selection_history.mode = SelectionHistoryMode::Skipping;
25642 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25643 s.select_ranges(selections.into_iter().map(|(start, end)| {
25644 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
25645 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
25646 }));
25647 });
25648 self.selection_history.mode = SelectionHistoryMode::Normal;
25649 };
25650 }
25651
25652 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
25653 }
25654
25655 /// Load folds from the file_folds database table by file path.
25656 /// Used when manually opening a file that was previously closed.
25657 fn load_folds_from_db(
25658 &mut self,
25659 workspace_id: WorkspaceId,
25660 file_path: PathBuf,
25661 window: &mut Window,
25662 cx: &mut Context<Editor>,
25663 ) {
25664 if self.mode.is_minimap()
25665 || WorkspaceSettings::get(None, cx).restore_on_startup
25666 == RestoreOnStartupBehavior::EmptyTab
25667 {
25668 return;
25669 }
25670
25671 let Some(folds) = EditorDb::global(cx)
25672 .get_file_folds(workspace_id, &file_path)
25673 .log_err()
25674 else {
25675 return;
25676 };
25677 if folds.is_empty() {
25678 return;
25679 }
25680
25681 let snapshot = self.buffer.read(cx).snapshot(cx);
25682 let snapshot_len = snapshot.len().0;
25683
25684 // Helper: search for fingerprint in buffer, return offset if found
25685 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
25686 let search_start = snapshot
25687 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
25688 .0;
25689 let search_end = snapshot_len.saturating_sub(fingerprint.len());
25690
25691 let mut byte_offset = search_start;
25692 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
25693 if byte_offset > search_end {
25694 break;
25695 }
25696 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
25697 return Some(byte_offset);
25698 }
25699 byte_offset += ch.len_utf8();
25700 }
25701 None
25702 };
25703
25704 let mut search_start = 0usize;
25705
25706 let valid_folds: Vec<_> = folds
25707 .into_iter()
25708 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
25709 let sfp = start_fp?;
25710 let efp = end_fp?;
25711 let efp_len = efp.len();
25712
25713 let start_matches = stored_start < snapshot_len
25714 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
25715 let efp_check_pos = stored_end.saturating_sub(efp_len);
25716 let end_matches = efp_check_pos >= stored_start
25717 && stored_end <= snapshot_len
25718 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
25719
25720 let (new_start, new_end) = if start_matches && end_matches {
25721 (stored_start, stored_end)
25722 } else if sfp == efp {
25723 let new_start = find_fingerprint(&sfp, search_start)?;
25724 let fold_len = stored_end - stored_start;
25725 let new_end = new_start + fold_len;
25726 (new_start, new_end)
25727 } else {
25728 let new_start = find_fingerprint(&sfp, search_start)?;
25729 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
25730 let new_end = efp_pos + efp_len;
25731 (new_start, new_end)
25732 };
25733
25734 search_start = new_end;
25735
25736 if new_end <= new_start {
25737 return None;
25738 }
25739
25740 Some(
25741 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
25742 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
25743 )
25744 })
25745 .collect();
25746
25747 if !valid_folds.is_empty() {
25748 self.fold_ranges(valid_folds, false, window, cx);
25749 }
25750 }
25751
25752 fn lsp_data_enabled(&self) -> bool {
25753 self.enable_lsp_data && self.mode().is_full()
25754 }
25755
25756 fn update_lsp_data(
25757 &mut self,
25758 for_buffer: Option<BufferId>,
25759 window: &mut Window,
25760 cx: &mut Context<'_, Self>,
25761 ) {
25762 if !self.lsp_data_enabled() {
25763 return;
25764 }
25765
25766 if let Some(buffer_id) = for_buffer {
25767 self.pull_diagnostics(buffer_id, window, cx);
25768 }
25769 self.refresh_semantic_tokens(for_buffer, None, cx);
25770 self.refresh_document_colors(for_buffer, window, cx);
25771 self.refresh_folding_ranges(for_buffer, window, cx);
25772 self.refresh_document_symbols(for_buffer, cx);
25773 }
25774
25775 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
25776 if !self.lsp_data_enabled() {
25777 return;
25778 }
25779 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
25780 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
25781 }
25782 }
25783
25784 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
25785 if !self.lsp_data_enabled() {
25786 return;
25787 }
25788
25789 if !self.registered_buffers.contains_key(&buffer_id)
25790 && let Some(project) = self.project.as_ref()
25791 {
25792 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
25793 project.update(cx, |project, cx| {
25794 self.registered_buffers.insert(
25795 buffer_id,
25796 project.register_buffer_with_language_servers(&buffer, cx),
25797 );
25798 });
25799 } else {
25800 self.registered_buffers.remove(&buffer_id);
25801 }
25802 }
25803 }
25804
25805 fn create_style(&self, cx: &App) -> EditorStyle {
25806 let settings = ThemeSettings::get_global(cx);
25807
25808 let mut text_style = match self.mode {
25809 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
25810 color: cx.theme().colors().editor_foreground,
25811 font_family: settings.ui_font.family.clone(),
25812 font_features: settings.ui_font.features.clone(),
25813 font_fallbacks: settings.ui_font.fallbacks.clone(),
25814 font_size: rems(0.875).into(),
25815 font_weight: settings.ui_font.weight,
25816 line_height: relative(settings.buffer_line_height.value()),
25817 ..Default::default()
25818 },
25819 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
25820 color: cx.theme().colors().editor_foreground,
25821 font_family: settings.buffer_font.family.clone(),
25822 font_features: settings.buffer_font.features.clone(),
25823 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25824 font_size: settings.buffer_font_size(cx).into(),
25825 font_weight: settings.buffer_font.weight,
25826 line_height: relative(settings.buffer_line_height.value()),
25827 ..Default::default()
25828 },
25829 };
25830 if let Some(text_style_refinement) = &self.text_style_refinement {
25831 text_style.refine(text_style_refinement)
25832 }
25833
25834 let background = match self.mode {
25835 EditorMode::SingleLine => cx.theme().system().transparent,
25836 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
25837 EditorMode::Full { .. } => cx.theme().colors().editor_background,
25838 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
25839 };
25840
25841 EditorStyle {
25842 background,
25843 border: cx.theme().colors().border,
25844 local_player: cx.theme().players().local(),
25845 text: text_style,
25846 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
25847 syntax: cx.theme().syntax().clone(),
25848 status: cx.theme().status().clone(),
25849 inlay_hints_style: make_inlay_hints_style(cx),
25850 edit_prediction_styles: make_suggestion_styles(cx),
25851 unnecessary_code_fade: settings.unnecessary_code_fade,
25852 show_underlines: self.diagnostics_enabled(),
25853 }
25854 }
25855
25856 fn breadcrumbs_inner(&self, cx: &App) -> Option<Vec<HighlightedText>> {
25857 let multibuffer = self.buffer().read(cx);
25858 let is_singleton = multibuffer.is_singleton();
25859 let (buffer_id, symbols) = self.outline_symbols_at_cursor.as_ref()?;
25860 let buffer = multibuffer.buffer(*buffer_id)?;
25861
25862 let buffer = buffer.read(cx);
25863 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
25864 let mut breadcrumbs = if is_singleton {
25865 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
25866 buffer
25867 .snapshot()
25868 .resolve_file_path(
25869 self.project
25870 .as_ref()
25871 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
25872 .unwrap_or_default(),
25873 cx,
25874 )
25875 .unwrap_or_else(|| {
25876 if multibuffer.is_singleton() {
25877 multibuffer.title(cx).to_string()
25878 } else {
25879 "untitled".to_string()
25880 }
25881 })
25882 });
25883 vec![HighlightedText {
25884 text: text.into(),
25885 highlights: vec![],
25886 }]
25887 } else {
25888 vec![]
25889 };
25890
25891 breadcrumbs.extend(symbols.iter().map(|symbol| HighlightedText {
25892 text: symbol.text.clone().into(),
25893 highlights: symbol.highlight_ranges.clone(),
25894 }));
25895 Some(breadcrumbs)
25896 }
25897
25898 fn disable_lsp_data(&mut self) {
25899 self.enable_lsp_data = false;
25900 }
25901
25902 fn disable_runnables(&mut self) {
25903 self.enable_runnables = false;
25904 }
25905
25906 fn update_data_on_scroll(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) {
25907 self.register_visible_buffers(cx);
25908 self.colorize_brackets(false, cx);
25909 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
25910 if !self.buffer().read(cx).is_singleton() {
25911 self.update_lsp_data(None, window, cx);
25912 self.refresh_runnables(None, window, cx);
25913 }
25914 }
25915}
25916
25917fn edit_for_markdown_paste<'a>(
25918 buffer: &MultiBufferSnapshot,
25919 range: Range<MultiBufferOffset>,
25920 to_insert: &'a str,
25921 url: Option<url::Url>,
25922) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
25923 if url.is_none() {
25924 return (range, Cow::Borrowed(to_insert));
25925 };
25926
25927 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
25928
25929 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
25930 Cow::Borrowed(to_insert)
25931 } else {
25932 Cow::Owned(format!("[{old_text}]({to_insert})"))
25933 };
25934 (range, new_text)
25935}
25936
25937fn process_completion_for_edit(
25938 completion: &Completion,
25939 intent: CompletionIntent,
25940 buffer: &Entity<Buffer>,
25941 cursor_position: &text::Anchor,
25942 cx: &mut Context<Editor>,
25943) -> CompletionEdit {
25944 let buffer = buffer.read(cx);
25945 let buffer_snapshot = buffer.snapshot();
25946 let (snippet, new_text) = if completion.is_snippet() {
25947 let mut snippet_source = completion.new_text.clone();
25948 // Workaround for typescript language server issues so that methods don't expand within
25949 // strings and functions with type expressions. The previous point is used because the query
25950 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
25951 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
25952 let previous_point = if previous_point.column > 0 {
25953 cursor_position.to_previous_offset(&buffer_snapshot)
25954 } else {
25955 cursor_position.to_offset(&buffer_snapshot)
25956 };
25957 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
25958 && scope.prefers_label_for_snippet_in_completion()
25959 && let Some(label) = completion.label()
25960 && matches!(
25961 completion.kind(),
25962 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
25963 )
25964 {
25965 snippet_source = label;
25966 }
25967 match Snippet::parse(&snippet_source).log_err() {
25968 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
25969 None => (None, completion.new_text.clone()),
25970 }
25971 } else {
25972 (None, completion.new_text.clone())
25973 };
25974
25975 let mut range_to_replace = {
25976 let replace_range = &completion.replace_range;
25977 if let CompletionSource::Lsp {
25978 insert_range: Some(insert_range),
25979 ..
25980 } = &completion.source
25981 {
25982 debug_assert_eq!(
25983 insert_range.start, replace_range.start,
25984 "insert_range and replace_range should start at the same position"
25985 );
25986 debug_assert!(
25987 insert_range
25988 .start
25989 .cmp(cursor_position, &buffer_snapshot)
25990 .is_le(),
25991 "insert_range should start before or at cursor position"
25992 );
25993 debug_assert!(
25994 replace_range
25995 .start
25996 .cmp(cursor_position, &buffer_snapshot)
25997 .is_le(),
25998 "replace_range should start before or at cursor position"
25999 );
26000
26001 let should_replace = match intent {
26002 CompletionIntent::CompleteWithInsert => false,
26003 CompletionIntent::CompleteWithReplace => true,
26004 CompletionIntent::Complete | CompletionIntent::Compose => {
26005 let insert_mode = LanguageSettings::for_buffer(&buffer, cx)
26006 .completions
26007 .lsp_insert_mode;
26008 match insert_mode {
26009 LspInsertMode::Insert => false,
26010 LspInsertMode::Replace => true,
26011 LspInsertMode::ReplaceSubsequence => {
26012 let mut text_to_replace = buffer.chars_for_range(
26013 buffer.anchor_before(replace_range.start)
26014 ..buffer.anchor_after(replace_range.end),
26015 );
26016 let mut current_needle = text_to_replace.next();
26017 for haystack_ch in completion.label.text.chars() {
26018 if let Some(needle_ch) = current_needle
26019 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
26020 {
26021 current_needle = text_to_replace.next();
26022 }
26023 }
26024 current_needle.is_none()
26025 }
26026 LspInsertMode::ReplaceSuffix => {
26027 if replace_range
26028 .end
26029 .cmp(cursor_position, &buffer_snapshot)
26030 .is_gt()
26031 {
26032 let range_after_cursor = *cursor_position..replace_range.end;
26033 let text_after_cursor = buffer
26034 .text_for_range(
26035 buffer.anchor_before(range_after_cursor.start)
26036 ..buffer.anchor_after(range_after_cursor.end),
26037 )
26038 .collect::<String>()
26039 .to_ascii_lowercase();
26040 completion
26041 .label
26042 .text
26043 .to_ascii_lowercase()
26044 .ends_with(&text_after_cursor)
26045 } else {
26046 true
26047 }
26048 }
26049 }
26050 }
26051 };
26052
26053 if should_replace {
26054 replace_range.clone()
26055 } else {
26056 insert_range.clone()
26057 }
26058 } else {
26059 replace_range.clone()
26060 }
26061 };
26062
26063 if range_to_replace
26064 .end
26065 .cmp(cursor_position, &buffer_snapshot)
26066 .is_lt()
26067 {
26068 range_to_replace.end = *cursor_position;
26069 }
26070
26071 let replace_range = range_to_replace.to_offset(buffer);
26072 CompletionEdit {
26073 new_text,
26074 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
26075 snippet,
26076 }
26077}
26078
26079struct CompletionEdit {
26080 new_text: String,
26081 replace_range: Range<BufferOffset>,
26082 snippet: Option<Snippet>,
26083}
26084
26085fn comment_delimiter_for_newline(
26086 start_point: &Point,
26087 buffer: &MultiBufferSnapshot,
26088 language: &LanguageScope,
26089) -> Option<Arc<str>> {
26090 let delimiters = language.line_comment_prefixes();
26091 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
26092 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26093
26094 let num_of_whitespaces = snapshot
26095 .chars_for_range(range.clone())
26096 .take_while(|c| c.is_whitespace())
26097 .count();
26098 let comment_candidate = snapshot
26099 .chars_for_range(range.clone())
26100 .skip(num_of_whitespaces)
26101 .take(max_len_of_delimiter + 2)
26102 .collect::<String>();
26103 let (delimiter, trimmed_len, is_repl) = delimiters
26104 .iter()
26105 .filter_map(|delimiter| {
26106 let prefix = delimiter.trim_end();
26107 if comment_candidate.starts_with(prefix) {
26108 let is_repl = if let Some(stripped_comment) = comment_candidate.strip_prefix(prefix)
26109 {
26110 stripped_comment.starts_with(" %%")
26111 } else {
26112 false
26113 };
26114 Some((delimiter, prefix.len(), is_repl))
26115 } else {
26116 None
26117 }
26118 })
26119 .max_by_key(|(_, len, _)| *len)?;
26120
26121 if let Some(BlockCommentConfig {
26122 start: block_start, ..
26123 }) = language.block_comment()
26124 {
26125 let block_start_trimmed = block_start.trim_end();
26126 if block_start_trimmed.starts_with(delimiter.trim_end()) {
26127 let line_content = snapshot
26128 .chars_for_range(range.clone())
26129 .skip(num_of_whitespaces)
26130 .take(block_start_trimmed.len())
26131 .collect::<String>();
26132
26133 if line_content.starts_with(block_start_trimmed) {
26134 return None;
26135 }
26136 }
26137 }
26138
26139 let cursor_is_placed_after_comment_marker =
26140 num_of_whitespaces + trimmed_len <= start_point.column as usize;
26141 if cursor_is_placed_after_comment_marker {
26142 if !is_repl {
26143 return Some(delimiter.clone());
26144 }
26145
26146 let line_content_after_cursor: String = snapshot
26147 .chars_for_range(range)
26148 .skip(start_point.column as usize)
26149 .collect();
26150
26151 if line_content_after_cursor.trim().is_empty() {
26152 return None;
26153 } else {
26154 return Some(delimiter.clone());
26155 }
26156 } else {
26157 None
26158 }
26159}
26160
26161fn documentation_delimiter_for_newline(
26162 start_point: &Point,
26163 buffer: &MultiBufferSnapshot,
26164 language: &LanguageScope,
26165 newline_config: &mut NewlineConfig,
26166) -> Option<Arc<str>> {
26167 let BlockCommentConfig {
26168 start: start_tag,
26169 end: end_tag,
26170 prefix: delimiter,
26171 tab_size: len,
26172 } = language.documentation_comment()?;
26173 let is_within_block_comment = buffer
26174 .language_scope_at(*start_point)
26175 .is_some_and(|scope| scope.override_name() == Some("comment"));
26176 if !is_within_block_comment {
26177 return None;
26178 }
26179
26180 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26181
26182 let num_of_whitespaces = snapshot
26183 .chars_for_range(range.clone())
26184 .take_while(|c| c.is_whitespace())
26185 .count();
26186
26187 // 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.
26188 let column = start_point.column;
26189 let cursor_is_after_start_tag = {
26190 let start_tag_len = start_tag.len();
26191 let start_tag_line = snapshot
26192 .chars_for_range(range.clone())
26193 .skip(num_of_whitespaces)
26194 .take(start_tag_len)
26195 .collect::<String>();
26196 if start_tag_line.starts_with(start_tag.as_ref()) {
26197 num_of_whitespaces + start_tag_len <= column as usize
26198 } else {
26199 false
26200 }
26201 };
26202
26203 let cursor_is_after_delimiter = {
26204 let delimiter_trim = delimiter.trim_end();
26205 let delimiter_line = snapshot
26206 .chars_for_range(range.clone())
26207 .skip(num_of_whitespaces)
26208 .take(delimiter_trim.len())
26209 .collect::<String>();
26210 if delimiter_line.starts_with(delimiter_trim) {
26211 num_of_whitespaces + delimiter_trim.len() <= column as usize
26212 } else {
26213 false
26214 }
26215 };
26216
26217 let mut needs_extra_line = false;
26218 let mut extra_line_additional_indent = IndentSize::spaces(0);
26219
26220 let cursor_is_before_end_tag_if_exists = {
26221 let mut char_position = 0u32;
26222 let mut end_tag_offset = None;
26223
26224 'outer: for chunk in snapshot.text_for_range(range) {
26225 if let Some(byte_pos) = chunk.find(&**end_tag) {
26226 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
26227 end_tag_offset = Some(char_position + chars_before_match);
26228 break 'outer;
26229 }
26230 char_position += chunk.chars().count() as u32;
26231 }
26232
26233 if let Some(end_tag_offset) = end_tag_offset {
26234 let cursor_is_before_end_tag = column <= end_tag_offset;
26235 if cursor_is_after_start_tag {
26236 if cursor_is_before_end_tag {
26237 needs_extra_line = true;
26238 }
26239 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
26240 if cursor_is_at_start_of_end_tag {
26241 extra_line_additional_indent.len = *len;
26242 }
26243 }
26244 cursor_is_before_end_tag
26245 } else {
26246 true
26247 }
26248 };
26249
26250 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
26251 && cursor_is_before_end_tag_if_exists
26252 {
26253 let additional_indent = if cursor_is_after_start_tag {
26254 IndentSize::spaces(*len)
26255 } else {
26256 IndentSize::spaces(0)
26257 };
26258
26259 *newline_config = NewlineConfig::Newline {
26260 additional_indent,
26261 extra_line_additional_indent: if needs_extra_line {
26262 Some(extra_line_additional_indent)
26263 } else {
26264 None
26265 },
26266 prevent_auto_indent: true,
26267 };
26268 Some(delimiter.clone())
26269 } else {
26270 None
26271 }
26272}
26273
26274const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
26275
26276fn list_delimiter_for_newline(
26277 start_point: &Point,
26278 buffer: &MultiBufferSnapshot,
26279 language: &LanguageScope,
26280 newline_config: &mut NewlineConfig,
26281) -> Option<Arc<str>> {
26282 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
26283
26284 let num_of_whitespaces = snapshot
26285 .chars_for_range(range.clone())
26286 .take_while(|c| c.is_whitespace())
26287 .count();
26288
26289 let task_list_entries: Vec<_> = language
26290 .task_list()
26291 .into_iter()
26292 .flat_map(|config| {
26293 config
26294 .prefixes
26295 .iter()
26296 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
26297 })
26298 .collect();
26299 let unordered_list_entries: Vec<_> = language
26300 .unordered_list()
26301 .iter()
26302 .map(|marker| (marker.as_ref(), marker.as_ref()))
26303 .collect();
26304
26305 let all_entries: Vec<_> = task_list_entries
26306 .into_iter()
26307 .chain(unordered_list_entries)
26308 .collect();
26309
26310 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
26311 let candidate: String = snapshot
26312 .chars_for_range(range.clone())
26313 .skip(num_of_whitespaces)
26314 .take(max_prefix_len)
26315 .collect();
26316
26317 if let Some((prefix, continuation)) = all_entries
26318 .iter()
26319 .filter(|(prefix, _)| candidate.starts_with(*prefix))
26320 .max_by_key(|(prefix, _)| prefix.len())
26321 {
26322 let end_of_prefix = num_of_whitespaces + prefix.len();
26323 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26324 let has_content_after_marker = snapshot
26325 .chars_for_range(range)
26326 .skip(end_of_prefix)
26327 .any(|c| !c.is_whitespace());
26328
26329 if has_content_after_marker && cursor_is_after_prefix {
26330 return Some((*continuation).into());
26331 }
26332
26333 if start_point.column as usize == end_of_prefix {
26334 if num_of_whitespaces == 0 {
26335 *newline_config = NewlineConfig::ClearCurrentLine;
26336 } else {
26337 *newline_config = NewlineConfig::UnindentCurrentLine {
26338 continuation: (*continuation).into(),
26339 };
26340 }
26341 }
26342
26343 return None;
26344 }
26345 }
26346
26347 let candidate: String = snapshot
26348 .chars_for_range(range.clone())
26349 .skip(num_of_whitespaces)
26350 .take(ORDERED_LIST_MAX_MARKER_LEN)
26351 .collect();
26352
26353 for ordered_config in language.ordered_list() {
26354 let regex = match Regex::new(&ordered_config.pattern) {
26355 Ok(r) => r,
26356 Err(_) => continue,
26357 };
26358
26359 if let Some(captures) = regex.captures(&candidate) {
26360 let full_match = captures.get(0)?;
26361 let marker_len = full_match.len();
26362 let end_of_prefix = num_of_whitespaces + marker_len;
26363 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
26364
26365 let has_content_after_marker = snapshot
26366 .chars_for_range(range)
26367 .skip(end_of_prefix)
26368 .any(|c| !c.is_whitespace());
26369
26370 if has_content_after_marker && cursor_is_after_prefix {
26371 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
26372 let continuation = ordered_config
26373 .format
26374 .replace("{1}", &(number + 1).to_string());
26375 return Some(continuation.into());
26376 }
26377
26378 if start_point.column as usize == end_of_prefix {
26379 let continuation = ordered_config.format.replace("{1}", "1");
26380 if num_of_whitespaces == 0 {
26381 *newline_config = NewlineConfig::ClearCurrentLine;
26382 } else {
26383 *newline_config = NewlineConfig::UnindentCurrentLine {
26384 continuation: continuation.into(),
26385 };
26386 }
26387 }
26388
26389 return None;
26390 }
26391 }
26392
26393 None
26394}
26395
26396fn is_list_prefix_row(
26397 row: MultiBufferRow,
26398 buffer: &MultiBufferSnapshot,
26399 language: &LanguageScope,
26400) -> bool {
26401 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
26402 return false;
26403 };
26404
26405 let num_of_whitespaces = snapshot
26406 .chars_for_range(range.clone())
26407 .take_while(|c| c.is_whitespace())
26408 .count();
26409
26410 let task_list_prefixes: Vec<_> = language
26411 .task_list()
26412 .into_iter()
26413 .flat_map(|config| {
26414 config
26415 .prefixes
26416 .iter()
26417 .map(|p| p.as_ref())
26418 .collect::<Vec<_>>()
26419 })
26420 .collect();
26421 let unordered_list_markers: Vec<_> = language
26422 .unordered_list()
26423 .iter()
26424 .map(|marker| marker.as_ref())
26425 .collect();
26426 let all_prefixes: Vec<_> = task_list_prefixes
26427 .into_iter()
26428 .chain(unordered_list_markers)
26429 .collect();
26430 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
26431 let candidate: String = snapshot
26432 .chars_for_range(range.clone())
26433 .skip(num_of_whitespaces)
26434 .take(max_prefix_len)
26435 .collect();
26436 if all_prefixes
26437 .iter()
26438 .any(|prefix| candidate.starts_with(*prefix))
26439 {
26440 return true;
26441 }
26442 }
26443
26444 let ordered_list_candidate: String = snapshot
26445 .chars_for_range(range)
26446 .skip(num_of_whitespaces)
26447 .take(ORDERED_LIST_MAX_MARKER_LEN)
26448 .collect();
26449 for ordered_config in language.ordered_list() {
26450 let regex = match Regex::new(&ordered_config.pattern) {
26451 Ok(r) => r,
26452 Err(_) => continue,
26453 };
26454 if let Some(captures) = regex.captures(&ordered_list_candidate) {
26455 return captures.get(0).is_some();
26456 }
26457 }
26458
26459 false
26460}
26461
26462#[derive(Debug)]
26463enum NewlineConfig {
26464 /// Insert newline with optional additional indent and optional extra blank line
26465 Newline {
26466 additional_indent: IndentSize,
26467 extra_line_additional_indent: Option<IndentSize>,
26468 prevent_auto_indent: bool,
26469 },
26470 /// Clear the current line
26471 ClearCurrentLine,
26472 /// Unindent the current line and add continuation
26473 UnindentCurrentLine { continuation: Arc<str> },
26474}
26475
26476impl NewlineConfig {
26477 fn has_extra_line(&self) -> bool {
26478 matches!(
26479 self,
26480 Self::Newline {
26481 extra_line_additional_indent: Some(_),
26482 ..
26483 }
26484 )
26485 }
26486
26487 fn insert_extra_newline_brackets(
26488 buffer: &MultiBufferSnapshot,
26489 range: Range<MultiBufferOffset>,
26490 language: &language::LanguageScope,
26491 ) -> bool {
26492 let leading_whitespace_len = buffer
26493 .reversed_chars_at(range.start)
26494 .take_while(|c| c.is_whitespace() && *c != '\n')
26495 .map(|c| c.len_utf8())
26496 .sum::<usize>();
26497 let trailing_whitespace_len = buffer
26498 .chars_at(range.end)
26499 .take_while(|c| c.is_whitespace() && *c != '\n')
26500 .map(|c| c.len_utf8())
26501 .sum::<usize>();
26502 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
26503
26504 language.brackets().any(|(pair, enabled)| {
26505 let pair_start = pair.start.trim_end();
26506 let pair_end = pair.end.trim_start();
26507
26508 enabled
26509 && pair.newline
26510 && buffer.contains_str_at(range.end, pair_end)
26511 && buffer.contains_str_at(
26512 range.start.saturating_sub_usize(pair_start.len()),
26513 pair_start,
26514 )
26515 })
26516 }
26517
26518 fn insert_extra_newline_tree_sitter(
26519 buffer: &MultiBufferSnapshot,
26520 range: Range<MultiBufferOffset>,
26521 ) -> bool {
26522 let (buffer, range) = match buffer
26523 .range_to_buffer_ranges(range.start..=range.end)
26524 .as_slice()
26525 {
26526 [(buffer, range, _)] => (*buffer, range.clone()),
26527 _ => return false,
26528 };
26529 let pair = {
26530 let mut result: Option<BracketMatch<usize>> = None;
26531
26532 for pair in buffer
26533 .all_bracket_ranges(range.start.0..range.end.0)
26534 .filter(move |pair| {
26535 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
26536 })
26537 {
26538 let len = pair.close_range.end - pair.open_range.start;
26539
26540 if let Some(existing) = &result {
26541 let existing_len = existing.close_range.end - existing.open_range.start;
26542 if len > existing_len {
26543 continue;
26544 }
26545 }
26546
26547 result = Some(pair);
26548 }
26549
26550 result
26551 };
26552 let Some(pair) = pair else {
26553 return false;
26554 };
26555 pair.newline_only
26556 && buffer
26557 .chars_for_range(pair.open_range.end..range.start.0)
26558 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
26559 .all(|c| c.is_whitespace() && c != '\n')
26560 }
26561}
26562
26563fn update_uncommitted_diff_for_buffer(
26564 editor: Entity<Editor>,
26565 project: &Entity<Project>,
26566 buffers: impl IntoIterator<Item = Entity<Buffer>>,
26567 buffer: Entity<MultiBuffer>,
26568 cx: &mut App,
26569) -> Task<()> {
26570 let mut tasks = Vec::new();
26571 project.update(cx, |project, cx| {
26572 for buffer in buffers {
26573 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
26574 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
26575 }
26576 }
26577 });
26578 cx.spawn(async move |cx| {
26579 let diffs = future::join_all(tasks).await;
26580 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
26581 return;
26582 }
26583
26584 buffer.update(cx, |buffer, cx| {
26585 for diff in diffs.into_iter().flatten() {
26586 buffer.add_diff(diff, cx);
26587 }
26588 });
26589 })
26590}
26591
26592fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
26593 let tab_size = tab_size.get() as usize;
26594 let mut width = offset;
26595
26596 for ch in text.chars() {
26597 width += if ch == '\t' {
26598 tab_size - (width % tab_size)
26599 } else {
26600 1
26601 };
26602 }
26603
26604 width - offset
26605}
26606
26607#[cfg(test)]
26608mod tests {
26609 use super::*;
26610
26611 #[test]
26612 fn test_string_size_with_expanded_tabs() {
26613 let nz = |val| NonZeroU32::new(val).unwrap();
26614 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
26615 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
26616 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
26617 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
26618 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
26619 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
26620 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
26621 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
26622 }
26623}
26624
26625/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
26626struct WordBreakingTokenizer<'a> {
26627 input: &'a str,
26628}
26629
26630impl<'a> WordBreakingTokenizer<'a> {
26631 fn new(input: &'a str) -> Self {
26632 Self { input }
26633 }
26634}
26635
26636fn is_char_ideographic(ch: char) -> bool {
26637 use unicode_script::Script::*;
26638 use unicode_script::UnicodeScript;
26639 matches!(ch.script(), Han | Tangut | Yi)
26640}
26641
26642fn is_grapheme_ideographic(text: &str) -> bool {
26643 text.chars().any(is_char_ideographic)
26644}
26645
26646fn is_grapheme_whitespace(text: &str) -> bool {
26647 text.chars().any(|x| x.is_whitespace())
26648}
26649
26650fn should_stay_with_preceding_ideograph(text: &str) -> bool {
26651 text.chars()
26652 .next()
26653 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
26654}
26655
26656#[derive(PartialEq, Eq, Debug, Clone, Copy)]
26657enum WordBreakToken<'a> {
26658 Word { token: &'a str, grapheme_len: usize },
26659 InlineWhitespace { token: &'a str, grapheme_len: usize },
26660 Newline,
26661}
26662
26663impl<'a> Iterator for WordBreakingTokenizer<'a> {
26664 /// Yields a span, the count of graphemes in the token, and whether it was
26665 /// whitespace. Note that it also breaks at word boundaries.
26666 type Item = WordBreakToken<'a>;
26667
26668 fn next(&mut self) -> Option<Self::Item> {
26669 use unicode_segmentation::UnicodeSegmentation;
26670 if self.input.is_empty() {
26671 return None;
26672 }
26673
26674 let mut iter = self.input.graphemes(true).peekable();
26675 let mut offset = 0;
26676 let mut grapheme_len = 0;
26677 if let Some(first_grapheme) = iter.next() {
26678 let is_newline = first_grapheme == "\n";
26679 let is_whitespace = is_grapheme_whitespace(first_grapheme);
26680 offset += first_grapheme.len();
26681 grapheme_len += 1;
26682 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
26683 if let Some(grapheme) = iter.peek().copied()
26684 && should_stay_with_preceding_ideograph(grapheme)
26685 {
26686 offset += grapheme.len();
26687 grapheme_len += 1;
26688 }
26689 } else {
26690 let mut words = self.input[offset..].split_word_bound_indices().peekable();
26691 let mut next_word_bound = words.peek().copied();
26692 if next_word_bound.is_some_and(|(i, _)| i == 0) {
26693 next_word_bound = words.next();
26694 }
26695 while let Some(grapheme) = iter.peek().copied() {
26696 if next_word_bound.is_some_and(|(i, _)| i == offset) {
26697 break;
26698 };
26699 if is_grapheme_whitespace(grapheme) != is_whitespace
26700 || (grapheme == "\n") != is_newline
26701 {
26702 break;
26703 };
26704 offset += grapheme.len();
26705 grapheme_len += 1;
26706 iter.next();
26707 }
26708 }
26709 let token = &self.input[..offset];
26710 self.input = &self.input[offset..];
26711 if token == "\n" {
26712 Some(WordBreakToken::Newline)
26713 } else if is_whitespace {
26714 Some(WordBreakToken::InlineWhitespace {
26715 token,
26716 grapheme_len,
26717 })
26718 } else {
26719 Some(WordBreakToken::Word {
26720 token,
26721 grapheme_len,
26722 })
26723 }
26724 } else {
26725 None
26726 }
26727 }
26728}
26729
26730#[test]
26731fn test_word_breaking_tokenizer() {
26732 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
26733 ("", &[]),
26734 (" ", &[whitespace(" ", 2)]),
26735 ("Ʒ", &[word("Ʒ", 1)]),
26736 ("Ǽ", &[word("Ǽ", 1)]),
26737 ("⋑", &[word("⋑", 1)]),
26738 ("⋑⋑", &[word("⋑⋑", 2)]),
26739 (
26740 "原理,进而",
26741 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
26742 ),
26743 (
26744 "hello world",
26745 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
26746 ),
26747 (
26748 "hello, world",
26749 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
26750 ),
26751 (
26752 " hello world",
26753 &[
26754 whitespace(" ", 2),
26755 word("hello", 5),
26756 whitespace(" ", 1),
26757 word("world", 5),
26758 ],
26759 ),
26760 (
26761 "这是什么 \n 钢笔",
26762 &[
26763 word("这", 1),
26764 word("是", 1),
26765 word("什", 1),
26766 word("么", 1),
26767 whitespace(" ", 1),
26768 newline(),
26769 whitespace(" ", 1),
26770 word("钢", 1),
26771 word("笔", 1),
26772 ],
26773 ),
26774 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
26775 ];
26776
26777 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26778 WordBreakToken::Word {
26779 token,
26780 grapheme_len,
26781 }
26782 }
26783
26784 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
26785 WordBreakToken::InlineWhitespace {
26786 token,
26787 grapheme_len,
26788 }
26789 }
26790
26791 fn newline() -> WordBreakToken<'static> {
26792 WordBreakToken::Newline
26793 }
26794
26795 for (input, result) in tests {
26796 assert_eq!(
26797 WordBreakingTokenizer::new(input)
26798 .collect::<Vec<_>>()
26799 .as_slice(),
26800 *result,
26801 );
26802 }
26803}
26804
26805fn wrap_with_prefix(
26806 first_line_prefix: String,
26807 subsequent_lines_prefix: String,
26808 unwrapped_text: String,
26809 wrap_column: usize,
26810 tab_size: NonZeroU32,
26811 preserve_existing_whitespace: bool,
26812) -> String {
26813 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
26814 let subsequent_lines_prefix_len =
26815 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
26816 let mut wrapped_text = String::new();
26817 let mut current_line = first_line_prefix;
26818 let mut is_first_line = true;
26819
26820 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
26821 let mut current_line_len = first_line_prefix_len;
26822 let mut in_whitespace = false;
26823 for token in tokenizer {
26824 let have_preceding_whitespace = in_whitespace;
26825 match token {
26826 WordBreakToken::Word {
26827 token,
26828 grapheme_len,
26829 } => {
26830 in_whitespace = false;
26831 let current_prefix_len = if is_first_line {
26832 first_line_prefix_len
26833 } else {
26834 subsequent_lines_prefix_len
26835 };
26836 if current_line_len + grapheme_len > wrap_column
26837 && current_line_len != current_prefix_len
26838 {
26839 wrapped_text.push_str(current_line.trim_end());
26840 wrapped_text.push('\n');
26841 is_first_line = false;
26842 current_line = subsequent_lines_prefix.clone();
26843 current_line_len = subsequent_lines_prefix_len;
26844 }
26845 current_line.push_str(token);
26846 current_line_len += grapheme_len;
26847 }
26848 WordBreakToken::InlineWhitespace {
26849 mut token,
26850 mut grapheme_len,
26851 } => {
26852 in_whitespace = true;
26853 if have_preceding_whitespace && !preserve_existing_whitespace {
26854 continue;
26855 }
26856 if !preserve_existing_whitespace {
26857 // Keep a single whitespace grapheme as-is
26858 if let Some(first) =
26859 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
26860 {
26861 token = first;
26862 } else {
26863 token = " ";
26864 }
26865 grapheme_len = 1;
26866 }
26867 let current_prefix_len = if is_first_line {
26868 first_line_prefix_len
26869 } else {
26870 subsequent_lines_prefix_len
26871 };
26872 if current_line_len + grapheme_len > wrap_column {
26873 wrapped_text.push_str(current_line.trim_end());
26874 wrapped_text.push('\n');
26875 is_first_line = false;
26876 current_line = subsequent_lines_prefix.clone();
26877 current_line_len = subsequent_lines_prefix_len;
26878 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
26879 current_line.push_str(token);
26880 current_line_len += grapheme_len;
26881 }
26882 }
26883 WordBreakToken::Newline => {
26884 in_whitespace = true;
26885 let current_prefix_len = if is_first_line {
26886 first_line_prefix_len
26887 } else {
26888 subsequent_lines_prefix_len
26889 };
26890 if preserve_existing_whitespace {
26891 wrapped_text.push_str(current_line.trim_end());
26892 wrapped_text.push('\n');
26893 is_first_line = false;
26894 current_line = subsequent_lines_prefix.clone();
26895 current_line_len = subsequent_lines_prefix_len;
26896 } else if have_preceding_whitespace {
26897 continue;
26898 } else if current_line_len + 1 > wrap_column
26899 && current_line_len != current_prefix_len
26900 {
26901 wrapped_text.push_str(current_line.trim_end());
26902 wrapped_text.push('\n');
26903 is_first_line = false;
26904 current_line = subsequent_lines_prefix.clone();
26905 current_line_len = subsequent_lines_prefix_len;
26906 } else if current_line_len != current_prefix_len {
26907 current_line.push(' ');
26908 current_line_len += 1;
26909 }
26910 }
26911 }
26912 }
26913
26914 if !current_line.is_empty() {
26915 wrapped_text.push_str(¤t_line);
26916 }
26917 wrapped_text
26918}
26919
26920#[test]
26921fn test_wrap_with_prefix() {
26922 assert_eq!(
26923 wrap_with_prefix(
26924 "# ".to_string(),
26925 "# ".to_string(),
26926 "abcdefg".to_string(),
26927 4,
26928 NonZeroU32::new(4).unwrap(),
26929 false,
26930 ),
26931 "# abcdefg"
26932 );
26933 assert_eq!(
26934 wrap_with_prefix(
26935 "".to_string(),
26936 "".to_string(),
26937 "\thello world".to_string(),
26938 8,
26939 NonZeroU32::new(4).unwrap(),
26940 false,
26941 ),
26942 "hello\nworld"
26943 );
26944 assert_eq!(
26945 wrap_with_prefix(
26946 "// ".to_string(),
26947 "// ".to_string(),
26948 "xx \nyy zz aa bb cc".to_string(),
26949 12,
26950 NonZeroU32::new(4).unwrap(),
26951 false,
26952 ),
26953 "// xx yy zz\n// aa bb cc"
26954 );
26955 assert_eq!(
26956 wrap_with_prefix(
26957 String::new(),
26958 String::new(),
26959 "这是什么 \n 钢笔".to_string(),
26960 3,
26961 NonZeroU32::new(4).unwrap(),
26962 false,
26963 ),
26964 "这是什\n么 钢\n笔"
26965 );
26966 assert_eq!(
26967 wrap_with_prefix(
26968 String::new(),
26969 String::new(),
26970 format!("foo{}bar", '\u{2009}'), // thin space
26971 80,
26972 NonZeroU32::new(4).unwrap(),
26973 false,
26974 ),
26975 format!("foo{}bar", '\u{2009}')
26976 );
26977}
26978
26979pub trait CollaborationHub {
26980 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
26981 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
26982 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
26983}
26984
26985impl CollaborationHub for Entity<Project> {
26986 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
26987 self.read(cx).collaborators()
26988 }
26989
26990 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
26991 self.read(cx).user_store().read(cx).participant_indices()
26992 }
26993
26994 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
26995 let this = self.read(cx);
26996 let user_ids = this.collaborators().values().map(|c| c.user_id);
26997 this.user_store().read(cx).participant_names(user_ids, cx)
26998 }
26999}
27000
27001pub trait SemanticsProvider {
27002 fn hover(
27003 &self,
27004 buffer: &Entity<Buffer>,
27005 position: text::Anchor,
27006 cx: &mut App,
27007 ) -> Option<Task<Option<Vec<project::Hover>>>>;
27008
27009 fn inline_values(
27010 &self,
27011 buffer_handle: Entity<Buffer>,
27012 range: Range<text::Anchor>,
27013 cx: &mut App,
27014 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
27015
27016 fn applicable_inlay_chunks(
27017 &self,
27018 buffer: &Entity<Buffer>,
27019 ranges: &[Range<text::Anchor>],
27020 cx: &mut App,
27021 ) -> Vec<Range<BufferRow>>;
27022
27023 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
27024
27025 fn inlay_hints(
27026 &self,
27027 invalidate: InvalidationStrategy,
27028 buffer: Entity<Buffer>,
27029 ranges: Vec<Range<text::Anchor>>,
27030 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27031 cx: &mut App,
27032 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
27033
27034 fn semantic_tokens(
27035 &self,
27036 buffer: Entity<Buffer>,
27037 refresh: Option<RefreshForServer>,
27038 cx: &mut App,
27039 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>>;
27040
27041 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27042
27043 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
27044
27045 fn document_highlights(
27046 &self,
27047 buffer: &Entity<Buffer>,
27048 position: text::Anchor,
27049 cx: &mut App,
27050 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
27051
27052 fn definitions(
27053 &self,
27054 buffer: &Entity<Buffer>,
27055 position: text::Anchor,
27056 kind: GotoDefinitionKind,
27057 cx: &mut App,
27058 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
27059
27060 fn range_for_rename(
27061 &self,
27062 buffer: &Entity<Buffer>,
27063 position: text::Anchor,
27064 cx: &mut App,
27065 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
27066
27067 fn perform_rename(
27068 &self,
27069 buffer: &Entity<Buffer>,
27070 position: text::Anchor,
27071 new_name: String,
27072 cx: &mut App,
27073 ) -> Option<Task<Result<ProjectTransaction>>>;
27074}
27075
27076pub trait CompletionProvider {
27077 fn completions(
27078 &self,
27079 excerpt_id: ExcerptId,
27080 buffer: &Entity<Buffer>,
27081 buffer_position: text::Anchor,
27082 trigger: CompletionContext,
27083 window: &mut Window,
27084 cx: &mut Context<Editor>,
27085 ) -> Task<Result<Vec<CompletionResponse>>>;
27086
27087 fn resolve_completions(
27088 &self,
27089 _buffer: Entity<Buffer>,
27090 _completion_indices: Vec<usize>,
27091 _completions: Rc<RefCell<Box<[Completion]>>>,
27092 _cx: &mut Context<Editor>,
27093 ) -> Task<Result<bool>> {
27094 Task::ready(Ok(false))
27095 }
27096
27097 fn apply_additional_edits_for_completion(
27098 &self,
27099 _buffer: Entity<Buffer>,
27100 _completions: Rc<RefCell<Box<[Completion]>>>,
27101 _completion_index: usize,
27102 _push_to_history: bool,
27103 _all_commit_ranges: Vec<Range<language::Anchor>>,
27104 _cx: &mut Context<Editor>,
27105 ) -> Task<Result<Option<language::Transaction>>> {
27106 Task::ready(Ok(None))
27107 }
27108
27109 fn is_completion_trigger(
27110 &self,
27111 buffer: &Entity<Buffer>,
27112 position: language::Anchor,
27113 text: &str,
27114 trigger_in_words: bool,
27115 cx: &mut Context<Editor>,
27116 ) -> bool;
27117
27118 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
27119
27120 fn sort_completions(&self) -> bool {
27121 true
27122 }
27123
27124 fn filter_completions(&self) -> bool {
27125 true
27126 }
27127
27128 fn show_snippets(&self) -> bool {
27129 false
27130 }
27131}
27132
27133pub trait CodeActionProvider {
27134 fn id(&self) -> Arc<str>;
27135
27136 fn code_actions(
27137 &self,
27138 buffer: &Entity<Buffer>,
27139 range: Range<text::Anchor>,
27140 window: &mut Window,
27141 cx: &mut App,
27142 ) -> Task<Result<Vec<CodeAction>>>;
27143
27144 fn apply_code_action(
27145 &self,
27146 buffer_handle: Entity<Buffer>,
27147 action: CodeAction,
27148 excerpt_id: ExcerptId,
27149 push_to_history: bool,
27150 window: &mut Window,
27151 cx: &mut App,
27152 ) -> Task<Result<ProjectTransaction>>;
27153}
27154
27155impl CodeActionProvider for Entity<Project> {
27156 fn id(&self) -> Arc<str> {
27157 "project".into()
27158 }
27159
27160 fn code_actions(
27161 &self,
27162 buffer: &Entity<Buffer>,
27163 range: Range<text::Anchor>,
27164 _window: &mut Window,
27165 cx: &mut App,
27166 ) -> Task<Result<Vec<CodeAction>>> {
27167 self.update(cx, |project, cx| {
27168 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
27169 let code_actions = project.code_actions(buffer, range, None, cx);
27170 cx.background_spawn(async move {
27171 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
27172 Ok(code_lens_actions
27173 .context("code lens fetch")?
27174 .into_iter()
27175 .flatten()
27176 .chain(
27177 code_actions
27178 .context("code action fetch")?
27179 .into_iter()
27180 .flatten(),
27181 )
27182 .collect())
27183 })
27184 })
27185 }
27186
27187 fn apply_code_action(
27188 &self,
27189 buffer_handle: Entity<Buffer>,
27190 action: CodeAction,
27191 _excerpt_id: ExcerptId,
27192 push_to_history: bool,
27193 _window: &mut Window,
27194 cx: &mut App,
27195 ) -> Task<Result<ProjectTransaction>> {
27196 self.update(cx, |project, cx| {
27197 project.apply_code_action(buffer_handle, action, push_to_history, cx)
27198 })
27199 }
27200}
27201
27202fn snippet_completions(
27203 project: &Project,
27204 buffer: &Entity<Buffer>,
27205 buffer_anchor: text::Anchor,
27206 classifier: CharClassifier,
27207 cx: &mut App,
27208) -> Task<Result<CompletionResponse>> {
27209 let languages = buffer.read(cx).languages_at(buffer_anchor);
27210 let snippet_store = project.snippets().read(cx);
27211
27212 let scopes: Vec<_> = languages
27213 .iter()
27214 .filter_map(|language| {
27215 let language_name = language.lsp_id();
27216 let snippets = snippet_store.snippets_for(Some(language_name), cx);
27217
27218 if snippets.is_empty() {
27219 None
27220 } else {
27221 Some((language.default_scope(), snippets))
27222 }
27223 })
27224 .collect();
27225
27226 if scopes.is_empty() {
27227 return Task::ready(Ok(CompletionResponse {
27228 completions: vec![],
27229 display_options: CompletionDisplayOptions::default(),
27230 is_incomplete: false,
27231 }));
27232 }
27233
27234 let snapshot = buffer.read(cx).text_snapshot();
27235 let executor = cx.background_executor().clone();
27236
27237 cx.background_spawn(async move {
27238 let is_word_char = |c| classifier.is_word(c);
27239
27240 let mut is_incomplete = false;
27241 let mut completions: Vec<Completion> = Vec::new();
27242
27243 const MAX_PREFIX_LEN: usize = 128;
27244 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
27245 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
27246 let window_start = snapshot.clip_offset(window_start, Bias::Left);
27247
27248 let max_buffer_window: String = snapshot
27249 .text_for_range(window_start..buffer_offset)
27250 .collect();
27251
27252 if max_buffer_window.is_empty() {
27253 return Ok(CompletionResponse {
27254 completions: vec![],
27255 display_options: CompletionDisplayOptions::default(),
27256 is_incomplete: true,
27257 });
27258 }
27259
27260 for (_scope, snippets) in scopes.into_iter() {
27261 // Sort snippets by word count to match longer snippet prefixes first.
27262 let mut sorted_snippet_candidates = snippets
27263 .iter()
27264 .enumerate()
27265 .flat_map(|(snippet_ix, snippet)| {
27266 snippet
27267 .prefix
27268 .iter()
27269 .enumerate()
27270 .map(move |(prefix_ix, prefix)| {
27271 let word_count =
27272 snippet_candidate_suffixes(prefix, &is_word_char).count();
27273 ((snippet_ix, prefix_ix), prefix, word_count)
27274 })
27275 })
27276 .collect_vec();
27277 sorted_snippet_candidates
27278 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
27279
27280 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
27281
27282 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, &is_word_char)
27283 .take(
27284 sorted_snippet_candidates
27285 .first()
27286 .map(|(_, _, word_count)| *word_count)
27287 .unwrap_or_default(),
27288 )
27289 .collect_vec();
27290
27291 const MAX_RESULTS: usize = 100;
27292 // Each match also remembers how many characters from the buffer it consumed
27293 let mut matches: Vec<(StringMatch, usize)> = vec![];
27294
27295 let mut snippet_list_cutoff_index = 0;
27296 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
27297 let word_count = buffer_index + 1;
27298 // Increase `snippet_list_cutoff_index` until we have all of the
27299 // snippets with sufficiently many words.
27300 while sorted_snippet_candidates
27301 .get(snippet_list_cutoff_index)
27302 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
27303 *snippet_word_count >= word_count
27304 })
27305 {
27306 snippet_list_cutoff_index += 1;
27307 }
27308
27309 // Take only the candidates with at least `word_count` many words
27310 let snippet_candidates_at_word_len =
27311 &sorted_snippet_candidates[..snippet_list_cutoff_index];
27312
27313 let candidates = snippet_candidates_at_word_len
27314 .iter()
27315 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
27316 .enumerate() // index in `sorted_snippet_candidates`
27317 // First char must match
27318 .filter(|(_ix, prefix)| {
27319 itertools::equal(
27320 prefix
27321 .chars()
27322 .next()
27323 .into_iter()
27324 .flat_map(|c| c.to_lowercase()),
27325 buffer_window
27326 .chars()
27327 .next()
27328 .into_iter()
27329 .flat_map(|c| c.to_lowercase()),
27330 )
27331 })
27332 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
27333 .collect::<Vec<StringMatchCandidate>>();
27334
27335 matches.extend(
27336 fuzzy::match_strings(
27337 &candidates,
27338 &buffer_window,
27339 buffer_window.chars().any(|c| c.is_uppercase()),
27340 true,
27341 MAX_RESULTS - matches.len(), // always prioritize longer snippets
27342 &Default::default(),
27343 executor.clone(),
27344 )
27345 .await
27346 .into_iter()
27347 .map(|string_match| (string_match, buffer_window.len())),
27348 );
27349
27350 if matches.len() >= MAX_RESULTS {
27351 break;
27352 }
27353 }
27354
27355 let to_lsp = |point: &text::Anchor| {
27356 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
27357 point_to_lsp(end)
27358 };
27359 let lsp_end = to_lsp(&buffer_anchor);
27360
27361 if matches.len() >= MAX_RESULTS {
27362 is_incomplete = true;
27363 }
27364
27365 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
27366 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
27367 sorted_snippet_candidates[string_match.candidate_id];
27368 let snippet = &snippets[snippet_index];
27369 let start = buffer_offset - buffer_window_len;
27370 let start = snapshot.anchor_before(start);
27371 let range = start..buffer_anchor;
27372 let lsp_start = to_lsp(&start);
27373 let lsp_range = lsp::Range {
27374 start: lsp_start,
27375 end: lsp_end,
27376 };
27377 Completion {
27378 replace_range: range,
27379 new_text: snippet.body.clone(),
27380 source: CompletionSource::Lsp {
27381 insert_range: None,
27382 server_id: LanguageServerId(usize::MAX),
27383 resolved: true,
27384 lsp_completion: Box::new(lsp::CompletionItem {
27385 label: snippet.prefix.first().unwrap().clone(),
27386 kind: Some(CompletionItemKind::SNIPPET),
27387 label_details: snippet.description.as_ref().map(|description| {
27388 lsp::CompletionItemLabelDetails {
27389 detail: Some(description.clone()),
27390 description: None,
27391 }
27392 }),
27393 insert_text_format: Some(InsertTextFormat::SNIPPET),
27394 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27395 lsp::InsertReplaceEdit {
27396 new_text: snippet.body.clone(),
27397 insert: lsp_range,
27398 replace: lsp_range,
27399 },
27400 )),
27401 filter_text: Some(snippet.body.clone()),
27402 sort_text: Some(char::MAX.to_string()),
27403 ..lsp::CompletionItem::default()
27404 }),
27405 lsp_defaults: None,
27406 },
27407 label: CodeLabel {
27408 text: matching_prefix.clone(),
27409 runs: Vec::new(),
27410 filter_range: 0..matching_prefix.len(),
27411 },
27412 icon_path: None,
27413 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
27414 single_line: snippet.name.clone().into(),
27415 plain_text: snippet
27416 .description
27417 .clone()
27418 .map(|description| description.into()),
27419 }),
27420 insert_text_mode: None,
27421 confirm: None,
27422 match_start: Some(start),
27423 snippet_deduplication_key: Some((snippet_index, prefix_index)),
27424 }
27425 }));
27426 }
27427
27428 Ok(CompletionResponse {
27429 completions,
27430 display_options: CompletionDisplayOptions::default(),
27431 is_incomplete,
27432 })
27433 })
27434}
27435
27436impl CompletionProvider for Entity<Project> {
27437 fn completions(
27438 &self,
27439 _excerpt_id: ExcerptId,
27440 buffer: &Entity<Buffer>,
27441 buffer_position: text::Anchor,
27442 options: CompletionContext,
27443 _window: &mut Window,
27444 cx: &mut Context<Editor>,
27445 ) -> Task<Result<Vec<CompletionResponse>>> {
27446 self.update(cx, |project, cx| {
27447 let task = project.completions(buffer, buffer_position, options, cx);
27448 cx.background_spawn(task)
27449 })
27450 }
27451
27452 fn resolve_completions(
27453 &self,
27454 buffer: Entity<Buffer>,
27455 completion_indices: Vec<usize>,
27456 completions: Rc<RefCell<Box<[Completion]>>>,
27457 cx: &mut Context<Editor>,
27458 ) -> Task<Result<bool>> {
27459 self.update(cx, |project, cx| {
27460 project.lsp_store().update(cx, |lsp_store, cx| {
27461 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
27462 })
27463 })
27464 }
27465
27466 fn apply_additional_edits_for_completion(
27467 &self,
27468 buffer: Entity<Buffer>,
27469 completions: Rc<RefCell<Box<[Completion]>>>,
27470 completion_index: usize,
27471 push_to_history: bool,
27472 all_commit_ranges: Vec<Range<language::Anchor>>,
27473 cx: &mut Context<Editor>,
27474 ) -> Task<Result<Option<language::Transaction>>> {
27475 self.update(cx, |project, cx| {
27476 project.lsp_store().update(cx, |lsp_store, cx| {
27477 lsp_store.apply_additional_edits_for_completion(
27478 buffer,
27479 completions,
27480 completion_index,
27481 push_to_history,
27482 all_commit_ranges,
27483 cx,
27484 )
27485 })
27486 })
27487 }
27488
27489 fn is_completion_trigger(
27490 &self,
27491 buffer: &Entity<Buffer>,
27492 position: language::Anchor,
27493 text: &str,
27494 trigger_in_words: bool,
27495 cx: &mut Context<Editor>,
27496 ) -> bool {
27497 let mut chars = text.chars();
27498 let char = if let Some(char) = chars.next() {
27499 char
27500 } else {
27501 return false;
27502 };
27503 if chars.next().is_some() {
27504 return false;
27505 }
27506
27507 let buffer = buffer.read(cx);
27508 let snapshot = buffer.snapshot();
27509 let classifier = snapshot
27510 .char_classifier_at(position)
27511 .scope_context(Some(CharScopeContext::Completion));
27512 if trigger_in_words && classifier.is_word(char) {
27513 return true;
27514 }
27515
27516 buffer.completion_triggers().contains(text)
27517 }
27518
27519 fn show_snippets(&self) -> bool {
27520 true
27521 }
27522}
27523
27524impl SemanticsProvider for WeakEntity<Project> {
27525 fn hover(
27526 &self,
27527 buffer: &Entity<Buffer>,
27528 position: text::Anchor,
27529 cx: &mut App,
27530 ) -> Option<Task<Option<Vec<project::Hover>>>> {
27531 self.update(cx, |project, cx| project.hover(buffer, position, cx))
27532 .ok()
27533 }
27534
27535 fn document_highlights(
27536 &self,
27537 buffer: &Entity<Buffer>,
27538 position: text::Anchor,
27539 cx: &mut App,
27540 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
27541 self.update(cx, |project, cx| {
27542 project.document_highlights(buffer, position, cx)
27543 })
27544 .ok()
27545 }
27546
27547 fn definitions(
27548 &self,
27549 buffer: &Entity<Buffer>,
27550 position: text::Anchor,
27551 kind: GotoDefinitionKind,
27552 cx: &mut App,
27553 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
27554 self.update(cx, |project, cx| match kind {
27555 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
27556 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
27557 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
27558 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
27559 })
27560 .ok()
27561 }
27562
27563 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27564 self.update(cx, |project, cx| {
27565 if project
27566 .active_debug_session(cx)
27567 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
27568 {
27569 return true;
27570 }
27571
27572 buffer.update(cx, |buffer, cx| {
27573 project.any_language_server_supports_inlay_hints(buffer, cx)
27574 })
27575 })
27576 .unwrap_or(false)
27577 }
27578
27579 fn supports_semantic_tokens(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
27580 self.update(cx, |project, cx| {
27581 buffer.update(cx, |buffer, cx| {
27582 project.any_language_server_supports_semantic_tokens(buffer, cx)
27583 })
27584 })
27585 .unwrap_or(false)
27586 }
27587
27588 fn inline_values(
27589 &self,
27590 buffer_handle: Entity<Buffer>,
27591 range: Range<text::Anchor>,
27592 cx: &mut App,
27593 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
27594 self.update(cx, |project, cx| {
27595 let (session, active_stack_frame) = project.active_debug_session(cx)?;
27596
27597 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
27598 })
27599 .ok()
27600 .flatten()
27601 }
27602
27603 fn applicable_inlay_chunks(
27604 &self,
27605 buffer: &Entity<Buffer>,
27606 ranges: &[Range<text::Anchor>],
27607 cx: &mut App,
27608 ) -> Vec<Range<BufferRow>> {
27609 self.update(cx, |project, cx| {
27610 project.lsp_store().update(cx, |lsp_store, cx| {
27611 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
27612 })
27613 })
27614 .unwrap_or_default()
27615 }
27616
27617 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
27618 self.update(cx, |project, cx| {
27619 project.lsp_store().update(cx, |lsp_store, _| {
27620 lsp_store.invalidate_inlay_hints(for_buffers)
27621 })
27622 })
27623 .ok();
27624 }
27625
27626 fn inlay_hints(
27627 &self,
27628 invalidate: InvalidationStrategy,
27629 buffer: Entity<Buffer>,
27630 ranges: Vec<Range<text::Anchor>>,
27631 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
27632 cx: &mut App,
27633 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
27634 self.update(cx, |project, cx| {
27635 project.lsp_store().update(cx, |lsp_store, cx| {
27636 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
27637 })
27638 })
27639 .ok()
27640 }
27641
27642 fn semantic_tokens(
27643 &self,
27644 buffer: Entity<Buffer>,
27645 refresh: Option<RefreshForServer>,
27646 cx: &mut App,
27647 ) -> Option<Shared<Task<std::result::Result<BufferSemanticTokens, Arc<anyhow::Error>>>>> {
27648 self.update(cx, |this, cx| {
27649 this.lsp_store().update(cx, |lsp_store, cx| {
27650 lsp_store.semantic_tokens(buffer, refresh, cx)
27651 })
27652 })
27653 .ok()
27654 }
27655
27656 fn range_for_rename(
27657 &self,
27658 buffer: &Entity<Buffer>,
27659 position: text::Anchor,
27660 cx: &mut App,
27661 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
27662 self.update(cx, |project, cx| {
27663 let buffer = buffer.clone();
27664 let task = project.prepare_rename(buffer.clone(), position, cx);
27665 cx.spawn(async move |_, cx| {
27666 Ok(match task.await? {
27667 PrepareRenameResponse::Success(range) => Some(range),
27668 PrepareRenameResponse::InvalidPosition => None,
27669 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
27670 // Fallback on using TreeSitter info to determine identifier range
27671 buffer.read_with(cx, |buffer, _| {
27672 let snapshot = buffer.snapshot();
27673 let (range, kind) = snapshot.surrounding_word(position, None);
27674 if kind != Some(CharKind::Word) {
27675 return None;
27676 }
27677 Some(
27678 snapshot.anchor_before(range.start)
27679 ..snapshot.anchor_after(range.end),
27680 )
27681 })
27682 }
27683 })
27684 })
27685 })
27686 .ok()
27687 }
27688
27689 fn perform_rename(
27690 &self,
27691 buffer: &Entity<Buffer>,
27692 position: text::Anchor,
27693 new_name: String,
27694 cx: &mut App,
27695 ) -> Option<Task<Result<ProjectTransaction>>> {
27696 self.update(cx, |project, cx| {
27697 project.perform_rename(buffer.clone(), position, new_name, cx)
27698 })
27699 .ok()
27700 }
27701}
27702
27703fn consume_contiguous_rows(
27704 contiguous_row_selections: &mut Vec<Selection<Point>>,
27705 selection: &Selection<Point>,
27706 display_map: &DisplaySnapshot,
27707 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
27708) -> (MultiBufferRow, MultiBufferRow) {
27709 contiguous_row_selections.push(selection.clone());
27710 let start_row = starting_row(selection, display_map);
27711 let mut end_row = ending_row(selection, display_map);
27712
27713 while let Some(next_selection) = selections.peek() {
27714 if next_selection.start.row <= end_row.0 {
27715 end_row = ending_row(next_selection, display_map);
27716 contiguous_row_selections.push(selections.next().unwrap().clone());
27717 } else {
27718 break;
27719 }
27720 }
27721 (start_row, end_row)
27722}
27723
27724fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27725 if selection.start.column > 0 {
27726 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
27727 } else {
27728 MultiBufferRow(selection.start.row)
27729 }
27730}
27731
27732fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
27733 if next_selection.end.column > 0 || next_selection.is_empty() {
27734 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
27735 } else {
27736 MultiBufferRow(next_selection.end.row)
27737 }
27738}
27739
27740impl EditorSnapshot {
27741 pub fn remote_selections_in_range<'a>(
27742 &'a self,
27743 range: &'a Range<Anchor>,
27744 collaboration_hub: &dyn CollaborationHub,
27745 cx: &'a App,
27746 ) -> impl 'a + Iterator<Item = RemoteSelection> {
27747 let participant_names = collaboration_hub.user_names(cx);
27748 let participant_indices = collaboration_hub.user_participant_indices(cx);
27749 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
27750 let collaborators_by_replica_id = collaborators_by_peer_id
27751 .values()
27752 .map(|collaborator| (collaborator.replica_id, collaborator))
27753 .collect::<HashMap<_, _>>();
27754 self.buffer_snapshot()
27755 .selections_in_range(range, false)
27756 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
27757 if replica_id == ReplicaId::AGENT {
27758 Some(RemoteSelection {
27759 replica_id,
27760 selection,
27761 cursor_shape,
27762 line_mode,
27763 collaborator_id: CollaboratorId::Agent,
27764 user_name: Some("Agent".into()),
27765 color: cx.theme().players().agent(),
27766 })
27767 } else {
27768 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
27769 let participant_index = participant_indices.get(&collaborator.user_id).copied();
27770 let user_name = participant_names.get(&collaborator.user_id).cloned();
27771 Some(RemoteSelection {
27772 replica_id,
27773 selection,
27774 cursor_shape,
27775 line_mode,
27776 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
27777 user_name,
27778 color: if let Some(index) = participant_index {
27779 cx.theme().players().color_for_participant(index.0)
27780 } else {
27781 cx.theme().players().absent()
27782 },
27783 })
27784 }
27785 })
27786 }
27787
27788 pub fn hunks_for_ranges(
27789 &self,
27790 ranges: impl IntoIterator<Item = Range<Point>>,
27791 ) -> Vec<MultiBufferDiffHunk> {
27792 let mut hunks = Vec::new();
27793 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
27794 HashMap::default();
27795 for query_range in ranges {
27796 let query_rows =
27797 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
27798 for hunk in self.buffer_snapshot().diff_hunks_in_range(
27799 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
27800 ) {
27801 // Include deleted hunks that are adjacent to the query range, because
27802 // otherwise they would be missed.
27803 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
27804 if hunk.status().is_deleted() {
27805 intersects_range |= hunk.row_range.start == query_rows.end;
27806 intersects_range |= hunk.row_range.end == query_rows.start;
27807 }
27808 if intersects_range {
27809 if !processed_buffer_rows
27810 .entry(hunk.buffer_id)
27811 .or_default()
27812 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
27813 {
27814 continue;
27815 }
27816 hunks.push(hunk);
27817 }
27818 }
27819 }
27820
27821 hunks
27822 }
27823
27824 fn display_diff_hunks_for_rows<'a>(
27825 &'a self,
27826 display_rows: Range<DisplayRow>,
27827 folded_buffers: &'a HashSet<BufferId>,
27828 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
27829 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
27830 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
27831
27832 self.buffer_snapshot()
27833 .diff_hunks_in_range(buffer_start..buffer_end)
27834 .filter_map(|hunk| {
27835 if folded_buffers.contains(&hunk.buffer_id)
27836 || (hunk.row_range.is_empty() && self.buffer.all_diff_hunks_expanded())
27837 {
27838 return None;
27839 }
27840
27841 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
27842 let hunk_end_point = if hunk.row_range.end > hunk.row_range.start {
27843 let last_row = MultiBufferRow(hunk.row_range.end.0 - 1);
27844 let line_len = self.buffer_snapshot().line_len(last_row);
27845 Point::new(last_row.0, line_len)
27846 } else {
27847 Point::new(hunk.row_range.end.0, 0)
27848 };
27849
27850 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
27851 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
27852
27853 let display_hunk = if hunk_display_start.column() != 0 {
27854 DisplayDiffHunk::Folded {
27855 display_row: hunk_display_start.row(),
27856 }
27857 } else {
27858 let mut end_row = hunk_display_end.row();
27859 if hunk.row_range.end > hunk.row_range.start || hunk_display_end.column() > 0 {
27860 end_row.0 += 1;
27861 }
27862 let is_created_file = hunk.is_created_file();
27863
27864 DisplayDiffHunk::Unfolded {
27865 status: hunk.status(),
27866 diff_base_byte_range: hunk.diff_base_byte_range.start.0
27867 ..hunk.diff_base_byte_range.end.0,
27868 word_diffs: hunk.word_diffs,
27869 display_row_range: hunk_display_start.row()..end_row,
27870 multi_buffer_range: Anchor::range_in_buffer(
27871 hunk.excerpt_id,
27872 hunk.buffer_range,
27873 ),
27874 is_created_file,
27875 }
27876 };
27877
27878 Some(display_hunk)
27879 })
27880 }
27881
27882 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
27883 self.display_snapshot
27884 .buffer_snapshot()
27885 .language_at(position)
27886 }
27887
27888 pub fn is_focused(&self) -> bool {
27889 self.is_focused
27890 }
27891
27892 pub fn placeholder_text(&self) -> Option<String> {
27893 self.placeholder_display_snapshot
27894 .as_ref()
27895 .map(|display_map| display_map.text())
27896 }
27897
27898 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
27899 self.scroll_anchor.scroll_position(&self.display_snapshot)
27900 }
27901
27902 pub fn gutter_dimensions(
27903 &self,
27904 font_id: FontId,
27905 font_size: Pixels,
27906 style: &EditorStyle,
27907 window: &mut Window,
27908 cx: &App,
27909 ) -> GutterDimensions {
27910 if self.show_gutter
27911 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
27912 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
27913 {
27914 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
27915 matches!(
27916 ProjectSettings::get_global(cx).git.git_gutter,
27917 GitGutterSetting::TrackedFiles
27918 )
27919 });
27920 let gutter_settings = EditorSettings::get_global(cx).gutter;
27921 let show_line_numbers = self
27922 .show_line_numbers
27923 .unwrap_or(gutter_settings.line_numbers);
27924 let line_gutter_width = if show_line_numbers {
27925 // Avoid flicker-like gutter resizes when the line number gains another digit by
27926 // only resizing the gutter on files with > 10**min_line_number_digits lines.
27927 let min_width_for_number_on_gutter =
27928 ch_advance * gutter_settings.min_line_number_digits as f32;
27929 self.max_line_number_width(style, window)
27930 .max(min_width_for_number_on_gutter)
27931 } else {
27932 0.0.into()
27933 };
27934
27935 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
27936 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
27937
27938 let git_blame_entries_width =
27939 self.git_blame_gutter_max_author_length
27940 .map(|max_author_length| {
27941 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
27942 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
27943
27944 /// The number of characters to dedicate to gaps and margins.
27945 const SPACING_WIDTH: usize = 4;
27946
27947 let max_char_count = max_author_length.min(renderer.max_author_length())
27948 + ::git::SHORT_SHA_LENGTH
27949 + MAX_RELATIVE_TIMESTAMP.len()
27950 + SPACING_WIDTH;
27951
27952 ch_advance * max_char_count
27953 });
27954
27955 let is_singleton = self.buffer_snapshot().is_singleton();
27956
27957 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
27958 left_padding += if !is_singleton {
27959 ch_width * 4.0
27960 } else if show_runnables || show_breakpoints {
27961 ch_width * 3.0
27962 } else if show_git_gutter && show_line_numbers {
27963 ch_width * 2.0
27964 } else if show_git_gutter || show_line_numbers {
27965 ch_width
27966 } else {
27967 px(0.)
27968 };
27969
27970 let shows_folds = is_singleton && gutter_settings.folds;
27971
27972 let right_padding = if shows_folds && show_line_numbers {
27973 ch_width * 4.0
27974 } else if shows_folds || (!is_singleton && show_line_numbers) {
27975 ch_width * 3.0
27976 } else if show_line_numbers {
27977 ch_width
27978 } else {
27979 px(0.)
27980 };
27981
27982 GutterDimensions {
27983 left_padding,
27984 right_padding,
27985 width: line_gutter_width + left_padding + right_padding,
27986 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
27987 git_blame_entries_width,
27988 }
27989 } else if self.offset_content {
27990 GutterDimensions::default_with_margin(font_id, font_size, cx)
27991 } else {
27992 GutterDimensions::default()
27993 }
27994 }
27995
27996 pub fn render_crease_toggle(
27997 &self,
27998 buffer_row: MultiBufferRow,
27999 row_contains_cursor: bool,
28000 editor: Entity<Editor>,
28001 window: &mut Window,
28002 cx: &mut App,
28003 ) -> Option<AnyElement> {
28004 let folded = self.is_line_folded(buffer_row);
28005 let mut is_foldable = false;
28006
28007 if let Some(crease) = self
28008 .crease_snapshot
28009 .query_row(buffer_row, self.buffer_snapshot())
28010 {
28011 is_foldable = true;
28012 match crease {
28013 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
28014 if let Some(render_toggle) = render_toggle {
28015 let toggle_callback =
28016 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
28017 if folded {
28018 editor.update(cx, |editor, cx| {
28019 editor.fold_at(buffer_row, window, cx)
28020 });
28021 } else {
28022 editor.update(cx, |editor, cx| {
28023 editor.unfold_at(buffer_row, window, cx)
28024 });
28025 }
28026 });
28027 return Some((render_toggle)(
28028 buffer_row,
28029 folded,
28030 toggle_callback,
28031 window,
28032 cx,
28033 ));
28034 }
28035 }
28036 }
28037 }
28038
28039 is_foldable |= !self.use_lsp_folding_ranges && self.starts_indent(buffer_row);
28040
28041 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
28042 Some(
28043 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
28044 .toggle_state(folded)
28045 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
28046 if folded {
28047 this.unfold_at(buffer_row, window, cx);
28048 } else {
28049 this.fold_at(buffer_row, window, cx);
28050 }
28051 }))
28052 .into_any_element(),
28053 )
28054 } else {
28055 None
28056 }
28057 }
28058
28059 pub fn render_crease_trailer(
28060 &self,
28061 buffer_row: MultiBufferRow,
28062 window: &mut Window,
28063 cx: &mut App,
28064 ) -> Option<AnyElement> {
28065 let folded = self.is_line_folded(buffer_row);
28066 if let Crease::Inline { render_trailer, .. } = self
28067 .crease_snapshot
28068 .query_row(buffer_row, self.buffer_snapshot())?
28069 {
28070 let render_trailer = render_trailer.as_ref()?;
28071 Some(render_trailer(buffer_row, folded, window, cx))
28072 } else {
28073 None
28074 }
28075 }
28076
28077 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
28078 let digit_count = self.widest_line_number().ilog10() + 1;
28079 column_pixels(style, digit_count as usize, window)
28080 }
28081
28082 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
28083 ///
28084 /// This is positive if `base` is before `line`.
28085 fn relative_line_delta(
28086 &self,
28087 current_selection_head: DisplayRow,
28088 first_visible_row: DisplayRow,
28089 consider_wrapped_lines: bool,
28090 ) -> i64 {
28091 let current_selection_head = current_selection_head.as_display_point().to_point(self);
28092 let first_visible_row = first_visible_row.as_display_point().to_point(self);
28093
28094 if consider_wrapped_lines {
28095 let wrap_snapshot = self.wrap_snapshot();
28096 let base_wrap_row = wrap_snapshot
28097 .make_wrap_point(current_selection_head, Bias::Left)
28098 .row();
28099 let wrap_row = wrap_snapshot
28100 .make_wrap_point(first_visible_row, Bias::Left)
28101 .row();
28102
28103 wrap_row.0 as i64 - base_wrap_row.0 as i64
28104 } else {
28105 let fold_snapshot = self.fold_snapshot();
28106 let base_fold_row = fold_snapshot
28107 .to_fold_point(self.to_inlay_point(current_selection_head), Bias::Left)
28108 .row();
28109 let fold_row = fold_snapshot
28110 .to_fold_point(self.to_inlay_point(first_visible_row), Bias::Left)
28111 .row();
28112
28113 fold_row as i64 - base_fold_row as i64
28114 }
28115 }
28116
28117 /// Returns the unsigned relative line number to display for each row in `rows`.
28118 ///
28119 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
28120 pub fn calculate_relative_line_numbers(
28121 &self,
28122 rows: &Range<DisplayRow>,
28123 current_selection_head: DisplayRow,
28124 count_wrapped_lines: bool,
28125 ) -> HashMap<DisplayRow, u32> {
28126 let initial_offset =
28127 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
28128
28129 self.row_infos(rows.start)
28130 .take(rows.len())
28131 .enumerate()
28132 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
28133 .filter(|(_row, row_info)| {
28134 row_info.buffer_row.is_some()
28135 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
28136 })
28137 .enumerate()
28138 .filter_map(|(i, (row, row_info))| {
28139 // We want to ensure here that the current line has absolute
28140 // numbering, even if we are in a soft-wrapped line. With the
28141 // exception that if we are in a deleted line, we should number this
28142 // relative with 0, as otherwise it would have no line number at all
28143 let relative_line_number = (initial_offset + i as i64).unsigned_abs() as u32;
28144
28145 (relative_line_number != 0
28146 || row_info
28147 .diff_status
28148 .is_some_and(|status| status.is_deleted()))
28149 .then_some((row, relative_line_number))
28150 })
28151 .collect()
28152 }
28153}
28154
28155pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
28156 let font_size = style.text.font_size.to_pixels(window.rem_size());
28157 let layout = window.text_system().shape_line(
28158 SharedString::from(" ".repeat(column)),
28159 font_size,
28160 &[TextRun {
28161 len: column,
28162 font: style.text.font(),
28163 color: Hsla::default(),
28164 ..Default::default()
28165 }],
28166 None,
28167 );
28168
28169 layout.width
28170}
28171
28172impl Deref for EditorSnapshot {
28173 type Target = DisplaySnapshot;
28174
28175 fn deref(&self) -> &Self::Target {
28176 &self.display_snapshot
28177 }
28178}
28179
28180#[derive(Clone, Debug, PartialEq, Eq)]
28181pub enum EditorEvent {
28182 /// Emitted when the stored review comments change (added, removed, or updated).
28183 ReviewCommentsChanged {
28184 /// The new total count of review comments.
28185 total_count: usize,
28186 },
28187 InputIgnored {
28188 text: Arc<str>,
28189 },
28190 InputHandled {
28191 utf16_range_to_replace: Option<Range<isize>>,
28192 text: Arc<str>,
28193 },
28194 ExcerptsAdded {
28195 buffer: Entity<Buffer>,
28196 predecessor: ExcerptId,
28197 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
28198 },
28199 ExcerptsRemoved {
28200 ids: Vec<ExcerptId>,
28201 removed_buffer_ids: Vec<BufferId>,
28202 },
28203 BufferFoldToggled {
28204 ids: Vec<ExcerptId>,
28205 folded: bool,
28206 },
28207 ExcerptsEdited {
28208 ids: Vec<ExcerptId>,
28209 },
28210 ExcerptsExpanded {
28211 ids: Vec<ExcerptId>,
28212 },
28213 ExpandExcerptsRequested {
28214 excerpt_ids: Vec<ExcerptId>,
28215 lines: u32,
28216 direction: ExpandExcerptDirection,
28217 },
28218 StageOrUnstageRequested {
28219 stage: bool,
28220 hunks: Vec<MultiBufferDiffHunk>,
28221 },
28222 OpenExcerptsRequested {
28223 selections_by_buffer: HashMap<BufferId, (Vec<Range<BufferOffset>>, Option<u32>)>,
28224 split: bool,
28225 },
28226 RestoreRequested {
28227 hunks: Vec<MultiBufferDiffHunk>,
28228 },
28229 BufferEdited,
28230 Edited {
28231 transaction_id: clock::Lamport,
28232 },
28233 Reparsed(BufferId),
28234 Focused,
28235 FocusedIn,
28236 Blurred,
28237 DirtyChanged,
28238 Saved,
28239 TitleChanged,
28240 SelectionsChanged {
28241 local: bool,
28242 },
28243 ScrollPositionChanged {
28244 local: bool,
28245 autoscroll: bool,
28246 },
28247 TransactionUndone {
28248 transaction_id: clock::Lamport,
28249 },
28250 TransactionBegun {
28251 transaction_id: clock::Lamport,
28252 },
28253 CursorShapeChanged,
28254 BreadcrumbsChanged,
28255 OutlineSymbolsChanged,
28256 PushedToNavHistory {
28257 anchor: Anchor,
28258 is_deactivate: bool,
28259 },
28260}
28261
28262impl EventEmitter<EditorEvent> for Editor {}
28263
28264impl Focusable for Editor {
28265 fn focus_handle(&self, _cx: &App) -> FocusHandle {
28266 self.focus_handle.clone()
28267 }
28268}
28269
28270impl Render for Editor {
28271 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
28272 EditorElement::new(&cx.entity(), self.create_style(cx))
28273 }
28274}
28275
28276impl EntityInputHandler for Editor {
28277 fn text_for_range(
28278 &mut self,
28279 range_utf16: Range<usize>,
28280 adjusted_range: &mut Option<Range<usize>>,
28281 _: &mut Window,
28282 cx: &mut Context<Self>,
28283 ) -> Option<String> {
28284 let snapshot = self.buffer.read(cx).read(cx);
28285 let start = snapshot.clip_offset_utf16(
28286 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
28287 Bias::Left,
28288 );
28289 let end = snapshot.clip_offset_utf16(
28290 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
28291 Bias::Right,
28292 );
28293 if (start.0.0..end.0.0) != range_utf16 {
28294 adjusted_range.replace(start.0.0..end.0.0);
28295 }
28296 Some(snapshot.text_for_range(start..end).collect())
28297 }
28298
28299 fn selected_text_range(
28300 &mut self,
28301 ignore_disabled_input: bool,
28302 _: &mut Window,
28303 cx: &mut Context<Self>,
28304 ) -> Option<UTF16Selection> {
28305 // Prevent the IME menu from appearing when holding down an alphabetic key
28306 // while input is disabled.
28307 if !ignore_disabled_input && !self.input_enabled {
28308 return None;
28309 }
28310
28311 let selection = self
28312 .selections
28313 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
28314 let range = selection.range();
28315
28316 Some(UTF16Selection {
28317 range: range.start.0.0..range.end.0.0,
28318 reversed: selection.reversed,
28319 })
28320 }
28321
28322 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
28323 let snapshot = self.buffer.read(cx).read(cx);
28324 let range = self
28325 .text_highlights(HighlightKey::InputComposition, cx)?
28326 .1
28327 .first()?;
28328 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
28329 }
28330
28331 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
28332 self.clear_highlights(HighlightKey::InputComposition, cx);
28333 self.ime_transaction.take();
28334 }
28335
28336 fn replace_text_in_range(
28337 &mut self,
28338 range_utf16: Option<Range<usize>>,
28339 text: &str,
28340 window: &mut Window,
28341 cx: &mut Context<Self>,
28342 ) {
28343 if !self.input_enabled {
28344 cx.emit(EditorEvent::InputIgnored { text: text.into() });
28345 return;
28346 }
28347
28348 self.transact(window, cx, |this, window, cx| {
28349 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
28350 if let Some(marked_ranges) = this.marked_text_ranges(cx) {
28351 // During IME composition, macOS reports the replacement range
28352 // relative to the first marked region (the only one visible via
28353 // marked_text_range). The correct targets for replacement are the
28354 // marked ranges themselves — one per cursor — so use them directly.
28355 Some(marked_ranges)
28356 } else if range_utf16.start == range_utf16.end {
28357 // An empty replacement range means "insert at cursor" with no text
28358 // to replace. macOS reports the cursor position from its own
28359 // (single-cursor) view of the buffer, which diverges from our actual
28360 // cursor positions after multi-cursor edits have shifted offsets.
28361 // Treating this as range_utf16=None lets each cursor insert in place.
28362 None
28363 } else {
28364 // Outside of IME composition (e.g. Accessibility Keyboard word
28365 // completion), the range is an absolute document offset for the
28366 // newest cursor. Fan it out to all cursors via
28367 // selection_replacement_ranges, which applies the delta relative
28368 // to the newest selection to every cursor.
28369 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28370 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28371 Some(this.selection_replacement_ranges(range_utf16, cx))
28372 }
28373 } else {
28374 this.marked_text_ranges(cx)
28375 };
28376
28377 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
28378 let newest_selection_id = this.selections.newest_anchor().id;
28379 this.selections
28380 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28381 .iter()
28382 .zip(ranges_to_replace.iter())
28383 .find_map(|(selection, range)| {
28384 if selection.id == newest_selection_id {
28385 Some(
28386 (range.start.0.0 as isize - selection.head().0.0 as isize)
28387 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28388 )
28389 } else {
28390 None
28391 }
28392 })
28393 });
28394
28395 cx.emit(EditorEvent::InputHandled {
28396 utf16_range_to_replace: range_to_replace,
28397 text: text.into(),
28398 });
28399
28400 if let Some(new_selected_ranges) = new_selected_ranges {
28401 // Only backspace if at least one range covers actual text. When all
28402 // ranges are empty (e.g. a trailing-space insertion from Accessibility
28403 // Keyboard sends replacementRange=cursor..cursor), backspace would
28404 // incorrectly delete the character just before the cursor.
28405 let should_backspace = new_selected_ranges.iter().any(|r| r.start != r.end);
28406 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28407 selections.select_ranges(new_selected_ranges)
28408 });
28409 if should_backspace {
28410 this.backspace(&Default::default(), window, cx);
28411 }
28412 }
28413
28414 this.handle_input(text, window, cx);
28415 });
28416
28417 if let Some(transaction) = self.ime_transaction {
28418 self.buffer.update(cx, |buffer, cx| {
28419 buffer.group_until_transaction(transaction, cx);
28420 });
28421 }
28422
28423 self.unmark_text(window, cx);
28424 }
28425
28426 fn replace_and_mark_text_in_range(
28427 &mut self,
28428 range_utf16: Option<Range<usize>>,
28429 text: &str,
28430 new_selected_range_utf16: Option<Range<usize>>,
28431 window: &mut Window,
28432 cx: &mut Context<Self>,
28433 ) {
28434 if !self.input_enabled {
28435 return;
28436 }
28437
28438 let transaction = self.transact(window, cx, |this, window, cx| {
28439 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
28440 let snapshot = this.buffer.read(cx).read(cx);
28441 if let Some(relative_range_utf16) = range_utf16.as_ref() {
28442 for marked_range in &mut marked_ranges {
28443 marked_range.end = marked_range.start + relative_range_utf16.end;
28444 marked_range.start += relative_range_utf16.start;
28445 marked_range.start =
28446 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
28447 marked_range.end =
28448 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
28449 }
28450 }
28451 Some(marked_ranges)
28452 } else if let Some(range_utf16) = range_utf16 {
28453 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
28454 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
28455 Some(this.selection_replacement_ranges(range_utf16, cx))
28456 } else {
28457 None
28458 };
28459
28460 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
28461 let newest_selection_id = this.selections.newest_anchor().id;
28462 this.selections
28463 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
28464 .iter()
28465 .zip(ranges_to_replace.iter())
28466 .find_map(|(selection, range)| {
28467 if selection.id == newest_selection_id {
28468 Some(
28469 (range.start.0.0 as isize - selection.head().0.0 as isize)
28470 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
28471 )
28472 } else {
28473 None
28474 }
28475 })
28476 });
28477
28478 cx.emit(EditorEvent::InputHandled {
28479 utf16_range_to_replace: range_to_replace,
28480 text: text.into(),
28481 });
28482
28483 if let Some(ranges) = ranges_to_replace {
28484 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28485 s.select_ranges(ranges)
28486 });
28487 }
28488
28489 let marked_ranges = {
28490 let snapshot = this.buffer.read(cx).read(cx);
28491 this.selections
28492 .disjoint_anchors_arc()
28493 .iter()
28494 .map(|selection| {
28495 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
28496 })
28497 .collect::<Vec<_>>()
28498 };
28499
28500 if text.is_empty() {
28501 this.unmark_text(window, cx);
28502 } else {
28503 this.highlight_text(
28504 HighlightKey::InputComposition,
28505 marked_ranges.clone(),
28506 HighlightStyle {
28507 underline: Some(UnderlineStyle {
28508 thickness: px(1.),
28509 color: None,
28510 wavy: false,
28511 }),
28512 ..Default::default()
28513 },
28514 cx,
28515 );
28516 }
28517
28518 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
28519 let use_autoclose = this.use_autoclose;
28520 let use_auto_surround = this.use_auto_surround;
28521 this.set_use_autoclose(false);
28522 this.set_use_auto_surround(false);
28523 this.handle_input(text, window, cx);
28524 this.set_use_autoclose(use_autoclose);
28525 this.set_use_auto_surround(use_auto_surround);
28526
28527 if let Some(new_selected_range) = new_selected_range_utf16 {
28528 let snapshot = this.buffer.read(cx).read(cx);
28529 let new_selected_ranges = marked_ranges
28530 .into_iter()
28531 .map(|marked_range| {
28532 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
28533 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
28534 insertion_start.0 + new_selected_range.start,
28535 ));
28536 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
28537 insertion_start.0 + new_selected_range.end,
28538 ));
28539 snapshot.clip_offset_utf16(new_start, Bias::Left)
28540 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
28541 })
28542 .collect::<Vec<_>>();
28543
28544 drop(snapshot);
28545 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
28546 selections.select_ranges(new_selected_ranges)
28547 });
28548 }
28549 });
28550
28551 self.ime_transaction = self.ime_transaction.or(transaction);
28552 if let Some(transaction) = self.ime_transaction {
28553 self.buffer.update(cx, |buffer, cx| {
28554 buffer.group_until_transaction(transaction, cx);
28555 });
28556 }
28557
28558 if self
28559 .text_highlights(HighlightKey::InputComposition, cx)
28560 .is_none()
28561 {
28562 self.ime_transaction.take();
28563 }
28564 }
28565
28566 fn bounds_for_range(
28567 &mut self,
28568 range_utf16: Range<usize>,
28569 element_bounds: gpui::Bounds<Pixels>,
28570 window: &mut Window,
28571 cx: &mut Context<Self>,
28572 ) -> Option<gpui::Bounds<Pixels>> {
28573 let text_layout_details = self.text_layout_details(window, cx);
28574 let CharacterDimensions {
28575 em_width,
28576 em_advance,
28577 line_height,
28578 } = self.character_dimensions(window, cx);
28579
28580 let snapshot = self.snapshot(window, cx);
28581 let scroll_position = snapshot.scroll_position();
28582 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
28583
28584 let start =
28585 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
28586 let x = Pixels::from(
28587 ScrollOffset::from(
28588 snapshot.x_for_display_point(start, &text_layout_details)
28589 + self.gutter_dimensions.full_width(),
28590 ) - scroll_left,
28591 );
28592 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
28593
28594 Some(Bounds {
28595 origin: element_bounds.origin + point(x, y),
28596 size: size(em_width, line_height),
28597 })
28598 }
28599
28600 fn character_index_for_point(
28601 &mut self,
28602 point: gpui::Point<Pixels>,
28603 _window: &mut Window,
28604 _cx: &mut Context<Self>,
28605 ) -> Option<usize> {
28606 let position_map = self.last_position_map.as_ref()?;
28607 if !position_map.text_hitbox.contains(&point) {
28608 return None;
28609 }
28610 let display_point = position_map.point_for_position(point).previous_valid;
28611 let anchor = position_map
28612 .snapshot
28613 .display_point_to_anchor(display_point, Bias::Left);
28614 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
28615 Some(utf16_offset.0.0)
28616 }
28617
28618 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
28619 self.expects_character_input
28620 }
28621}
28622
28623trait SelectionExt {
28624 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
28625 fn spanned_rows(
28626 &self,
28627 include_end_if_at_line_start: bool,
28628 map: &DisplaySnapshot,
28629 ) -> Range<MultiBufferRow>;
28630}
28631
28632impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
28633 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
28634 let start = self
28635 .start
28636 .to_point(map.buffer_snapshot())
28637 .to_display_point(map);
28638 let end = self
28639 .end
28640 .to_point(map.buffer_snapshot())
28641 .to_display_point(map);
28642 if self.reversed {
28643 end..start
28644 } else {
28645 start..end
28646 }
28647 }
28648
28649 fn spanned_rows(
28650 &self,
28651 include_end_if_at_line_start: bool,
28652 map: &DisplaySnapshot,
28653 ) -> Range<MultiBufferRow> {
28654 let start = self.start.to_point(map.buffer_snapshot());
28655 let mut end = self.end.to_point(map.buffer_snapshot());
28656 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
28657 end.row -= 1;
28658 }
28659
28660 let buffer_start = map.prev_line_boundary(start).0;
28661 let buffer_end = map.next_line_boundary(end).0;
28662 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
28663 }
28664}
28665
28666impl<T: InvalidationRegion> InvalidationStack<T> {
28667 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
28668 where
28669 S: Clone + ToOffset,
28670 {
28671 while let Some(region) = self.last() {
28672 let all_selections_inside_invalidation_ranges =
28673 if selections.len() == region.ranges().len() {
28674 selections
28675 .iter()
28676 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
28677 .all(|(selection, invalidation_range)| {
28678 let head = selection.head().to_offset(buffer);
28679 invalidation_range.start <= head && invalidation_range.end >= head
28680 })
28681 } else {
28682 false
28683 };
28684
28685 if all_selections_inside_invalidation_ranges {
28686 break;
28687 } else {
28688 self.pop();
28689 }
28690 }
28691 }
28692}
28693
28694#[derive(Clone)]
28695struct ErasedEditorImpl(Entity<Editor>);
28696
28697impl ui_input::ErasedEditor for ErasedEditorImpl {
28698 fn text(&self, cx: &App) -> String {
28699 self.0.read(cx).text(cx)
28700 }
28701
28702 fn set_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28703 self.0.update(cx, |this, cx| {
28704 this.set_text(text, window, cx);
28705 })
28706 }
28707
28708 fn clear(&self, window: &mut Window, cx: &mut App) {
28709 self.0.update(cx, |this, cx| this.clear(window, cx));
28710 }
28711
28712 fn set_placeholder_text(&self, text: &str, window: &mut Window, cx: &mut App) {
28713 self.0.update(cx, |this, cx| {
28714 this.set_placeholder_text(text, window, cx);
28715 });
28716 }
28717
28718 fn focus_handle(&self, cx: &App) -> FocusHandle {
28719 self.0.read(cx).focus_handle(cx)
28720 }
28721
28722 fn render(&self, _: &mut Window, cx: &App) -> AnyElement {
28723 let settings = ThemeSettings::get_global(cx);
28724 let theme_color = cx.theme().colors();
28725
28726 let text_style = TextStyle {
28727 font_family: settings.ui_font.family.clone(),
28728 font_features: settings.ui_font.features.clone(),
28729 font_size: rems(0.875).into(),
28730 font_weight: settings.ui_font.weight,
28731 font_style: FontStyle::Normal,
28732 line_height: relative(1.2),
28733 color: theme_color.text,
28734 ..Default::default()
28735 };
28736 let editor_style = EditorStyle {
28737 background: theme_color.ghost_element_background,
28738 local_player: cx.theme().players().local(),
28739 syntax: cx.theme().syntax().clone(),
28740 text: text_style,
28741 ..Default::default()
28742 };
28743 EditorElement::new(&self.0, editor_style).into_any()
28744 }
28745
28746 fn as_any(&self) -> &dyn Any {
28747 &self.0
28748 }
28749
28750 fn move_selection_to_end(&self, window: &mut Window, cx: &mut App) {
28751 self.0.update(cx, |editor, cx| {
28752 let editor_offset = editor.buffer().read(cx).len(cx);
28753 editor.change_selections(
28754 SelectionEffects::scroll(Autoscroll::Next),
28755 window,
28756 cx,
28757 |s| s.select_ranges(Some(editor_offset..editor_offset)),
28758 );
28759 });
28760 }
28761
28762 fn subscribe(
28763 &self,
28764 mut callback: Box<dyn FnMut(ui_input::ErasedEditorEvent, &mut Window, &mut App) + 'static>,
28765 window: &mut Window,
28766 cx: &mut App,
28767 ) -> Subscription {
28768 window.subscribe(&self.0, cx, move |_, event: &EditorEvent, window, cx| {
28769 let event = match event {
28770 EditorEvent::BufferEdited => ui_input::ErasedEditorEvent::BufferEdited,
28771 EditorEvent::Blurred => ui_input::ErasedEditorEvent::Blurred,
28772 _ => return,
28773 };
28774 (callback)(event, window, cx);
28775 })
28776 }
28777
28778 fn set_masked(&self, masked: bool, _window: &mut Window, cx: &mut App) {
28779 self.0.update(cx, |editor, cx| {
28780 editor.set_masked(masked, cx);
28781 });
28782 }
28783}
28784impl<T> Default for InvalidationStack<T> {
28785 fn default() -> Self {
28786 Self(Default::default())
28787 }
28788}
28789
28790impl<T> Deref for InvalidationStack<T> {
28791 type Target = Vec<T>;
28792
28793 fn deref(&self) -> &Self::Target {
28794 &self.0
28795 }
28796}
28797
28798impl<T> DerefMut for InvalidationStack<T> {
28799 fn deref_mut(&mut self) -> &mut Self::Target {
28800 &mut self.0
28801 }
28802}
28803
28804impl InvalidationRegion for SnippetState {
28805 fn ranges(&self) -> &[Range<Anchor>] {
28806 &self.ranges[self.active_index]
28807 }
28808}
28809
28810fn edit_prediction_edit_text(
28811 current_snapshot: &BufferSnapshot,
28812 edits: &[(Range<Anchor>, impl AsRef<str>)],
28813 edit_preview: &EditPreview,
28814 include_deletions: bool,
28815 cx: &App,
28816) -> HighlightedText {
28817 let edits = edits
28818 .iter()
28819 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
28820 .collect::<Vec<_>>();
28821
28822 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
28823}
28824
28825fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
28826 // Fallback for providers that don't provide edit_preview (like Copilot)
28827 // Just show the raw edit text with basic styling
28828 let mut text = String::new();
28829 let mut highlights = Vec::new();
28830
28831 let insertion_highlight_style = HighlightStyle {
28832 color: Some(cx.theme().colors().text),
28833 ..Default::default()
28834 };
28835
28836 for (_, edit_text) in edits {
28837 let start_offset = text.len();
28838 text.push_str(edit_text);
28839 let end_offset = text.len();
28840
28841 if start_offset < end_offset {
28842 highlights.push((start_offset..end_offset, insertion_highlight_style));
28843 }
28844 }
28845
28846 HighlightedText {
28847 text: text.into(),
28848 highlights,
28849 }
28850}
28851
28852pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
28853 match severity {
28854 lsp::DiagnosticSeverity::ERROR => colors.error,
28855 lsp::DiagnosticSeverity::WARNING => colors.warning,
28856 lsp::DiagnosticSeverity::INFORMATION => colors.info,
28857 lsp::DiagnosticSeverity::HINT => colors.info,
28858 _ => colors.ignored,
28859 }
28860}
28861
28862pub fn styled_runs_for_code_label<'a>(
28863 label: &'a CodeLabel,
28864 syntax_theme: &'a theme::SyntaxTheme,
28865 local_player: &'a theme::PlayerColor,
28866) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
28867 let fade_out = HighlightStyle {
28868 fade_out: Some(0.35),
28869 ..Default::default()
28870 };
28871
28872 if label.runs.is_empty() {
28873 let desc_start = label.filter_range.end;
28874 let fade_run =
28875 (desc_start < label.text.len()).then(|| (desc_start..label.text.len(), fade_out));
28876 return Either::Left(fade_run.into_iter());
28877 }
28878
28879 let mut prev_end = label.filter_range.end;
28880 Either::Right(
28881 label
28882 .runs
28883 .iter()
28884 .enumerate()
28885 .flat_map(move |(ix, (range, highlight_id))| {
28886 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
28887 HighlightStyle {
28888 color: Some(local_player.cursor),
28889 ..Default::default()
28890 }
28891 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
28892 HighlightStyle {
28893 background_color: Some(local_player.selection),
28894 ..Default::default()
28895 }
28896 } else if let Some(style) = syntax_theme.get(*highlight_id).cloned() {
28897 style
28898 } else {
28899 return Default::default();
28900 };
28901
28902 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
28903 let muted_style = style.highlight(fade_out);
28904 if range.start >= label.filter_range.end {
28905 if range.start > prev_end {
28906 runs.push((prev_end..range.start, fade_out));
28907 }
28908 runs.push((range.clone(), muted_style));
28909 } else if range.end <= label.filter_range.end {
28910 runs.push((range.clone(), style));
28911 } else {
28912 runs.push((range.start..label.filter_range.end, style));
28913 runs.push((label.filter_range.end..range.end, muted_style));
28914 }
28915 prev_end = cmp::max(prev_end, range.end);
28916
28917 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
28918 runs.push((prev_end..label.text.len(), fade_out));
28919 }
28920
28921 runs
28922 }),
28923 )
28924}
28925
28926pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
28927 let mut prev_index = 0;
28928 let mut prev_codepoint: Option<char> = None;
28929 text.char_indices()
28930 .chain([(text.len(), '\0')])
28931 .filter_map(move |(index, codepoint)| {
28932 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28933 let is_boundary = index == text.len()
28934 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
28935 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
28936 if is_boundary {
28937 let chunk = &text[prev_index..index];
28938 prev_index = index;
28939 Some(chunk)
28940 } else {
28941 None
28942 }
28943 })
28944}
28945
28946/// Given a string of text immediately before the cursor, iterates over possible
28947/// strings a snippet could match to. More precisely: returns an iterator over
28948/// suffixes of `text` created by splitting at word boundaries (before & after
28949/// every non-word character).
28950///
28951/// Shorter suffixes are returned first.
28952pub(crate) fn snippet_candidate_suffixes<'a>(
28953 text: &'a str,
28954 is_word_char: &'a dyn Fn(char) -> bool,
28955) -> impl std::iter::Iterator<Item = &'a str> + 'a {
28956 let mut prev_index = text.len();
28957 let mut prev_codepoint = None;
28958 text.char_indices()
28959 .rev()
28960 .chain([(0, '\0')])
28961 .filter_map(move |(index, codepoint)| {
28962 let prev_index = std::mem::replace(&mut prev_index, index);
28963 let prev_codepoint = prev_codepoint.replace(codepoint)?;
28964 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
28965 None
28966 } else {
28967 let chunk = &text[prev_index..]; // go to end of string
28968 Some(chunk)
28969 }
28970 })
28971}
28972
28973pub trait RangeToAnchorExt: Sized {
28974 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
28975
28976 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
28977 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
28978 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
28979 }
28980}
28981
28982impl<T: ToOffset> RangeToAnchorExt for Range<T> {
28983 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
28984 let start_offset = self.start.to_offset(snapshot);
28985 let end_offset = self.end.to_offset(snapshot);
28986 if start_offset == end_offset {
28987 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
28988 } else {
28989 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
28990 }
28991 }
28992}
28993
28994pub trait RowExt {
28995 fn as_f64(&self) -> f64;
28996
28997 fn next_row(&self) -> Self;
28998
28999 fn previous_row(&self) -> Self;
29000
29001 fn minus(&self, other: Self) -> u32;
29002}
29003
29004impl RowExt for DisplayRow {
29005 fn as_f64(&self) -> f64 {
29006 self.0 as _
29007 }
29008
29009 fn next_row(&self) -> Self {
29010 Self(self.0 + 1)
29011 }
29012
29013 fn previous_row(&self) -> Self {
29014 Self(self.0.saturating_sub(1))
29015 }
29016
29017 fn minus(&self, other: Self) -> u32 {
29018 self.0 - other.0
29019 }
29020}
29021
29022impl RowExt for MultiBufferRow {
29023 fn as_f64(&self) -> f64 {
29024 self.0 as _
29025 }
29026
29027 fn next_row(&self) -> Self {
29028 Self(self.0 + 1)
29029 }
29030
29031 fn previous_row(&self) -> Self {
29032 Self(self.0.saturating_sub(1))
29033 }
29034
29035 fn minus(&self, other: Self) -> u32 {
29036 self.0 - other.0
29037 }
29038}
29039
29040trait RowRangeExt {
29041 type Row;
29042
29043 fn len(&self) -> usize;
29044
29045 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
29046}
29047
29048impl RowRangeExt for Range<MultiBufferRow> {
29049 type Row = MultiBufferRow;
29050
29051 fn len(&self) -> usize {
29052 (self.end.0 - self.start.0) as usize
29053 }
29054
29055 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
29056 (self.start.0..self.end.0).map(MultiBufferRow)
29057 }
29058}
29059
29060impl RowRangeExt for Range<DisplayRow> {
29061 type Row = DisplayRow;
29062
29063 fn len(&self) -> usize {
29064 (self.end.0 - self.start.0) as usize
29065 }
29066
29067 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
29068 (self.start.0..self.end.0).map(DisplayRow)
29069 }
29070}
29071
29072/// If select range has more than one line, we
29073/// just point the cursor to range.start.
29074fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
29075 if range.start.row == range.end.row {
29076 range
29077 } else {
29078 range.start..range.start
29079 }
29080}
29081pub struct KillRing(ClipboardItem);
29082impl Global for KillRing {}
29083
29084const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
29085
29086enum BreakpointPromptEditAction {
29087 Log,
29088 Condition,
29089 HitCondition,
29090}
29091
29092struct BreakpointPromptEditor {
29093 pub(crate) prompt: Entity<Editor>,
29094 editor: WeakEntity<Editor>,
29095 breakpoint_anchor: Anchor,
29096 breakpoint: Breakpoint,
29097 edit_action: BreakpointPromptEditAction,
29098 block_ids: HashSet<CustomBlockId>,
29099 editor_margins: Arc<Mutex<EditorMargins>>,
29100 _subscriptions: Vec<Subscription>,
29101}
29102
29103impl BreakpointPromptEditor {
29104 const MAX_LINES: u8 = 4;
29105
29106 fn new(
29107 editor: WeakEntity<Editor>,
29108 breakpoint_anchor: Anchor,
29109 breakpoint: Breakpoint,
29110 edit_action: BreakpointPromptEditAction,
29111 window: &mut Window,
29112 cx: &mut Context<Self>,
29113 ) -> Self {
29114 let base_text = match edit_action {
29115 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
29116 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
29117 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
29118 }
29119 .map(|msg| msg.to_string())
29120 .unwrap_or_default();
29121
29122 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
29123 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29124
29125 let prompt = cx.new(|cx| {
29126 let mut prompt = Editor::new(
29127 EditorMode::AutoHeight {
29128 min_lines: 1,
29129 max_lines: Some(Self::MAX_LINES as usize),
29130 },
29131 buffer,
29132 None,
29133 window,
29134 cx,
29135 );
29136 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
29137 prompt.set_show_cursor_when_unfocused(false, cx);
29138 prompt.set_placeholder_text(
29139 match edit_action {
29140 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
29141 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
29142 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
29143 },
29144 window,
29145 cx,
29146 );
29147
29148 prompt
29149 });
29150
29151 Self {
29152 prompt,
29153 editor,
29154 breakpoint_anchor,
29155 breakpoint,
29156 edit_action,
29157 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
29158 block_ids: Default::default(),
29159 _subscriptions: vec![],
29160 }
29161 }
29162
29163 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
29164 self.block_ids.extend(block_ids)
29165 }
29166
29167 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
29168 if let Some(editor) = self.editor.upgrade() {
29169 let message = self
29170 .prompt
29171 .read(cx)
29172 .buffer
29173 .read(cx)
29174 .as_singleton()
29175 .expect("A multi buffer in breakpoint prompt isn't possible")
29176 .read(cx)
29177 .as_rope()
29178 .to_string();
29179
29180 editor.update(cx, |editor, cx| {
29181 editor.edit_breakpoint_at_anchor(
29182 self.breakpoint_anchor,
29183 self.breakpoint.clone(),
29184 match self.edit_action {
29185 BreakpointPromptEditAction::Log => {
29186 BreakpointEditAction::EditLogMessage(message.into())
29187 }
29188 BreakpointPromptEditAction::Condition => {
29189 BreakpointEditAction::EditCondition(message.into())
29190 }
29191 BreakpointPromptEditAction::HitCondition => {
29192 BreakpointEditAction::EditHitCondition(message.into())
29193 }
29194 },
29195 cx,
29196 );
29197
29198 editor.remove_blocks(self.block_ids.clone(), None, cx);
29199 cx.focus_self(window);
29200 });
29201 }
29202 }
29203
29204 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
29205 self.editor
29206 .update(cx, |editor, cx| {
29207 editor.remove_blocks(self.block_ids.clone(), None, cx);
29208 window.focus(&editor.focus_handle, cx);
29209 })
29210 .log_err();
29211 }
29212
29213 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
29214 let settings = ThemeSettings::get_global(cx);
29215 let text_style = TextStyle {
29216 color: if self.prompt.read(cx).read_only(cx) {
29217 cx.theme().colors().text_disabled
29218 } else {
29219 cx.theme().colors().text
29220 },
29221 font_family: settings.buffer_font.family.clone(),
29222 font_fallbacks: settings.buffer_font.fallbacks.clone(),
29223 font_size: settings.buffer_font_size(cx).into(),
29224 font_weight: settings.buffer_font.weight,
29225 line_height: relative(settings.buffer_line_height.value()),
29226 ..Default::default()
29227 };
29228 EditorElement::new(
29229 &self.prompt,
29230 EditorStyle {
29231 background: cx.theme().colors().editor_background,
29232 local_player: cx.theme().players().local(),
29233 text: text_style,
29234 ..Default::default()
29235 },
29236 )
29237 }
29238
29239 fn render_close_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29240 let focus_handle = self.prompt.focus_handle(cx);
29241 IconButton::new("cancel", IconName::Close)
29242 .icon_color(Color::Muted)
29243 .shape(IconButtonShape::Square)
29244 .tooltip(move |_window, cx| {
29245 Tooltip::for_action_in("Cancel", &menu::Cancel, &focus_handle, cx)
29246 })
29247 .on_click(cx.listener(|this, _, window, cx| {
29248 this.cancel(&menu::Cancel, window, cx);
29249 }))
29250 }
29251
29252 fn render_confirm_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
29253 let focus_handle = self.prompt.focus_handle(cx);
29254 IconButton::new("confirm", IconName::Return)
29255 .icon_color(Color::Muted)
29256 .shape(IconButtonShape::Square)
29257 .tooltip(move |_window, cx| {
29258 Tooltip::for_action_in("Confirm", &menu::Confirm, &focus_handle, cx)
29259 })
29260 .on_click(cx.listener(|this, _, window, cx| {
29261 this.confirm(&menu::Confirm, window, cx);
29262 }))
29263 }
29264}
29265
29266impl Render for BreakpointPromptEditor {
29267 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29268 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
29269 let editor_margins = *self.editor_margins.lock();
29270 let gutter_dimensions = editor_margins.gutter;
29271 let left_gutter_width = gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0);
29272 let right_padding = editor_margins.right + px(9.);
29273 h_flex()
29274 .key_context("Editor")
29275 .bg(cx.theme().colors().editor_background)
29276 .border_y_1()
29277 .border_color(cx.theme().status().info_border)
29278 .size_full()
29279 .py(window.line_height() / 2.5)
29280 .pr(right_padding)
29281 .on_action(cx.listener(Self::confirm))
29282 .on_action(cx.listener(Self::cancel))
29283 .child(
29284 WithRemSize::new(ui_font_size)
29285 .h_full()
29286 .w(left_gutter_width)
29287 .flex()
29288 .flex_row()
29289 .flex_shrink_0()
29290 .items_center()
29291 .justify_center()
29292 .gap_1()
29293 .child(self.render_close_button(cx)),
29294 )
29295 .child(
29296 h_flex()
29297 .w_full()
29298 .justify_between()
29299 .child(div().flex_1().child(self.render_prompt_editor(cx)))
29300 .child(
29301 WithRemSize::new(ui_font_size)
29302 .flex()
29303 .flex_row()
29304 .items_center()
29305 .child(self.render_confirm_button(cx)),
29306 ),
29307 )
29308 }
29309}
29310
29311impl Focusable for BreakpointPromptEditor {
29312 fn focus_handle(&self, cx: &App) -> FocusHandle {
29313 self.prompt.focus_handle(cx)
29314 }
29315}
29316
29317fn all_edits_insertions_or_deletions(
29318 edits: &Vec<(Range<Anchor>, Arc<str>)>,
29319 snapshot: &MultiBufferSnapshot,
29320) -> bool {
29321 let mut all_insertions = true;
29322 let mut all_deletions = true;
29323
29324 for (range, new_text) in edits.iter() {
29325 let range_is_empty = range.to_offset(snapshot).is_empty();
29326 let text_is_empty = new_text.is_empty();
29327
29328 if range_is_empty != text_is_empty {
29329 if range_is_empty {
29330 all_deletions = false;
29331 } else {
29332 all_insertions = false;
29333 }
29334 } else {
29335 return false;
29336 }
29337
29338 if !all_insertions && !all_deletions {
29339 return false;
29340 }
29341 }
29342 all_insertions || all_deletions
29343}
29344
29345struct MissingEditPredictionKeybindingTooltip;
29346
29347impl Render for MissingEditPredictionKeybindingTooltip {
29348 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
29349 ui::tooltip_container(cx, |container, cx| {
29350 container
29351 .flex_shrink_0()
29352 .max_w_80()
29353 .min_h(rems_from_px(124.))
29354 .justify_between()
29355 .child(
29356 v_flex()
29357 .flex_1()
29358 .text_ui_sm(cx)
29359 .child(Label::new("Conflict with Accept Keybinding"))
29360 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
29361 )
29362 .child(
29363 h_flex()
29364 .pb_1()
29365 .gap_1()
29366 .items_end()
29367 .w_full()
29368 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
29369 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
29370 }))
29371 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
29372 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
29373 })),
29374 )
29375 })
29376 }
29377}
29378
29379#[derive(Debug, Clone, Copy, PartialEq)]
29380pub struct LineHighlight {
29381 pub background: Background,
29382 pub border: Option<gpui::Hsla>,
29383 pub include_gutter: bool,
29384 pub type_id: Option<TypeId>,
29385}
29386
29387struct LineManipulationResult {
29388 pub new_text: String,
29389 pub line_count_before: usize,
29390 pub line_count_after: usize,
29391}
29392
29393fn render_diff_hunk_controls(
29394 row: u32,
29395 status: &DiffHunkStatus,
29396 hunk_range: Range<Anchor>,
29397 is_created_file: bool,
29398 line_height: Pixels,
29399 editor: &Entity<Editor>,
29400 _window: &mut Window,
29401 cx: &mut App,
29402) -> AnyElement {
29403 h_flex()
29404 .h(line_height)
29405 .mr_1()
29406 .gap_1()
29407 .px_0p5()
29408 .pb_1()
29409 .border_x_1()
29410 .border_b_1()
29411 .border_color(cx.theme().colors().border_variant)
29412 .rounded_b_lg()
29413 .bg(cx.theme().colors().editor_background)
29414 .gap_1()
29415 .block_mouse_except_scroll()
29416 .shadow_md()
29417 .child(if status.has_secondary_hunk() {
29418 Button::new(("stage", row as u64), "Stage")
29419 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29420 .tooltip({
29421 let focus_handle = editor.focus_handle(cx);
29422 move |_window, cx| {
29423 Tooltip::for_action_in(
29424 "Stage Hunk",
29425 &::git::ToggleStaged,
29426 &focus_handle,
29427 cx,
29428 )
29429 }
29430 })
29431 .on_click({
29432 let editor = editor.clone();
29433 move |_event, _window, cx| {
29434 editor.update(cx, |editor, cx| {
29435 editor.stage_or_unstage_diff_hunks(
29436 true,
29437 vec![hunk_range.start..hunk_range.start],
29438 cx,
29439 );
29440 });
29441 }
29442 })
29443 } else {
29444 Button::new(("unstage", row as u64), "Unstage")
29445 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
29446 .tooltip({
29447 let focus_handle = editor.focus_handle(cx);
29448 move |_window, cx| {
29449 Tooltip::for_action_in(
29450 "Unstage Hunk",
29451 &::git::ToggleStaged,
29452 &focus_handle,
29453 cx,
29454 )
29455 }
29456 })
29457 .on_click({
29458 let editor = editor.clone();
29459 move |_event, _window, cx| {
29460 editor.update(cx, |editor, cx| {
29461 editor.stage_or_unstage_diff_hunks(
29462 false,
29463 vec![hunk_range.start..hunk_range.start],
29464 cx,
29465 );
29466 });
29467 }
29468 })
29469 })
29470 .child(
29471 Button::new(("restore", row as u64), "Restore")
29472 .tooltip({
29473 let focus_handle = editor.focus_handle(cx);
29474 move |_window, cx| {
29475 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
29476 }
29477 })
29478 .on_click({
29479 let editor = editor.clone();
29480 move |_event, window, cx| {
29481 editor.update(cx, |editor, cx| {
29482 let snapshot = editor.snapshot(window, cx);
29483 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
29484 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
29485 });
29486 }
29487 })
29488 .disabled(is_created_file),
29489 )
29490 .when(
29491 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
29492 |el| {
29493 el.child(
29494 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
29495 .shape(IconButtonShape::Square)
29496 .icon_size(IconSize::Small)
29497 // .disabled(!has_multiple_hunks)
29498 .tooltip({
29499 let focus_handle = editor.focus_handle(cx);
29500 move |_window, cx| {
29501 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
29502 }
29503 })
29504 .on_click({
29505 let editor = editor.clone();
29506 move |_event, window, cx| {
29507 editor.update(cx, |editor, cx| {
29508 let snapshot = editor.snapshot(window, cx);
29509 let position =
29510 hunk_range.end.to_point(&snapshot.buffer_snapshot());
29511 editor.go_to_hunk_before_or_after_position(
29512 &snapshot,
29513 position,
29514 Direction::Next,
29515 true,
29516 window,
29517 cx,
29518 );
29519 editor.expand_selected_diff_hunks(cx);
29520 });
29521 }
29522 }),
29523 )
29524 .child(
29525 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
29526 .shape(IconButtonShape::Square)
29527 .icon_size(IconSize::Small)
29528 // .disabled(!has_multiple_hunks)
29529 .tooltip({
29530 let focus_handle = editor.focus_handle(cx);
29531 move |_window, cx| {
29532 Tooltip::for_action_in(
29533 "Previous Hunk",
29534 &GoToPreviousHunk,
29535 &focus_handle,
29536 cx,
29537 )
29538 }
29539 })
29540 .on_click({
29541 let editor = editor.clone();
29542 move |_event, window, cx| {
29543 editor.update(cx, |editor, cx| {
29544 let snapshot = editor.snapshot(window, cx);
29545 let point =
29546 hunk_range.start.to_point(&snapshot.buffer_snapshot());
29547 editor.go_to_hunk_before_or_after_position(
29548 &snapshot,
29549 point,
29550 Direction::Prev,
29551 true,
29552 window,
29553 cx,
29554 );
29555 editor.expand_selected_diff_hunks(cx);
29556 });
29557 }
29558 }),
29559 )
29560 },
29561 )
29562 .into_any_element()
29563}
29564
29565pub fn multibuffer_context_lines(cx: &App) -> u32 {
29566 EditorSettings::try_get(cx)
29567 .map(|settings| settings.excerpt_context_lines)
29568 .unwrap_or(2)
29569 .min(32)
29570}